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?
It looks like you are going awry in many ways:
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.
public interface IDataService
{
void DoSomething();
bool AppliesTo(string provider);
}
public interface IDataServiceStrategy
{
void DoSomething(string provider);
}
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");
}
}
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
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[]>()));
}
}
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.