1

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?

NightOwl888
  • 55,572
  • 24
  • 139
  • 212
JanuszB
  • 68
  • 6

1 Answers1

2

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.

Community
  • 1
  • 1
NightOwl888
  • 55,572
  • 24
  • 139
  • 212
  • NightOwl888, thank you for your post. How specifically would you implement #4 (switching the services at runtime)? Some code would help, thanks. – JanuszB Dec 22 '15 at 16:23
  • Sweet, thank you for all the pointers for future research, much appreciated! – JanuszB Dec 22 '15 at 19:50