ASP.NET IHttpController Web API Design

I try to combine Web API controllers ( IHttpController ) with decorators, but when I do this, the Web API throws an exception, because somehow it expects the actual implementation.

Applying decorators to controllers is a trick that I have successfully applied to MVC controllers, and I obviously would like to do the same in the web API.

I created a custom IHttpControllerActivator that allows you to allow decorated implementations of IHttpController . Here's the implementation lost:

 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 a ValuesController , the web API throws me the following InvalidCastException :

Unable to create object of type 'WebApiTest.MyHttpControllerDecorator' for input '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) 

Just as if the Web API provided us with the IHttpController abstraction, but skipped it and still depends on the implementation itself. This, of course, will be a serious violation of the principle of dependency inversion and will make abstraction completely useless. So I'm probably doing something wrong.

What I do? How can I happily decorate my API controllers?

+6
source share
3 answers

I would say that a natural, developed way to achieve this behavior in the ASP.NET Web API, with custom message handlers / delegation handlers

For example, I 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 // eg 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 // eg: // SetHeaders(resp, principal); return resp; }); } 

And here's how to insert this into the structure:

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

You can get around this by implementing IHttpActionInvoker and "converting" the decorator to a decorated instance at the point where the IHttpController abstraction IHttpController no longer relevant.

This is easy to do, inheriting from ApiControllerActionInvoker .

(I hard-coded the example and would expect the implementation of the real world 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; } } 

It has been registered with Global.asax.cs

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

If you really want to do this, this is another matter - who knows the consequences of changing actionContext ?

+1
source

You can provide a custom implementation of IHttpControllerSelector to change the type created for a particular controller. (Please note that I did not check this for wear)

Update the decorator to be shared

 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 a 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 controllers, add registration of the issued 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 a method of creating a Decorated instance.

 private MyHttpControllerDecorator<T> DecoratorBuilder<T>(T instance) where T : IHttpController { return new MyHttpControllerDecorator<T>(instance); } 
+1
source

All Articles