I have two assemblies which implement the same interfaces (two different implementations for the same interface). When user logs into web forms application certain variable (flag) is being set to specific value. This variable should be used for loading implementations from one of these assemblies.
When application starts, I have the following code in Global.asax to register or known implementations - I have tried using Autofac and SimpeInjector:
// SimpleInjector
private static void Bootstrap()
{
var container = new Container();
// 2. Configure the container (register)
container.Register<IUserRepository, UserRepository>();
// 3. Store the container for use by Page classes.
Global.container = container;
// 4. Optionally verify the container's configuration.
// Did you know the container can diagnose your configuration?
// For more information, go to: https://bit.ly/YE8OJj.
container.Verify();
VerifyPages(container);
}
// Autofac
protected void Application_Start(object sender, EventArgs e)
{
BundleConfig.RegisterBundles(BundleTable.Bundles);
// Build up your application container and register your dependencies.
var builder = new ContainerBuilder();
builder.RegisterType<UserRepository>().As<IUserRepository>().InstancePerRequest();
// ... continue registering dependencies...
// Once you're done registering things, set the container
// provider up with your registrations.
_containerProvider = new ContainerProvider(builder.Build());
}
Now, after a user logs in, I need to register additional implementations, but from one of the two assemblies. I need to do this from code behind of login.aspx page, I believe.
Now I don't know if I should update the container by registering additional types somehow or if I can override some method of Autofac or SimpleInjector so when it tries to instantiate an implementation for an interface, I can point it to a specific assembly.
How could I implement this behavior? Please advise. Thanks.
What you need is some abstraction that allows you to request some contextual information for the user that allows you to base the decision of on which type to load. For instance:
public interface IUserContext
{
bool IsAdministrator { get; }
}
Here the Administator
property is the thing that determines what types to load for the user. How to implement this class of course completely depends on how to store this information. Perhaps you retrieve this information from the database, or from a cookie, the session, what ever.
If you need an implementation that is depending on something that is ASP.NET specific (cookie, sessie, request), you will need to have an Web application specific implementation, such as:
public class AspNetUserContext : IUserContext
{
public bool IsAdministrator
{
get
{
return HttpContext.Current != null &&
(bool?)HttpContext.Current.Session["isadmin"] == true;
}
}
}
You can register this context using Simple Injector as follows:
container.Register<IUserContext, AspNetUserContext>();
Now if you have a certain abstraction where the implementations must differ based on this flag, you can register a factory delegate as follows:
container.Register<AdministratorUserRepository>();
container.Register<NormalUserRepository>();
container.Register<IUserRepository>(() =>
{
container.GetInstance<IUserContext>().IsAdministrator
? container.GetInstance<AdministratorUserRepository>()
: container.GetInstance<NormalUserRepository>();
});
Or better it would be to create some sort of composite proxy type that allows delegating to the actual repository:
public class AdminSelectableUserRepositoryProxy : IUserRepository
{
private readonly AdministratorUserRepository adminRepo;
private readonly NormalUserRepository normalRepo;
private readonly IUserContext userContext;
public AdminSelectableUserRepositoryProxy(
AdministratorUserRepository adminRepo,
NormalUserRepository normalRepo,
IUserContext userContext)
{
this.adminRepo = adminRepo;
this.normalRepo = normalRepo;
this.userContext = userContext;
}
public User GetById(Guid id)
{
return this.Repository.GetById(id);
}
private IUserRepository Repository
{
get
{
return this.userContext.IsAdministrator ? this.adminRepo : this.normalRepo;
}
}
}
Now the registration will can be simplified to the following:
container.Register<AdministratorUserRepository>();
container.Register<NormalUserRepository>();
container.Register<IUserRepository, AdminSelectableUserRepositoryProxy>();