Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Autofac with Mvc controller constructor injection

I'm having trouble with Autofac constructor injection. I'm somewhat solved my problem but I'm turning to the community for a full solution.

This works with DI

builder.RegisterControllers(typeof(MvcApplication).Assembly)
       .InstancePerHttpRequest();

This does not.

builder.RegisterControllers(typeof(MvcApplication).Assembly)
       .AsImplementedInterfaces()
       .InstancePerHttpRequest();

Here's my controller.

public class HomeController : BaseController
{
    public HomeController(IPlugin plugin)
    {

    }
}

I have a module to resolve the IPlugin injection. I'm wondering why I need to take the .AsImplementedInterfaces() out to make this work. I would prefer to use interfaces because I'm using MEF to import IControllers from other assemblies at runtime.

Update

Thanks to japonex (see comments below) I've updated my solution. Here's what I did to get everything working.

First my project has its own controllers, and I then use MEF to import controllers from other assemblies.

Controllers for my current project must be registered without the .AsImplementedInterfaces() call, so like so

builder.RegisterControllers(typeof(MvcApplication).Assembly).InstancePerRequest();

This will put the controllers into Autofac using the type instead of as an interface (MyProject.Controllers.HomeController instead of System.Web.Mvc.IController).

Next in my plugin project I simply had to export the types instead of as an interface. So I use this

[Export]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class HomeController : PluginController

Instead of this

[Export(typeof(IController))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class HomeController : PluginController

Notice the difference in the Export attribute.

Next I set the current DependencyResolver to the AutofacDependencyResolver.

Last, but not least, I created a custom ControllerFactory to create the controllers. Technically this isn't needed, since I'm no longer using interfaces, but I have code to check the status of the controller before creating it. This allows me to easily enable/disable a plugin from an admin area.

So the root of the problem is that Autofac needed types instead of interfaces to resolve properly. I'm sure this could be done with interfaces, but this solution works for me. The only real reason I would have wanted to use interfaces is because I could then ask for all controllers from Autofac without knowing their type.

public class MyControllerFactory : DefaultControllerFactory
{
    private readonly IContainer _container;

    public PortalControllerFactory(IContainer container)
    {
        _container = container;
    }

    public override IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName)
    {
        Type controllerType = GetControllerType(requestContext, controllerName);

        if(controllerType == null)
        {
            throw new HttpException(404, ErrorMessages.Http404);
        }

        IPlugin plugin = PluginManager.Current.GetPlugin(controllerType);

        if (plugin != null && plugin.Status != Common.Enums.PluginStatus.Enabled)
        {
            //the controller/plugin is disabled so modify the route data and return the controller
            RouteData data = new RouteData();
            data.Values.Add("controller", "plugin");
            data.Values.Add("action", "disabled");
            data.Values.Add("plugin", plugin.Name);

            requestContext.RouteData = data;

            return ViewRenderer.CreateController<Project.Controllers.PluginController>(data);
        }

        var controller = ((AutofacDependencyResolver)DependencyResolver.Current).RequestLifetimeScope.Resolve(controllerType);
        return controller as IController;
    }
}
like image 294
Matt Avatar asked Nov 11 '22 04:11

Matt


1 Answers

When you use .AsImplementedInterfaces you're saying that for each concrete type, Autofac registers this type for each interface implementation.

So, you're registering all controllers in your Assembly to IController and propably because you don't have any rule (keyed or named registration) to decide which concrete type return, Autofac probably returns the last registration for IController.

Try call container.Resolve and see what you get.

Consider that if you have:

 public class HomeController : BaseController
{
    public HomeController(IPlugin plugin)
    {

    }
}

and

  public class Home2Controller : BaseController
{
    public Home2Controller(IPlugin plugin)
    {

    }
}

HomeController will be registered to IController and the Home2Controller also will be registered to IController.

And I think the default behavior of Autofac is to use the last registration if you don't use any kind of rule (keyed or named registration)

like image 70
japoneizo Avatar answered Nov 14 '22 23:11

japoneizo