Search code examples
c#.netdependency-injectionautofac

Registering and Resolving Different Parameter Constructors with different TypeOf in Autofac


I have a class with 2 constructors with 2 different typed parameters in contructors, this works good until i have a single contructor but as soon as I create a another contructor and try to resolve it , the autofac crashes and says Specify a Constructor using constructor configuration, I tried registering the contructor in DI but the first registration overrides the second one

builder.RegisterType<DeviceMasterPageViewModel>().UsingConstructor(typeof(DeviceListModel));
builder.RegisterType<DeviceMasterPageViewModel>().UsingConstructor(typeof(int));

here is my class with 2 contructor

public DeviceMasterPageViewModel(DeviceListModel scannendDevice)
    {}
 public DeviceMasterPageViewModel(int selectedDeviceID)
    {}

i am not able to understand how should I register this class in DI so that when I pass the specific type of parameter the specified typeof contructor should get called


Solution

  • You can only register a component with Autofac one way. The last registration in wins. That's why when you're trying to register twice it's not working.

    In general what you're trying to do is not really compatible with DI. DI really wants there to be one constructor on your object and that's it. Further, one of the constructors you propose has a primitive type in it (int), which is also not great for DI.

    If DI allowed two constructors... how does it choose? Let's say in the resolution context both the int parameter and the DeviceListModel parameter are available. Which constructor is right?

    If you use int in a constructor... since you can really only register one registration of a given System.Type, that means any object you have that takes an int will get the same int value. Is that right?

    So, all that said, let's say you still really want this to happen. You're going to have to write some logic of your own.

    Part of that logic needs to know where you expect the constructor parameters to come from. Are they things you're resolving that should be already registered in the container? Or are they things you're going to pass in?

    That is, you have two basic scenarios:

    # All the stuff the DMPVM needs is _registered_
    var b = new ContainerBuilder();
    b.Register(3).As<int>();
    b.RegisterType<DeviceListModel>().AsSelf();
    b.RegisterType<DeviceMasterPageViewModel>().AsSelf();
    var c = b.Build();
    var d = c.Resolve<DeviceMasterPageViewModel>();
    

    OR

    # You plan on _passing in_ the values
    var b = new ContainerBuilder();
    b.RegisterType<DeviceMasterPageViewModel>().AsSelf();
    var c = b.Build();
    var d = c.Resolve<DeviceMasterPageViewModel>(new TypedParameter(typeof(int), 3));
    

    (This is the crux of the question being asked in the comments. Knowing this is important to the type of custom logic you'll write.)

    If you assume all the things will be registered, then likely you'll need to write your own IConstructorSelector. Autofac ships with two - one that matches the most available parameters and one that matches a specific constructor signature. You can then provide your constructor selector during registration.

    How exactly to write that is going to be up to you. I don't think this is a good idea and it's going to have a lot of caveats to it, like what happens if you have a really long lifetime for the component getting resolved; and what it could do to app perf; and so on. This one's entirely up to you.

    If you're passing in the parameters, it's a little easier. Instead of writing a constructor selector, you could register a lambda. Look for the parameters and use them as needed. This is documented.

    It might be like:

    b.Register((ctx, plist) => {
      var intParam = plist
        .OfType<TypedParameter>()
        .Where(p => p.Type == typeof(int))
        .FirstOrDefault();
      if(intParam != null) {
        return new DeviceMasterPageViewModel((int)intParam.Value);
      }
    
      // int param isn't found, do a similar search for the
      // DeviceListModel parameter.
    }).As<DeviceMasterPageViewModel>();
    

    As you can see, you can do some dynamic factory style logic right there based on the parameters passed in.

    But, again, your life will be far easier if you avoid this entirely.