装饰ASP.NET Web API IHttpController

我正在尝试使用装饰器来包装Web API控制器( IHttpController实现),但是当我这样做时,Web API会抛出exception,因为不知何故它会期待实际的实现。

将装饰器应用于控制器是我成功应用于MVC控制器的一个技巧,我显然希望在Web API中也这样做。

我创建了一个自定义的IHttpControllerActivator ,它允许解析修饰的IHttpController实现。 这是一个剥离的实现:

 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); } } 

我的装饰师看起来像这样:

 public class MyHttpControllerDecorator : IHttpController { private readonly IHttpController decoratee; public MyHttpControllerDecorator(IHttpController decoratee) { this.decoratee = decoratee; } public Task 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); } } 

但是,当我运行我的应用程序并请求ValuesController ,Web API会抛出以下InvalidCastException

无法将类型为“WebApiTest.MyHttpControllerDecorator”的对象强制转换为“WebApiTest.Controllers.ValuesController”。

堆栈跟踪:

 at lambda_method(Closure , Object , Object[] ) at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.c__DisplayClass13.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.b__4() at System.Threading.Tasks.TaskHelpers.RunSynchronously[TResult](Func`1 func, CancellationToken cancellationToken) 

这就好像Web API为我们提供了IHttpController抽象,但跳过了它仍然依赖于实现本身。 这当然会严重违反依赖性倒置原则,并使抽象完全无用。 所以我可能会做错事。

我做错了什么? 我怎样才能愉快地装饰我的API控制器?

我会说,如何在ASP.NET Web API中实现此行为的自然设计方式是使用自定义消息处理程序/委派处理程序

例如,我确实有这个DelegationHandler

 public class AuthenticationDelegationHandler : DelegatingHandler { protected override System.Threading.Tasks.Task 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; }); } 

这就是如何将其注入结构:

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

您可以通过实现IHttpActionInvoker并将装饰器“转换”为IHttpController抽象不再相关的点来装饰实例来解决这个问题。

这可以通过inheritanceApiControllerActionInvoker轻松完成。

(我已对该示例进行了硬编码,并期望任何实际的实现都更加灵活。)

 public class ContainerActionInvoker : ApiControllerActionInvoker { private readonly Container container; public ContainerActionInvoker(Container container) { this.container = container; } public override Task 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; } } 

这是在Global.asax.cs中注册的

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

你是否真的要这样做是另一回事 – 谁知道改变actionContext的后果?

您可以提供IHttpControllerSelector的自定义实现来更改为特定控制器实例化的类型。 (请注意我没有测试这个用尽)

将装饰器更新为通用装置

 public class MyHttpControllerDecorator : MyHttpController where T : MyHttpController { public readonly T decoratee; public MyHttpControllerDecorator(T decoratee) { this.decoratee = decoratee; } public Task ExecuteAsync( HttpControllerContext controllerContext, CancellationToken cancellationToken) { return this.decoratee.ExecuteAsync(controllerContext, cancellationToken); } [ActionName("Default")] public DtoModel Get(int id) { return this.decoratee.Get(id); } } 

定义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); } } } 

注册控制器时,请添加控制器类型的装饰版本的注册

 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); } } 

注册IHttpControllerSelector的实现

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

这是创建装饰实例的方法

 private MyHttpControllerDecorator DecoratorBuilder(T instance) where T : IHttpController { return new MyHttpControllerDecorator(instance); }