Question

I'm trying to wrap Web API controllers (IHttpController implementations) with decorators, but when I do this, Web API throws an exception, because somehow it is expecting the actual implementation.

Applying decorators to controllers is a trick I successfully apply to MVC controllers and I obviously like to do the same in Web API.

I created a custom IHttpControllerActivator that allows resolving decorated IHttpController implementations. Here's a stripped implementation:

public class CrossCuttingConcernHttpControllerActivator : IHttpControllerActivator {
    private readonly Container container;
    public CrossCuttingConcernHttpControllerActivator(Container container) {
        this.container = container;
    }

    public IHttpController Create(HttpRequestMessage request, 
        HttpControllerDescriptor controllerDescriptor, Type controllerType)
    {
        var controller = (IHttpController)this.container.GetInstance(controllerType);

        // Wrap the instance in one or multiple decorators. Note that in reality, the 
        // decorator is applied by the container, but that doesn't really matter here.
        return new MyHttpControllerDecorator(controller);
    }
}

My decorator looks like this:

public class MyHttpControllerDecorator : IHttpController {
    private readonly IHttpController decoratee;
    public MyHttpControllerDecorator(IHttpController decoratee) {
        this.decoratee = decoratee;
    }

    public Task<HttpResponseMessage> ExecuteAsync(
        HttpControllerContext controllerContext,
        CancellationToken cancellationToken)
    {
        // this decorator does not add any logic. Just the minimal amount of code to
        // reproduce the issue.
        return this.decoratee.ExecuteAsync(controllerContext, cancellationToken);
    }
}

However, when I run my application and request the ValuesController, Web API throws me the following InvalidCastException:

Unable to cast object of type 'WebApiTest.MyHttpControllerDecorator' to type 'WebApiTest.Controllers.ValuesController'.

Stacktrace:

