User defined repository injection at runtime

March 01, 2016

A common design pattern is to use a dependency injection container in the composition root to switch between various concrete implementations of an interface abstraction (such as for services, repositories etc.).
The configuration for these bindings are often dependant on the build configuration, or an application configuration settings and are singular, as such that only one "concrete implementation" is bound at run time.
While recently working on a project, we needed to swap out the implementation of each IRepository based on the user's selection. To put this into context, the application is an ASP.NET MVC 5 website that has some common UI functionality, which we can then plug into multiple different data sources, as per the user's choice of repository.
Since we are using Ninject, we can rely on it's ability to bind multiple implementations of an abstraction, by name. We can then use that name (as selected by the user) in a factory method to create instances of each type.
Example Ninject module:

using MyProject.ApplicationServices.ExampleData;
using MyProject.Domain.RepositoryDefinitions.ExampleDataRepository;
using Ninject.Modules;
namespace MyProject.UI.MVC.DependencyInjection
{
public class ExampleDataRepositoryModule : NinjectModule
{
public override void Load()
{
// define the repo names
var exampleRepo1Name = "Example Repo 1";
var exampleRepo2Name = "Example Repo 2";
var exampleRepo3Name = "Example Repo 3";
// setup all the named versions of the service layers for each of the repository names
this.Bind<ExampleDataService>().ToSelf().Named(exampleRepo1Name);
this.Bind<ExampleDataService>().ToSelf().Named(exampleRepo2Name);
this.Bind<ExampleDataService>().ToSelf().Named(exampleRepo3Name);
// set up the repositories that are bound in each of the above cases
this.Bind<IExampleDataRepository>().To<Repositories.ExampleRepo1.ExampleDataRepository>().WhenAnyAncestorNamed(exampleRepo1Name);
this.Bind<IExampleDataRepository>().To<Repositories.ExampleRepo2.ExampleDataRepository>().WhenAnyAncestorNamed(exampleRepo2Name);
this.Bind<IExampleDataRepository>().To<Repositories.ExampleRepo3.ExampleDataRepository>().WhenAnyAncestorNamed(exampleRepo3Name);
}
}
}

We can store the user's selection in some kind of state (in ASP.NET this can be backed by the web session), so we can now use this selection along with the above bindings in a service factory, to create instances of the service with the specified repository.