4

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.

NightOwl888
  • 55,572
  • 24
  • 139
  • 212
SOfanatic
  • 5,523
  • 5
  • 36
  • 57
  • `.When(r => CurrentUser.Role == "RoleA");` ? - assuming `When` is computed on every resolution you don't want to capture value during setup time... – Alexei Levenkov Mar 12 '15 at 18:57
  • @AlexeiLevenkov I'm not sure what you mean? the `CurrentUser` gets computed from the user that is logged in so that value would change on every request, isn't `Ninject`s binding happening every time a controller is created? – SOfanatic Mar 12 '15 at 19:07
  • I don't know how Ninject works and my suggestion is based on how Unity would behave. I assume that Ninject would execute `.When` every time type needs to be resolved and default resolution frequency (aka "lifetime" in Unity) is every time one asks for the type... This way when controller created (at least once every http request) it will get `IUserMamager` matching `CurrentUser.Role`.... (Could be completely off as I have no direct knowledge of Ninject) – Alexei Levenkov Mar 12 '15 at 19:22
  • @AlexeiLevenkov ok thanks I'll try doing it with ninject first if it doesn't work I'll try unity. – SOfanatic Mar 12 '15 at 19:35

2 Answers2

5

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);
}
RagtimeWilly
  • 5,265
  • 3
  • 25
  • 41
  • 1
    +1. This is the way I do it. If you don't want to have the kernel reference outside of the composition root, your UserManagerProvider implementation could be part of that project (with the interface more widely available. Also, consider registering and resolving the user managers by name instead of having the if statements in the provider implementation. – Phil Sandler Mar 12 '15 at 23:17
  • @PhilSandler I agree, the `if` statement is nasty. Just wanted to keep implementation in line with question asked – RagtimeWilly Mar 12 '15 at 23:32
  • @PhilSandler what do you mean by register and resolving by name? – SOfanatic Mar 12 '15 at 23:46
  • @RagtimeWilly I'm not really using a static method for the CurrentUser that was just for demonstration purposes. I was trying to avoid passing the role to every `Manager` call since once the user is logged in I already know which managers should be use. I'm going to wait a bit to see if someone else shows up with an implementation that doesn't require the role to be included in every call to the manager, if not I'll have to go with this route that you are suggesting. Thanks. – SOfanatic Mar 12 '15 at 23:49
  • 1
    Binding and Resolving by name: https://github.com/ninject/ninject/wiki/Contextual-Binding#simple-constrained-resolution-named-bindings – Phil Sandler Mar 13 '15 at 00:11
  • As noted in the wiki, you should do this judiciously as it smacks a bit of service location. That said, it's the "least wrong" way to resolve to a specific implementation based on runtime context (IMO). – Phil Sandler Mar 13 '15 at 00:13
  • @PhilSandler I updated answer to include a note about resolving by name. Thanks. – RagtimeWilly Mar 13 '15 at 00:15
2

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

Community
  • 1
  • 1
NightOwl888
  • 55,572
  • 24
  • 139
  • 212