Декорирование ASP.NET Web API IHttpController

Я пытаюсь обернуть контроллеры Web API (реализации IHttpController) в декораторы, но когда я это делаю, Web API выдает исключение, потому что каким-то образом он ожидает фактической реализации.

Применение декораторов к контроллерам - это трюк, который я успешно применяю к контроллерам 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<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);
    }
}

Однако, когда я запускаю свое приложение и запрашиваю ValuesController, Web API выдает мне следующее InvalidCastException:

  

Невозможно привести объект типа 'WebApiTest.MyHttpControllerDecorator'   набрать '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)

Это похоже на то, как если бы Web API предоставил нам абстракцию IHttpController, но пропускает ее и все еще зависит от самой реализации. Это, конечно, будет серьезным нарушением принципа инверсии зависимости и сделает абстракцию совершенно бесполезной. Поэтому я, вероятно, делаю что-то не так.

Что я делаю не так? Как я могу украсить свои контроллеры API?

7 голосов | спросил Steven 7 SatEurope/Moscow2013-12-07T21:04:43+04:00Europe/Moscow12bEurope/MoscowSat, 07 Dec 2013 21:04:43 +0400 2013, 21:04:43

3 ответа


0

Я бы сказал, что естественный, продуманный способ достижения этого поведения в ASP.NET Web API - с помощью with-http /http-message-handlers" rel =" nofollow "> Пользовательские обработчики сообщений /обработчики делегирования

Например, у меня есть 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;
        });
    }

И это как вставить это в структуру:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.MessageHandlers.Add(new AuthenticationDelegationHandler());
ответил Radim Köhler 8 SunEurope/Moscow2013-12-08T14:29:30+04:00Europe/Moscow12bEurope/MoscowSun, 08 Dec 2013 14:29:30 +0400 2013, 14:29:30
0

Вы можете обойти это, реализовав IHttpActionInvoker и «преобразовав» декоратор в украшенный экземпляр в тот момент, когда IHttpController абстракция больше не актуальна.

Это легко сделать, унаследовав от ApiControllerActionInvoker.

(я жестко запрограммировал пример и ожидал, что любая реальная реализация будет более гибкой.)

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

Это было зарегистрировано в Global.asax.cs

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

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

Если вы действительно хотите сделать это, это другой вопрос - кто знает последствия изменения actionContext?

ответил qujck 10 TueEurope/Moscow2013-12-10T14:51:28+04:00Europe/Moscow12bEurope/MoscowTue, 10 Dec 2013 14:51:28 +0400 2013, 14:51:28
0

Вы можете предоставить пользовательскую реализацию 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);
    }
}

Определите пользовательскую реализацию 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<T> DecoratorBuilder<T>(T instance)
    where T : IHttpController
{
    return new MyHttpControllerDecorator<T>(instance);
}
ответил qujck 10 TueEurope/Moscow2013-12-10T19:30:31+04:00Europe/Moscow12bEurope/MoscowTue, 10 Dec 2013 19:30:31 +0400 2013, 19:30:31

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132