Question

I have a problem with the Autofac ExtensibleActionInvoker interacting with the MVC ModelBinder when using interfaces for parameters. The background is as follows:

I am building a MVC application and I am using Autofac MVC3's ExtensibleActionInvoker to inject my services as parameters to my actions, e.g.

    public ActionResult Test( IMyService service)
    {
        //A new instance of service is created by Autofac ExtensibleActionInvoker 
        return View();
    }

This works really well and makes for a really clean design (see Alex Meyer-Gleaves post for more information on this approach). I want to use this method as I am producing a code generator to create actions, views, services and DTOs and a per-action service approach makes this easier.

However I also want to use interfaces for the parameters in action classed which receive input from an HttpPost action. This is because I use DI to create classes outside each layer. If I change the DefaultModelBinder to use DI to create the class (see page 595 of Steve Sanderson's book on MVC3 on how to do this) this this works fine, e.g.

    [HttpPost]
    public ActionResult Test(ITestClass dataComingFromView)
    {
        //model binder creates the class via DI and then binds it to the data from the post
        return View();
    }

However in the above simple example above I get a conflict with the ExtensibleActionInvoker enabled, i.e.

  1. Without ExtensibleActionInvoker enabled the method above works fine, i.e. the extended DefaultModelBinder uses DI to create the TestClass class and modelbinder binds input from the view to the fields in the class.
  2. With ExtensibleActionInvoker enabled it does not work, i.e. I get an empty TestClass class with no binding. I assume the ExtensibleActionInvoker takes precedence over the model binder and just creates an empty TestClass class.
  3. (Just for completeness I should say that if I just use MVC "out of the box", i.e. no new DefaultModelBinder and no ExtensibleActionInvoker enabled, then it says you cannot use an interface as an Action method parameter.)

My question for anyone with better Autofac knowledge than me is: can I change the Autofac ExtensibleActionInvoker to select what it binds to? All my injected service classed start with IService so I could filter on that. I know you can do that in Autofac elsewhere but couldn't see anything to do that with ExtensibleActionInvoker, but maybe I missed it.

Any help would be appreciated.

Jon Smith - Selective Analytics

Was it helpful?

Solution 2

Having now worked on this problem I found a simple answer. My problem was due to me not really understanding how the MVC Model Binding worked.

If you look at my orginal problem I had created a DefaultModelBinder to allow me to use interfaces as my model parameters (see original question at the top). This was added after me including the Autofac's ExtensibleActionInvoker to bind my IService types. The problem was that the two DI approaches clashed.

The answer was that the DefaultModelBinder was sufficient to bind both my data classes and the Service definitions, so I do not need Autofac's ExtensibleActionInvoker. For completeness I have included the DefaultModelBinder code in case it is useful to anyone else.

public class DiModelBinder : DefaultModelBinder
{
    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
    {
        return modelType.IsInterface
                   ? DependencyResolver.Current.GetService(modelType)
                   : base.CreateModel(controllerContext, bindingContext, modelType);
    }
}

Note that I only call the DependencyResolver if the modeltype is an interface as I don't pass abstract classes between layers. Any alternative is to always call the DependencyResolver and then call the base.CreateModel if the DI does not resolve the type. I didn't do this because calling the DependencyResolver is slightly expensive so I only call it when I know I need it.

OTHER TIPS

You are correct that the problem is caused by the ExtensibleActionInvoker class. If you look at the source for it, there is a method called GetParameterValue(). See below:

    protected override object GetParameterValue(ControllerContext controllerContext, ParameterDescriptor parameterDescriptor)
    {
        if (_injectActionMethodParameters)
            return _context.ResolveOptional(parameterDescriptor.ParameterType) ?? base.GetParameterValue(controllerContext, parameterDescriptor);

        return base.GetParameterValue(controllerContext, parameterDescriptor);
    } 

This method overrides the method that eventually uses the MVC framework's model binder infrastructure. What this means, is that the ActionInvoker tries to resolve the parameter using AutoFac first, and if it fails, falls back to the default functionality. Based on the results you are getting, it seems that your AutoFac configuration must be setup to provide a default resolution of ITestClass.

In order to register a custom ModelBinder with AutoFac you have a couple options. You can decorate the view model with a ModelBinderTypeAttribute or you can do it in your configuration with the custom extension methods found in RegistrationExtensions.

One article I found looks like it provides an easy solution to a similar issue (see the end), but I have not tested this personally.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top