Search code examples
c#.net-coreioc-container

Use the ActivatorUtilities to determine which implementation to inject at runtime


I am using the built in .Net Core IoC container to resolve the application's dependencies. The way I have configured it is as follows with the use of Scrutor to scan my assembly:

services.Scan(s => s
    .FromAssembliesOf(currentAssemblyTypesList)
    .AddClasses(false)
    .UsingRegistrationStrategy(RegistrationStrategy.Append)
    .AsImplementedInterfaces()
    .WithTransientLifetime());

Up until now I've had simple cases in which each interface is implemented by one dependency so the previous configuration works perfectly to resolve the whole dependency tree.

Now consider the following code:

public interface IService {}  

public class ServiceOne : IService 
{
     public ServiceOne(IDependency dependency) {}
}  

public class ServiceTwo : IService 
{
     public ServiceTwo(IDependency dependency) {}
}  

public class SomeClass  
{  
    public SomeClass(IService service) {}  

    public void DoSomething()
    {
        this.service.SomeMethod();
    }
}

In this case "SomeClass" is in the middle of a dependency tree and I have two services that implement the same interface and which one should be injected into "SomeClass" will not be known until runtime. For reasons unimportant I have been asked that I should use the ActivatorUtilities class for this.

I am dealing with two scenarios for determining which IService should be instantiated:

  1. At application startup a flag gets set that determines which service is to be used before the registration takes place (for a similar setup but NOT for the same dependency tree).
  2. During the execution of the "DoSomething" method a decision is made for which one of the two services should be used so I'm guessing some kind of factory should be registered and injected to "SomeClass".

So the question is what do I need to change or add to the dependency registration process and how do I use the ActivatorUtilities class to accomplish these objectives?

Thank you.


Solution

  • So the trickiest part was to figure out what to do with the first scenario when the registration has already taken place and it turns out that there is a way to replace a specific definition. This can be accomplished with the following code:

    Func<IServiceProvider, object> factoryMethod = sp =>
    {
        if (condition1 && condition2)
        {
            return ActivatorUtilities.CreateInstance<ServiceOne>(sp);
        }
        else
        {
            return ActivatorUtilities.CreateInstance<ServiceTwo>(sp);
        }
    };
    
    services.Replace(ServiceDescriptor.Describe(typeof(IService), factoryMethod, ServiceLifetime.Transient));
    

    This works very nicely when condition1 and condition2 are known at the time of dependency registration, in other words at application startup before any request has been made.

    For the other scenario in which the conditions are not known until the application is running and a request has been made, since the built in .Net Core IoC container is not as feature rich as others like Castle or Autofac one way of doing this is to manually create a factory method object, something like the following:

    public interface IServiceFactory
    {
        IService Get(MyObject myObject);
    }
    
    public class ServiceFactory : IServiceFactory
    {
        private readonly IServiceProvider sp;
    
        public ServiceFactory(IServiceProvider sp)
        {
            this.sp = sp;
        }
    
        public IService Get(MyObject myObject)
        {
            if(myObject.SomeProperty == "whatever")
            {
                return ActivatorUtilities.CreateInstance<ServiceOne>(this.sp);
            }
            else
            {           
                return ActivatorUtilities.CreateInstance<ServiceTwo>(this.sp);
            }
        }
    }
    

    The only thing to keep in mind here is that the interface can and should be defined wherever all of the rest of the applications interfaces are defined, and you DO NOT want the rest of your application to be tightly coupled to the MicrosoftExtensions.DependencyInjection package because of the use of the IServiceProvider interface so the implementation of the factory should be wherever the rest of the registration logic is defined.

    I looked really hard for an example of ActivatorUtilities.CreateFactory method that would give me something like this but was unable to find one. I hope this is useful for someone.