C# 装饰ASP.NET Web API IHttpController
我试图用decorator包装Web API控制器(C# 装饰ASP.NET Web API IHttpController,c#,dependency-injection,asp.net-web-api,decorator,C#,Dependency Injection,Asp.net Web Api,Decorator,我试图用decorator包装Web API控制器(IHttpControllerimplementations),但是当我这样做时,Web API抛出一个异常,因为不知为什么它期望实际的实现 将装饰器应用于控制器是我成功应用于MVC控制器的一个技巧,显然我也喜欢在WebAPI中这样做 我创建了一个自定义的IHttpControllerActivator,它允许解析IHttpController实现。下面是一个精简的实现: 公共类横切concernhttpControllerActivator:
IHttpController
implementations),但是当我这样做时,Web API抛出一个异常,因为不知为什么它期望实际的实现
将装饰器应用于控制器是我成功应用于MVC控制器的一个技巧,显然我也喜欢在WebAPI中这样做
我创建了一个自定义的IHttpControllerActivator
,它允许解析IHttpController
实现。下面是一个精简的实现:
公共类横切concernhttpControllerActivator:IHttpControllerActivator{
专用只读容器;
公共横切concernhttpControllerActivator(容器){
this.container=容器;
}
公共IHTTP控制器创建(HttpRequestMessage请求,
HttpControllerDescriptor控制器描述器,类型控制器类型)
{
var controller=(IHttpController)this.container.GetInstance(controllerType);
//将实例包装在一个或多个装饰器中
//容器应用了decorator,但这在这里并不重要。
返回新的MyHttpControllerDecorator(控制器);
}
}
我的装饰师看起来像这样:
公共类MyHttpControllerDecorator:IHttpController{
私有只读IHTTP控制器装饰者;
公共MyHttpControllerDecorator(IHTTpControllerDecoratee){
this.decoree=decoree;
}
公共任务执行同步(
HttpControllerContext controllerContext,
取消令牌(取消令牌)
{
//此装饰程序不添加任何逻辑。只需添加最少量的代码即可
//复制问题。
返回this.decoratee.ExecuteAsync(controllerContext,cancellationToken);
}
}
但是,当我运行应用程序并请求valuescoontroller
时,Web API向我抛出以下InvalidCastException
:
无法强制转换类型为“WebApiest.MyHttpControllerDecorator”的对象
键入“WebApiTest.Controllers.ValuesController”
堆栈跟踪:
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)
lambda_方法(闭包、对象、对象[])处的
在System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.c__DisplayClass13.b__c(对象实例,对象[]方法参数)
位于System.Web.Http.Controller.ReflectedHttpActionDescriptor.ActionExecutor.Execute(对象实例,对象[]参数)
在System.Web.Http.Controllers.ReflectedHttpActionDescriptor.c_uuDisplayClass5.b_uuu4()中
在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<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;
});
}
您可以通过实现
ihttpackioninvoker
并在IHttpController
抽象不再相关时将装饰器“转换”为装饰实例来解决此问题
通过从ApiControllerActionInvoker
继承,可以很容易地实现这一点
(我已经对示例进行了硬编码,希望任何实际实现都更加灵活。)
您是否真的想这样做是另一回事-谁知道更改
actionContext
的后果?您可以提供IHttpControllerSelector
的自定义实现来更改为特定控制器实例化的类型。(请注意,我还没有测试到筋疲力尽)
将装饰器更新为泛型
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);
}
}
注册控制器时,添加控制器类型的修饰版本的注册
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);
}
}
这是创建装饰实例的方法
private MyHttpControllerDecorator<T> DecoratorBuilder<T>(T instance)
where T : IHttpController
{
return new MyHttpControllerDecorator<T>(instance);
}
私有MyHttpControllerDecoratorDecoratorBuilder(T实例)
其中T:IHTTP控制器
{
返回新的MyHttpControllerDecorator(实例);
}
感谢您的回复。我仍然有点恼火,因为我不能装饰IHttpController
,但您的解决方案可能会满足我的需要:-)。这种方法的唯一问题是,委托处理程序是单例的,因此不能依赖任何生命周期较短的服务。有没有一种方法可以让Web API在每个请求上解析它们?因为我们使用的是外部服务(也可以是单例服务),但将值设置到上下文中(请求,httpcontext用户)。。。我们不需要每个“请求”的实例。我想说,IHttpController装饰器不能工作,因为最后,我们不能使用这个接口。该框架调查(反射)“真实”控制器,并根据其方法/参数/属性决定调用实例上的哪个方法。所以装饰图案在这里不起作用。无论如何委派处理程序应解决以下问题:委派处理程序可能(通过构造函数注入)依赖于可能(或需要)以每请求(或更短)的方式定义的其他服务。当这种情况发生时,DelegationHandler的生存期应该与其依赖项的最短生活方式一样短(或更短)。有趣的是,Web API与MVC非常相似,但使用MVCIController
s可以毫无问题地进行修饰,因为正是控制器
基类进行了所有的反射以获得正确的操作。基础结构只调用IController
接口。
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);
}
}
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);
}
}
GlobalConfiguration.Configuration.Services.Replace(
typeof(IHttpControllerSelector),
new CustomControllerSelector(GlobalConfiguration.Configuration));
private MyHttpControllerDecorator<T> DecoratorBuilder<T>(T instance)
where T : IHttpController
{
return new MyHttpControllerDecorator<T>(instance);
}