0

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.

Michal Diviš
  • 2,008
  • 12
  • 19
  • This seems like it's a job for DI rather than manually using `ActivatorUtilities.CreateInstance`? – DavidG May 26 '22 at 09:28
  • I'm using `ActivatorUtilities.CreateInstance` because I need to pass an additional manual parameter (`Request` in this example) and I'm not aware of a way to do that with DI. – Michal Diviš May 26 '22 at 09:30
  • Does this answer your question? [Using ActivatorUtilities.CreateInstance To Create Instance From Type](https://stackoverflow.com/questions/52644507/using-activatorutilities-createinstance-to-create-instance-from-type) –  May 26 '22 at 09:38
  • I strongly suggest you go learn how DI works then. If `Request` is `HttpRequest`, then that is super easy to inject.. If it's another type that you made, that is also easy to inject. – DavidG May 26 '22 at 09:58
  • I'm creating an new instance of Request everytime, it's not something that can be registered into DI. – Michal Diviš May 26 '22 at 10:01
  • 2
    Prevent passing runtime data to injection constructors, as described [here](https://blogs.cuttingedge.it/steven/p/runtime-data/). Also prevent having multiple injection constructors, as described [here](https://blogs.cuttingedge.it/steven/p/ctors). – Steven May 26 '22 at 10:05
  • @Steven thank you! The articles you've mentioned helped me reconsider the design and do it differently. I'll post an answer explaining what I did. – Michal Diviš May 26 '22 at 10:56

3 Answers3

1

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.

Jeroen van Langen
  • 21,446
  • 3
  • 42
  • 57
  • Thanks, I've done this in the past. The only problem is that it gets very repetitive very fast. I have multiple view models I'd need to create factories for (or a single shared factory). This Activator thing is a way to avoid writing too much same code and construct them dynamically (if I can get it to work). – Michal Diviš May 26 '22 at 10:31
  • I understand what you saying, but I haven't seen a better (more structural) solution. I think solving this with `ActivatorUtilities.CreateInstance` is like using **Dependency Injection** as a **Service locator**. Instead of passing instances as parameters. – Jeroen van Langen May 26 '22 at 10:43
1

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);
Michal Diviš
  • 2,008
  • 12
  • 19
  • It's a nice solution, but calling a static method of a known type breaks the whole idea of decoupling type/interface with dependency injection. It's harder to unit test this. If you inject the CustomActivator, you're back at the factory model. – Jeroen van Langen May 26 '22 at 16:02
  • I'm not a pro with DI, in my limited experience with it, is that DI is mostly suitable for the abillity to replace components for testing. (for example replacing the datalayer) not for individual component who can have separate configuration. – Jeroen van Langen May 26 '22 at 16:17
  • I'm only using this CustomActivator in places where I can't inject anything, namely views. I'm using this in a WPF view (UserControl) to activate an instance of a view model. – Michal Diviš May 26 '22 at 16:17
0

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}");
        }

    }
}
McNets
  • 10,352
  • 3
  • 32
  • 61
  • I don't want to do this manually because my classes might have many dependencies that need to be resolved + additional parameters that need to be specified manually. – Michal Diviš May 26 '22 at 09:59