Provided the number of IUserManager
implementations is not very many (not likely to reach 100 implementations), you can use a Strategy Pattern to resolve all of your UserManager instances during composition and then pick the best instance for use at runtime.
First, we need a way to map IUserManager
implementations to roles.
public interface IUserManager
{
int Add(User user);
bool AppliesTo(string userRole);
}
public class UserManagerA : IUserManager
{
// Add method omitted
public bool AppliesTo(string userRole)
{
// Note that it is entirely possible to
// make this work with multiple roles and/or
// multiple conditions.
return (userRole == "RoleA");
}
}
public class UserManagerB : IUserManager
{
// Add method omitted
public bool AppliesTo(string userRole)
{
return (userRole == "RoleB");
}
}
Then we need a strategy class that simply picks the correct instance based on the userRole
. The IUserManager
instances are supplied by the DI container when the application is composed.
public interface IUserManagerStrategy
{
IUserManager GetManager(string userRole);
}
public class UserManagerStrategy
: IUserManagerStrategy
{
private readonly IUserManager[] userManagers;
public UserManagerStrategy(IUserManager[] userManagers)
{
if (userManagers == null)
throw new ArgumentNullException("userManagers");
this.userManagers = userManagers;
}
public IUserManager GetManager(string userRole)
{
var manager = this.userManagers.FirstOrDefault(x => x.AppliesTo(userRole));
if (manager == null && !string.IsNullOrEmpty(userRole))
{
// Note that you could optionally specify a default value
// here instead of throwing an exception.
throw new Exception(string.Format("User Manager for {0} not found", userRole));
}
return manager;
}
}
Usage
public class SomeService : ISomeService
{
private readonly IUserManagerStrategy userManagerStrategy;
public SomeService(IUserManagerStrategy userManagerStrategy)
{
if (userManagerStrategy == null)
throw new ArgumentNullException("userManagerStrategy");
this.userManagerStrategy = userManagerStrategy;
}
public void DoSomething()
{
string userRole = CurrentUser.Role;//this gets the user logged in
// Get the correct UserManger according to the role
IUserManager userManager = this.userManagerStrategy.GetManger(userRole);
// Do something with userManger
}
}
void RegisterServices(IKernel kernel)
{
kernel.Bind<IUserManager>().To<UserManagerA>();
kernel.Bind<IUserManager>().To<UserManagerB>();
// Ninject will automatically supply both IUserManager instances here
kernel.Bind<IUserManagerStrategy>().To<UserManagerStrategy>();
kernel.Bind<ISomeService>().To<SomeService>();
}
This method doesn't require you to inject the container into the application. There is no service location being used.
Note also that there is no switch case statement that would have to be modified every time you add a new UserManager
to the application. The logic of when to use a UserManager
is part of the UserManager
implementation and the order in which the logic is executed is determined by the DI configuration.
In addition, this will work regardless of which DI container you are using.
You could combine this with the CurrentUserProvider from RagtimeWilly's answer for a clean way to get the user role into the service where this is used.
Reference: Best way to use StructureMap to implement Strategy pattern