How to inject different concrete implementation into controller depending on current request action method attributes?

I currently have a custom PrototypingControllerFactory that looks for a custom [Prototype] attribute on the action method being invoked for the current request, and depending on whether the attribute is present or not will inject a different implementation of an interface ISomeService. (In this case ISomeService more or less abstracts a messaging service, so the mock implementation allows returning "canned" results when the real implementation is not yet ready to handle a particular message).

So for example, if I have a controller class like so:

public class MyController : Controller
{
    private readonly ISomeService _someService;

    public MyController(ISomeService someService /*, .... other dependencies */
    { _someService = someService; //... etc }


    public ActionResult Action1()
    {
        //...
        _someService.SomeMethod();
        //...
    }

    [Prototype]
    public ActionResult Action2()
    {
        //...
        _someService.SomeMethod();
        //...
    }
}

Then when Action1 is invoked from an http request, _someService should use the production implementation of ISomeService, but when Action2 is invoked, _someService should have a Mocked version of ISomeService.

From a strict design standpoint, I realize this might point to having too many actions in a particular controller (otherwise, for example, I could just mark an entire controller as having [Prototype]) , but due to project inertia, I would rather not try to force a change in how actions are placed in controllers.

Currently the autofac registration has the following:

builder.RegisterControllers(Assembly.GetExecutingAssembly());

if (ConfigurationManager.AppSettings["AllowPrototyping"] == "true")
{
    builder.RegisterType<PrototypingControllerFactory>().As<IControllerFactory>().InstancePerRequest();
}

However, this means that the controller factory has to do some fancy work to figure out constructor arguments, and get instances from the DI container. Recently, I have discovered some subtle differences in behavior between the custom controller factory, and the "real" controller factory that are not desirable.

I would like to eliminate the custom controller factory, and instead have autofac fully handle resolution of the controllers.

How can I tell autofac to resolve a different implementation of an interface depending on whether the currently executing action is decorated with my custom [Prototype] attribute?

Answers


The first option depends on reliably being able to determine the controller action method directly from the route data, which is not necessarily straightforward. Part of the issue is that creation of the controller (and hence injection of the dependencies) occurs fairly early in the process, even before authorization filters run.

If some reliable implementation of a magical method say ActionDescriptor GetActionDescriptor(RoutData routeData) actually existed, then I could do something like the following:

builder.Register<ISomeService>(c =>
{
    var httpRequest = c.Resolve<HttpRequestBase>();
    var actionDescriptor = GetActionDescriptor(httpRequest.RequestContext.RouteData);

    if (actionDescriptor.GetAttributes<PrototypeAttribute>().Any())
    {
        return new PrototypeSomeService();
    }
    return new RealSomeService();
}).InstancePerRequest();

However the closest I could come to getting an ActionDescriptor at the point where the controller is intantiated was overriding DefaultControllerFactory, which is precisely what I am trying to get away from.

From default controller factory you can use the protected DefaultControllerFactory.GetControllerType() from which you could then create a ReflectedControllerDescriptor. But then you still have to do some munging to get the correct action descriptor from controllerDescriptor.GetCanonicalActions(). (In fact, I suspect this "munging" is leading to the subtle differences in the original custom controller factory). This could get even more complicated when routes are used from other http handlers (think Elmah or MiniProfiler for example).


In the end I opted to avoid attempting to map RouteData to an ActionDescriptor by making [PrototypeAttribute] inherit from ActionFilterAttribute so that I could easily hook into OnActionExecuting, from which point I added an identifier to the current HttpContext, e.g.

public class PrototypeAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        filterContext.RequestContext.HttpContext.Items.Add("Prototype", "Prototype");

        base.OnActionExecuting(filterContext);
    }
}

Then I modified my PrototypeSomeService so that it wrapped an implementation of ISomeService and delegated to it if the context did not contain the prototype key, e.g.

public class PrototypeSomeService : ISomeService
{
     private readonly ISomeService _wrappedService;
     private readonly HttpRequestBase _httpRequest;

     public PrototypeSomeService(ISomeService wrappedService, HttpRequestBase httpRequest)
     {
         _wrappedService = wrappedService;
         _httpRequest = httpRequest
     }

     public object SomeMethod()
     {
         if(_httpRequest.RequestContext.HttpContext.Items.Contains("Prototype"))
             return _wrappedService.SomeMethod();

         //other prototype logic...
         return prototypeResult;
     }
}

The final piece to tie it all together is to use autofac's decorator capabilities:

 var someServiceRegistration = builder.RegisterType<SomeService>().InstancePerRequest();

 if (ConfigurationManager.AppSettings["AllowPrototyping"] == "true")
 {
     someServiceRegistration.Named<ISomeService>("Prototype");

     builder.RegisterDecorator<ISomeService>(
          (c, inner) => new PrototypeSomeService(inner, c.Resolve<HttpRequestBase>()),
          fromKey: "Prototype"
     );
 }
 else
 {
     someServiceRegistration.As<ISomeService>();
 }

One small downside is that you have to make sure you use a unique key for the httpcontext item or else strange things will happen, but that is pretty easily avoided by e.g. using a guid.

This approach allowed me to leave most of the existing code unchanged, only modifying the prototype implementation of the service and the autofac registrations.

You can read more about autofac decorators at:


Need Your Help

How to get the number of days that are the current year from a date range?

php mysql sql date

In my database, I have the attributes "startingdate", "endingdate", "daterange", "numdays" and a few others that are not of any importance for my question. I am trying to build a leave application ...

How to get the smooth scrolling on tableView? (using swift)

swift tableview smooth

I am working with tableViews now. Everything works perfect except the smooth scrolling of tableview. Here is a code: