I have the following class and interface structure and I'm having a hard time trying to get the code to do what I need.
public interface IUserManager
{
int Add(User user);
}
public class UserManagerA : IUserManager{}
public class UserManagerB : IUserManager{}
In this example I'm using Ninject as the IoC container but I'm open to changing it if some other container resolves the issue:
This is inside my NinjectWebCommon.cs
:
void RegisterServices(IKernel kernel)
{
string userRole = CurrentUser.Role;//this gets the user logged in
//This is the part I do not how to do
//I wish I could just type this in:
kernel.Bind<IUserManager>().To<UserManagerA>()
.When(userRole == "RoleA"); // this doesn't work obviously
kernel.Bind<IUserManager>().To<UserManagerB>()
.When(userRole == "RoleB"); // same doesn't work
}
All of that so that in my (MVC) controller I can do this:
public class UserController
{
private readonly IUserManager _userManager;
public UserController(IUserManager userManager)
{
_userManager = userManager;
}
public ActionResult Add(User user)
{
//this would call the correct manager
//based on the userRole
_userManager.Add(user);
}
}
I've been reading articles about Abstract Factory but haven't found one that explains how to integrate the factory with the IoC container and pass a parameter obtained at run-time to resolve the implementations.
Create a class responsible for providing the correct UserManager
and inject this to your controller:
public class UserManagerProvider : IUserManagerProvider
{
private readonly IContext _context;
public UserManagerProvider(IContext context)
{
_context = context;
}
public IUserManager Create(User currentUser)
{
if (currentUser.Role == "User A")
return _context.Kernel.Get<UserManagerA>();
if (currentUser.Role == "User B")
return _context.Kernel.Get<UserManagerB>();
// Or bind and resolve by name
// _context.Kernel.Get<IUserManager>(currentUser.Role);
}
}
And in controller:
private readonly IUserManager _userManager;
public UserController(IUserManagerProvider userManagerProvider)
{
_userManager = userManagerProvider.Create(CurrentUser);
}
Also, as a side note you should probably have a CurrentUserProvider
responsible for getting the current user. Relying on a static method will make things difficult to unit test and you're essentially hiding a dependency in all classes that reference it:
private readonly IUserManager _userManager;
private readonly User _currentUser;
public UserController(IUserManagerProvider userManagerProvider, ICurrentUserProvider currentUserProvider)
{
_currentUser = currentUserProvider.GetUser();
_userManager = userManagerProvider.Create(_currentUser);
}
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With