at lambda_method(Closure , Object , Object[] )
at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.<>c__DisplayClass13.<GetExecutor>b__c(Object instance, Object[] methodParameters)
at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.Execute(Object instance, Object[] arguments)
at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.<>c__DisplayClass5.<ExecuteAsync>b__4()
at System.Threading.Tasks.TaskHelpers.RunSynchronously[TResult](Func`1 func, CancellationToken cancellationToken)

It's just as if Web API supplies us with the IHttpController abstraction but skips it and still depends on the implementation itself. This would of course be a severe violation of the Dependency Inversion principle and make the abstraction utterly useless. So I'm probably doing something wrong instead.

What I'm I doing wrong? How can I happily decorate my API Controllers?

Was it helpful?

Solution

I would say, that the natural, designed way how to achieve this behaviour in ASP.NET Web API is with the Custom Message Handlers / Delegation Handlers

For example I do have this DelegationHandler in place

public class AuthenticationDelegationHandler : DelegatingHandler
{
    protected override System.Threading.Tasks.Task<HttpResponseMessage> 
        SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        // I. do some stuff to create Custom Principal
        // e.g.
        var principal = CreatePrincipal();
        ...

        // II. return execution to the framework            
        return base.SendAsync(request, cancellationToken).ContinueWith(t =>
        {
            HttpResponseMessage resp = t.Result;
            // III. do some stuff once finished
            // e.g.:
            // SetHeaders(resp, principal);

            return resp;
        });
    }

And this is how to inject that into the structure:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.MessageHandlers.Add(new AuthenticationDelegationHandler());

OTHER TIPS

You can work around this by implementing IHttpActionInvoker and "converting" the decorator into the decorated instance at the point that the IHttpController abstraction is no longer relevant.

This is easily done by inheriting from ApiControllerActionInvoker.

(I've hard coded the example and would expect any real world implementation to be more flexible.)

public class ContainerActionInvoker : ApiControllerActionInvoker
{
    private readonly Container container;

    public ContainerActionInvoker(Container container)
    {
        this.container = container;
    }

    public override Task<HttpResponseMessage> InvokeActionAsync(
        HttpActionContext actionContext, 
        CancellationToken cancellationToken)
    {
        if (actionContext.ControllerContext.Controller is MyHttpControllerDecorator)
        {
            MyHttpControllerDecorator decorator =
                (MyHttpControllerDecorator)actionContext.ControllerContext.Controller;
            // decoratee changed to public for the example
            actionContext.ControllerContext.Controller = decorator.decoratee;
        }

        var result = base.InvokeActionAsync(actionContext, cancellationToken);
        return result;
    }
}

This was registered in Global.asax.cs

GlobalConfiguration.Configuration.Services.Replace(
    typeof(IHttpControllerActivator),
    new CrossCuttingConcernHttpControllerActivator(container));

GlobalConfiguration.Configuration.Services.Replace(
    typeof(IHttpActionInvoker),
    new ContainerActionInvoker(container)); 

Whether you'd actually want to do this is another matter - who knows the ramifications of altering actionContext?

You can provide a custom implementation of IHttpControllerSelector to alter the type instantiated for a particular controller. (Please note I have not tested this to exhaustion)

Update the decorator to be generic

public class MyHttpControllerDecorator<T> : MyHttpController
    where T : MyHttpController
{
    public readonly T decoratee;

    public MyHttpControllerDecorator(T decoratee)
    {
        this.decoratee = decoratee;
    }

    public Task<HttpResponseMessage> ExecuteAsync(
        HttpControllerContext controllerContext,
        CancellationToken cancellationToken)
    {
        return this.decoratee.ExecuteAsync(controllerContext, cancellationToken);
    }

    [ActionName("Default")]
    public DtoModel Get(int id)
    {
        return this.decoratee.Get(id);
    }
}

Define the custom implementation of IHttpControllerSelector

public class CustomControllerSelector : DefaultHttpControllerSelector
{
    private readonly HttpConfiguration configuration;
    public CustomControllerSelector(HttpConfiguration configuration)
        : base(configuration)
    {
        this.configuration = configuration;
    }

    public override HttpControllerDescriptor SelectController(
        HttpRequestMessage request)
    {
        var controllerTypes = this.configuration.Services
            .GetHttpControllerTypeResolver().GetControllerTypes(
                this.configuration.Services.GetAssembliesResolver());

        var matchedTypes = controllerTypes.Where(i => 
             typeof(IHttpController).IsAssignableFrom(i)).ToList();

        var controllerName = base.GetControllerName(request);
        var matchedController = matchedTypes.FirstOrDefault(i => 
                i.Name.ToLower() == controllerName.ToLower() + "controller");

        if (matchedController.Namespace == "WebApiTest.Controllers")
        {
            Type decoratorType = typeof(MyHttpControllerDecorator<>);
            Type decoratedType = decoratorType.MakeGenericType(matchedController);
            return new HttpControllerDescriptor(this.configuration, controllerName, decoratedType);
        }
        else
        {
            return new HttpControllerDescriptor(this.configuration, controllerName, matchedController);
        }
    }
}

When registering the controllers, add in the registration of a decorated version of the controller type

var container = new SimpleInjector.Container();

var services = GlobalConfiguration.Configuration.Services;

var controllerTypes = services.GetHttpControllerTypeResolver()
    .GetControllerTypes(services.GetAssembliesResolver());

Type decoratorType = typeof(MyHttpControllerDecorator<>);
foreach (var controllerType in controllerTypes)
{
    if (controllerType.Namespace == "WebApiTest.Controllers")
    {
        Type decoratedType = decoratorType.MakeGenericType(controllerType);
        container.Register(decoratedType, () => 
            DecoratorBuilder(container.GetInstance(controllerType) as dynamic));
    }
    else
    {
        container.Register(controllerType);
    }
}

Register the implementation of IHttpControllerSelector

GlobalConfiguration.Configuration.Services.Replace(
    typeof(IHttpControllerSelector),
    new CustomControllerSelector(GlobalConfiguration.Configuration));

This is the method for creating the Decorated instance

private MyHttpControllerDecorator<T> DecoratorBuilder<T>(T instance)
    where T : IHttpController
{
    return new MyHttpControllerDecorator<T>(instance);
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top