views:

55

answers:

3
public class TheController : Controller
{
   IThe the;
   public TheController( IThe the)
   {
      //when User.IsInRole("r1") The1 should be injected else r2
      this.the = the;
   }
}

public class The1 : IThe{}
public class The2 : IThe{}

//anybody knows a good way of doing this ?
+2  A: 

IHandlerSelector is the way to go. See this post for an example of usage.

Alternatively if you prefer AutoFac-like experience you can use factory for that:

container.Register(Component.For<IThe>().UsingFactoryMethod(
  c => HttpContext.Current.User.IsInRole("r1") ?
    c.Resolve<IThe>("r1") :
    c.Resolve<IThe>("r2"));

Or if you want to use specific IThe just in one context, you can use DynamicParameters:

container.Register(Component.For<TheFactory>().Lifestyle.Transient.DynamicParameters(
  (c, d) => HttpContext.Current.User.IsInRole("r1") ?
    d["the"] = c.Resolve<IThe>("r1") :
    c.Resolve<IThe>("r2"));

However the most correct way of doing this is IHandlerSelector

Krzysztof Koźmic
+1  A: 

In Autofac:

var builder = new ContainerBuilder();

// Give the different implementations names
builder.RegisterType<The1>.Named<IThe>("r1");
builder.RegisterType<The2>.Named<IThe>("r2");

// Use a function for the default IThe
builder.Register(
  c => HttpContext.Current.User.IsInRole("r1") ?
    c.Resolve<IThe>("r1") :
    c.Resolve<IThe>("r2"))
  .As<IThe>()
  .ExternallyOwned();

If you have a lot of roles, you can use a method instead of the inline expression, e.g.:

builder.Register(c => ChooseTheImplementation(c))

(BTW, The "ExternallyOwned" modifier tells the container that the result of the function is disposed elsewhere, e.g. via the concrete components. You can usually leave it out but it makes good documentation :))

Nicholas Blumhardt
As far as I understand this answer, you would perform this registration in the application root. At that point, `User` isn't available (it's a property on the Controller class).
Mark Seemann
Fixed - now uses HttpContext. Thanks Mark.
Nicholas Blumhardt
is Windsor Castle capable of doing this kind of stuff ?
Omu
Yes- see Krzysztof's answer above. Windsor tends to favour interfaces over lambdas for it's extensibility points, but the net result is the same.
Nicholas Blumhardt
+1  A: 

The container-agnostic approach obviously employs an Abstract Factory:

public interface ITheFactory
{
    IThe Create(IPrincipal user);
}

You can take a dependency on ITheFactory instead of IThe:

public class TheController : Controller   
{   
    private readonly IThe the;   

    public TheController(ITheFactory theFactory)   
    {   
        if (theFactory == null)
        {
            throw new ArgumentNullException("theFactory");
        }

        this.the = theFactory.Create(this.User);
    }   
}   

I can't really remember if this.User is populated at this time, but if it isn't, you can just keep a reference to the factory and lazily resolve your dependency the first time it's requested.

However, Controller.User is a bit special because it ought to be available as Thread.CurrentPrincipal as well. This means that in this special case you don't actually have to introduce an Abstract Factory. Instead, you can write a Decorator that performs the selection every time it's used:

public class UserSelectingThe : IThe
{
    private readonly IThe the1;
    private readonly IThe the2;

    public UserSelectingThe(IThe the1, IThe the2)
    {
        if (the1 == null)
        {
            throw new ArgumentNullException("the1");
        }
        if (the2 == null)
        {
            throw new ArgumentNullException("the2");
        }

        this.the1 = the1;
        this.the2 = the2;
    }

    // Assuming IThe defines the Foo method:
    public Baz Foo(string bar)
    {
        if (Thread.CurrentPrincipal.IsInRole("r1"))
        {
            return this.the1.Foo(bar);
        }

        return this.the2.Foo(bar);
    }
}

In this case, you would be able to use your original TheController class unchanged.

Mark Seemann