2

I have this class

public class MyViewModel<T> where T : class
{
    public MyViewModel(
        Func<IEnumerable<T>, MyService<T>> myServiceFactory,
        IEnumerable<T> list) 
    { 
    }
}

and I need to register the Func in the bootstrapper.

I tried something like

builder.Register<Func<IEnumerable<T>, MyService<T>>>(c =>
{
    var ctx = c.Resolve<IComponentContext>();

    return collection => ctx.Resolve<MyService<T>>(collection);
});

but I can't get it working. Can anyone help me?

Steven
  • 166,672
  • 24
  • 332
  • 435
Sergio
  • 2,078
  • 3
  • 24
  • 41
  • You violate dependency inversion principal and inversion of control pattern. It's better to revise your design here. – Alexandr Nikitin Dec 28 '13 at 09:58
  • @AlexandrNikitin: Although I agree that the design should be changed, can you explain at what points he violates DIP? – Steven Dec 28 '13 at 10:49
  • @Steven You are right, thanks. What I don't like is the reference to the IComponentContext inside the MyServiceFactory, this could lead to bugs. – Alexandr Nikitin Dec 29 '13 at 10:45
  • @AlexandrNikitin I must admit I have no idea what IComponentContext is an how it should be used. – Steven Dec 29 '13 at 11:26
  • @Steven It's the base interface of Autofac's container which defines resolution and registration features. – Alexandr Nikitin Dec 29 '13 at 14:38

2 Answers2

4

As you already noticed, what you're trying to do does not compile. Without defining the T somewhere, the C# compiler doesn't understand. Without a staticly defined T, you would have to define the types using reflection, but you'll hit limits of Autofac and C# pretty soon. You're code becomes very nasty. Instead, you'll have to extract this code in a helper method:

private static void RegisterMyServiceFactory<T>(IContainerBuilder builder) {
    builder.Register<Func<IEnumerable<T>, MyService<T>>>(c =>
    {
        var ctx = c.Resolve<IComponentContext>();

        return collection => ctx.Resolve<MyService<T>>(collection);
 });

With this method need to call this method for every T you need in your application:

RegisterMyServiceFactory<Customer>(builder);
RegisterMyServiceFactory<Order>(builder);
RegisterMyServiceFactory<Employee>(builder);

This solution isn't particularly nice, but this is because you're missing an abstraction. Instead of letting your consumers depend upon Func<IEnumerable<T>, MyService<T>>, you should create a specific abstraction for consumers to depend upon:

public interface IServiceFactory<T> {
    MyService<T> Resolve(IEnumerable<T> collection);
}

And embed your code in an IServiceFactory<T> implementation:

private sealed class AutofacServiceFactory<T> : IServiceFactory<T> {
    private readonly IComponentContext context;
    public AutofacServiceFactory(IComponentContext context) {
        this.context = context;
    }

    public MyService<T> Resolve(IEnumerable<T> collection) {
        return context.Resolve<MyService<T>>(collection);
    }
}

You can register this open generic type as follows:

builder.RegisterGeneric(typeof(AutofacServiceFactory<>))
    .As(typeof(IServiceFactory<>));
Steven
  • 166,672
  • 24
  • 332
  • 435
0

If the builder.Register code is executed by an instance of a MyViewModel it could/should work.

Otherwise it depends on how/if the T is declared in your particular context and you'll need to supply more info.

Anyway, your problem does not seem to be related to autofac in anyway. It's related to the use of generics. The Register method is indeed a generic method, but the generic parameter has to be ultimately translated to a concrete type when the call is made. So, unless T is properly declared in the context of the call to builder.Register then it won't work as it seems you are trying to register a whole range of types.

Dan Badea
  • 136
  • 3