C# 装饰ASP.NET Web API IHttpController

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:

我试图用decorator包装Web API控制器(
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非常相似,但使用MVC
IController
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);
}