Search code examples
c#dependency-injectioninversion-of-controlunity-containerioc-container

Hot-swapping dependencies in Unity


I'm just starting with Unity IOC, hoping someone will help. I need to be able to switch the dependencies in Unity at run time. I have two containers each for production and dev/test environments, "prodRepository" and "testRepository" defined in the web.config as follows:

    <unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
        <alias alias="TestDataService" type="MyApp.API.Data.TestDataRepository.TestDataService, MyApp.API.Data" />
        <alias alias="ProdDataService" type="MyApp.API.Data.ProdDataRepository.ProdDataService, MyApp.API.Data" />
        <assembly name="MyApp.API.Data" />
        <container name="testRepository">
            <register type="MyApp.API.Data.IDataService"  mapTo="TestDataService">
                <lifetime type="hierarchical" />
            </register>
        </container>
        <container name="prodRepository">
            <register type="MyApp.API.Data.IDataService"  mapTo="ProdDataService">
                <lifetime type="hierarchical" />
            </register>
        </container>
    </unity>

In the WebApiConfig class the Unit is configured as follows

public static void Register(HttpConfiguration config)
{

    config.DependencyResolver = RegisterUnity("prodRepository");
  //... api configuration ommitted for illustration
}

public static IDependencyResolver RegisterUnity(string containerName)
{
    var container = new UnityContainer();
    container.LoadConfiguration(containerName);
    return new UnityResolver(container);
}

Just for test I created a simple controller and action to switch the configuration:

[HttpGet]
public IHttpActionResult SwitchResolver(string rName)
{

    GlobalConfiguration.Configuration
        .DependencyResolver =   WebApiConfig.RegisterUnity(rName);
    return Ok();
}

and I call it from a web browser: http://localhost/MyApp/api/Test/SwitchResolver?rName=prodRepository

When I try to retrieve the actual data from the repositories via the API, at first it comes from "prodRepository", understandably, as that's how it is initialized in the code. After I switch it to "testRepository" from the browser, the data comes from the test repo as expected. When I switch it back to prodRepository, the API keeps sending me the data from the test repo. I see in the controller that the GlobalConfiguration.Configuration .DependencyResolver changes the container and registrations to the ones specified in the URL query as expected, but it seems to change the configuration only once then stays at that configuration.

Ok, so this evil plan is what I came up with but as I am new to this I am probably going wrong direction altogether. I need to be able to specify dynamically at run-time which container to use, hopefully without reloading the API. Does the above code make sense or what would you suggest?


Solution

  • It looks like you are going awry in many ways:

    1. Using XML to configure a DI container is considered to be an obsolete approach.
    2. Do you really want to access test data from your production environment and vice versa? Usually one environment is chosen through a configuration setting and the setting itself is changed upon deployment to each environment. And in that case, it makes sense to load the data service only 1 time at application startup.
    3. If the answer to #2 is no, one way to get the job done easily and reliably is to use web.config transforms during deployment.
    4. If the answer to #2 is yes, you can solve this by using a strategy pattern, which allows you to create all data services at startup and switch between them at runtime.

    Here is an example of #4:

    NOTE: WebApi is stateless. It doesn't store anything on the server after the request has ended. Furthermore, if your WebApi client is not a browser, you may not be able to use techniques such as session state to store which data provider you are accessing from one request to the next because this depends on cookies.

    Therefore, having a SwitchResolver action is probably nonsensical. You should provide the repository on each request or otherwise have a default repository that can be overridden with a parameter per request.

    Interfaces

    public interface IDataService
    {
        void DoSomething();
        bool AppliesTo(string provider);
    }
    
    public interface IDataServiceStrategy
    {
        void DoSomething(string provider);
    }
    

    Data Services

    public class TestDataService : IDataService
    {
        public void DoSomething()
        {
            // Implementation
        }
    
        public bool AppliesTo(string provider)
        {
            return provider.Equals("testRepository");
        }
    }
    
    public class ProdDataService : IDataService
    {
        public void DoSomething()
        {
            // Implementation
        }
    
        public bool AppliesTo(string provider)
        {
            return provider.Equals("prodRepository");
        }
    }
    

    Strategy

    This is the class that does all of the heavy lifting.

    There is a GetDataService method that returns the selected service based on the passed in string. Note that you could alternatively make this method public in order to return an instance of IDataService to your controller so you wouldn't have to make two implementations of DoSomething.

    public class DataServiceStrategy
    {
        private readonly IDataService[] dataServices;
    
        public DataServiceStrategy(IDataService[] dataServices)
        {
            if (dataServices == null)
                throw new ArgumentNullException("dataServices");
            this.dataServices = dataServices;
        }
    
        public void DoSomething(string provider)
        {
            var dataService = this.GetDataService(provider);
            dataService.DoSomething();
        }
    
        private IDataService GetDataService(string provider)
        {
            var dataService = this.dataServices.Where(ds => ds.AppliesTo(provider));
            if (dataService == null)
            {
                // Note: you could alternatively use a default provider here
                // by passing another parameter through the constructor
                throw new InvalidOperationException("Provider '" + provider + "' not registered.");
            }
            return dataService;
        }
    }
    

    See these alternate implementations for some inspiration:

    Best way to use StructureMap to implement Strategy pattern

    Factory method with DI and Ioc

    Unity Registration

    Here we register the services with Unity using a container extension rather than XML configuration.

    You should also ensure you are using the correct way to register Unity with WebApi as per MSDN.

    public static IDependencyResolver RegisterUnity(string containerName)
    {
        var container = new UnityContainer();
        container.AddNewExtension<MyContainerExtension>();
        return new UnityResolver(container);
    }
    
    public class MyContainerExtension
        : UnityContainerExtension
    {
        protected override void Initialize()
        {
            // Register data services
    
            // Important: In Unity you must give types a name in order to resolve an array of types
            this.Container.RegisterType<IDataService, TestDataService>("TestDataService");
            this.Container.RegisterType<IDataService, ProdDataService>("ProdDataService");
    
            // Register strategy
    
            this.Container.RegisterType<IDataServiceStrategy, DataServiceStrategy>(
                new InjectionConstructor(new ResolvedParameter<IDataService[]>()));
        }
    }
    

    Usage

    public class SomeController : ApiController
    {
        private readonly IDataServiceStrategy dataServiceStrategy;
    
        public SomeController(IDataServiceStrategy dataServiceStrategy)
        {
            if (dataServiceStrategy == null)
                throw new ArgumentNullException("dataServiceStrategy");
            this.dataServiceStrategy = dataServiceStrategy;
        }
    
        // Valid values for rName are "prodRepository" or "testRepository"
        [HttpGet]
        public IHttpActionResult DoSomething(string rName)
        {
            this.dataServiceStrategy.DoSomething(rName);
            return Ok();
        }
    }
    

    I highly recommend you read the book Dependency Injection in .NET by Mark Seemann. It will help lead you down the correct path and help you make the best choices for your application as they apply to DI, which is more than what I can answer on a single question on SO.