How to decorate an ASP.NET MVC controller with Sim

2020-03-15 04:52发布

I'd like to apply some cross-cutting concerns to my MVC controllers. At the moment, this is implemented through an abstract base class, but as we are refactoring more of the code base to take advantage of dependency injection, I'm wondering if this is something Simple Injector can help me with through its decoration or interception facilities.

So I've attempted to create a pretty basic decorator:

public class ControllerDecorator : IController
{
    private readonly IController _controller;

    public ControllerDecorator(IController controller)
    {
        _controller = controller;
    }

    public void Execute(RequestContext requestContext)
    {
        // Do something of a cross-cutting nature here...

        _controller.Execute(requestContext);
    }
}

And in my composition root: container.RegisterDecorator<IController, ControllerDecorator>()

However, the code in my decorator's Execute method doesn't seem to ever get called. Is it because the MVC framework directly resolves my controller classes instead of going through IController? In that case, what can I do? What am I missing here?

1条回答
家丑人穷心不美
2楼-- · 2020-03-15 05:48

In the default configuration, you can't apply decorators to controllers. The reason for this is that MVC's DefaultControllerFactory requests controllers by their concrete type. Because it requests a concrete type, Simple Injector is unable to apply a decorator; it has to assume that the caller needs this concrete type and has to therefore return this exact type (or a sub type).

To fix this, you will have to replace the default DefaultControllerFactory with a custom one:

public class SimpleInjectorControllerFactory : DefaultControllerFactory {
    public IDictionary<Type, InstanceProducer> Producers { get; set; }
    protected override IController GetControllerInstance(RequestContext rc, Type type) {
        return (IController)this.Producers[type].GetInstance();
    }
}

Next, in your bootstrapper, you'll have to replace the call to RegisterMvcControllers with the following:

var controllerTypes = SimpleInjectorMvcExtensions.GetControllerTypesToRegister(
    container, Assembly.GetExecutingAssembly());

var controllerProducers = controllerTypes
    .ToDictionary(type => type, type => CreateControllerProducer(container, type));

// Verify after creating the controller producers.
container.Verify();

ControllerBuilder.Current.SetControllerFactory(
    new SimpleInjectorControllerFactory { Producers = controllerProducers });

The CreateControllerProducer method looks as follows:

private static InstanceProducer CreateControllerProducer(Container c, Type type) {
    var producer = Lifestyle.Transient.CreateProducer(typeof(IController), type, c);
    producer.Registration.SuppressDiagnosticWarning(
        DiagnosticType.DisposableTransientComponent,
        "MVC disposes the controller when the web request ends.");
    return producer;
}

The crucial part is that the call to CreateProducer is supplied with typeof(IController); this allows Simple Injector to apply a decorator for IController.

This is it; now you can register your decorator for IController.

One warning though: with both Web API and the new ASP.NET core it is impossible to apply decorators to controllers. Both frameworks expect concrete types; they will break if you wrap the real controller. The preferred way with those frameworks to decorate controllers is through the OWIN pipeline. So this answer solely works with MVC 3, 4 and 5.

查看更多
登录 后发表回答