Home > Net >  How to make the ActivatorUtilities.CreateInstance method use a different constructor?
How to make the ActivatorUtilities.CreateInstance method use a different constructor?

Time:05-26

Is there a way the tell the ActivatorUtilities.CreateInstance<T>(IServiceProvider serviceProvider); method to try to use other constructors if the first one can't be constructed?

I have a class with multiple constructors:

  • public ViewModelB(SomeDependency someDependency): this one only takes SomeDependency which is registered in a DI container
  • public ViewModelB(SomeDependency someDependency, GetUserRequest request): this one takes SomeDependency which is registered in a DI container and a GetUserRequest which has to be passed in manually

And I'm trying to activate them and resolve dependencies like so:

IServiceProvider serviceProvider; //this gets passed from somewhere
Guid userId; //this gets passed manually by the caller

//works
var instanceAWithoutParams = ActivatorUtilities.CreateInstance<ViewModelA>(serviceProvider);
//works
var instanceAWithParams = ActivatorUtilities.CreateInstance<ViewModelA>(serviceProvider, new[] { new GetUserRequest { UserId = userId } });

//does NOT work, it tries to use the first constructor and fails
var instanceBWithoutParams = ActivatorUtilities.CreateInstance<ViewModelB>(serviceProvider);
//works
var instanceBWithParams = ActivatorUtilities.CreateInstance<ViewModelB>(serviceProvider,, new[] { new GetUserRequest { UserId = userId } });

The activation of instanceBWithoutParams fails because it can't resolve the request parameter. It tries to use the first constructor and doesn't check other ones when the activation fails.

Here's what the services look like, they're the same with one difference: the order of the constructors.

public class ViewModelA
{
    private readonly SomeDependency _someDependency;
    private readonly GetUserRequest? _request;

    public ViewModelA(SomeDependency someDependency)
    {
        _someDependency = someDependency;
    }

    public ViewModelA(SomeDependency someDependency, GetUserRequest request)
    {
        _someDependency = someDependency;
        _request = request;
    }
}

public class ViewModelB
{
    private readonly SomeDependency _someDependency;
    private readonly GetUserRequest? _request;

    public ViewModelB(SomeDependency someDependency, GetUserRequest request)
    {
        _someDependency = someDependency;
        _request = request;
    }

    public ViewModelB(SomeDependency someDependency)
    {
        _someDependency = someDependency;
    }
}

public class GetUserRequest
{
    public Guid UserId { get; set; }
}

Thanks.

CodePudding user response:

I struggled with the same issue. Eventually I came up with this solution:

I would use something like a factory which is able to construct ServiceB by calling a method.

For example:

var serviceBFactory = ActivatorUtilities.CreateInstance<ServiceBFactory>(serviceProvider);

var instanceBWithoutParams = serviceBFactory.CreateServiceB();
var instanceBWithParams = serviceBFactory.CreateServiceB(new Request());

This way you keep you DI clean. But this means that the ServiceBFactory need to know which services need to be injected in a ServiceB. (so that will be a tight coupling) They come as a package.

CodePudding user response:

I've chosen to re-design the view models instead of trying to pass optional parameters next to services from DI (thanks to Steven for the helpful articles: 1 and 2).

There also seems to be no way of making the ActivatorUtilities.CreateInstance<T>(IServiceProvider serviceProvider); method try other constructors after one fails, so here's what my edited solution looks like.

I've moved the initialization of the optional parameter out of the constructor, that way I only have one constructor that only takes injectables. The parameter is then passed separately via the TakeParameter method. The only downside I can think of is that the parameter can no longer be readonly and I can live with that.

My custom activator utility:

public interface IAcceptParameter<T>
{
    void TakeParameter(T parameter);
}

public static class CustomActivator
{
    public static T CreateInstance<T>()
    {
        return ActivatorUtilities.CreateInstance<T>(_serviceProvider);
    }

    public static T CreateInstanceWithParam<T, K>(K parameter) where T : IAcceptParameter<K>
    {
        var instance = ActivatorUtilities.CreateInstance<T>(_serviceProvider);
        instance.TakeParameter(parameter);
        return instance;
    }
}

Changed view model

public class SomeViewModel : IAcceptParameter<Guid>
{
    private readonly SomeDependency _someDependency;
    private Guid? _userId;

    public SomeViewModel(SomeDependency someDependency)
    {
        _someDependency = someDependency;
    }

    public void TakeParameter(Guid parameter){
        _userId = parameter;
    }
}

How I use it

var instanceWithoutParam = CustomActivator.CreateInstance<SomeViewModel>(serviceProvider);

Guid userId;
var instanceWithParam = CustomActivator.CreateInstanceWithParam<SomeViewModel, Guid>(serviceProvider, userId);

CodePudding user response:

Let say you have a class like this:

public class a
{
    public string p { get; set; }

    public a()
    {
        p = "default constructor";
    }

    public a(string pv)
    {
        p = pv;
    }
}

You can use .GetConstructor method to use a specific constructor:

public class Program
{
    static void Main(string[] args)
    {
        var c = typeof(a).GetConstructor(new Type[] { typeof(string) });
        if (c != null)
        {
            var myA = (a)c.Invoke(new object[] { "new value" });
            Console.WriteLine($"Value of p is {myA.p}");
        }

    }
}
  • Related