повторно выполнить конвейер MVC с другим маршрутом из фильтра ресурсов. - PullRequest
0 голосов
/ 17 мая 2019

TL; DR

Как мне повторно вызвать механизм маршрутизации MVC и MvcHandler из IAsyncResourceFilter, используя другой маршрут? Я в основном хочу вызвать конвейер дважды (параллельно?).

фон

Я создаю HAL реализацию для ASP.Net Core API. Для реализации HAL возвращаемый ресурс должен иметь возможность встраивать другие ресурсы, чтобы уменьшить общение между клиентом и сервером. Ответ будет выглядеть так:

{
   "_links": {
      "self": {
         "href": "/myResource/1"
      }
   },
   "name": "myResource",
   "foo": "bar",
   "_embedded": {
      "relatedResource": {
         "_links": {
            "self": {
               "href": "/relatedResources/5"
            }
         },
         "name": "relatedResource",
         "baz": "foo"
      }
   }
}

Я хочу отделить вложение ресурсов от контроллеров, поэтому я решил, что просто

  1. вызовет конвейер MVC для исходного запроса.
  2. вызовите конвейер MVC для ресурсов для встраивания.
  3. вставляет ресурсы от 2 до ответа 1.

Если это сработает, это позволит включить кэширование ресурсов, а также авторизацию, а что нет.

Я хочу использовать ResourceFilter, потому что мне нужно получить доступ к контроллеру и действиям, на которые будет направлен запрос, чтобы узнать, какие ресурсы должны быть встроены. Поэтому мне нужен доступ к ControllerActionDescriptor.

public class HalEmbed : IAsyncResourceFilter
{
    public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
    {
        // Implementation not useful for question
        var actionToEmbed = GetActionToEmbed();

        await next();

        // TODO: How do I do this?
        var embedResult = await ReExecutePipelineHere(actionToEmbed.Url);

        // Will be magic if I know how to do the above.
        Embed(embedResult, context.Result);
    }
}

Эта диаграмма, которую я нашел в документах , подразумевает, что должна быть возможность повторно выполнить конвейер из фильтра ресурсов:

MVC filter Pipeline

Так как мне повторно выполнить конвейер MVC с другим URL-адресом, а также иметь возможность выполнять конвейер для текущего URL-адреса?

Ответы [ 2 ]

2 голосов
/ 17 мая 2019

Изображение, на которое вы смотрите, на самом деле не показывает, что они могут повторно выполнить конвейер. Вместо этого изображение пытается сказать, что фильтр ресурсов может закорачивать конвейер (например, возвращая кэшированный результат), и что он будет выполнен после повторной фильтрации результатов (например, для кэширования результатов для более поздних запросов).

Повторное выполнение самого конвейера фильтра MVC невозможно. Это происходит главным образом потому, что нет способа перенастроить контекст выполнения для отдельного выполнения.

Однако, что вы можете сделать, это повторно запустить конвейер промежуточного программного обеспечения. ExceptionHandlerMiddleware делает это, например, для отображения результата конечной точки обработки исключительной ситуации, когда она перехватывает исключение. Это достигается путем изменения параметров запроса (например, HttpContext.Request.Path) и повторного вызова промежуточного программного обеспечения (т.е. next()).

Поскольку промежуточное программное обеспечение действует непосредственно на объекты HttpRequest и HttpResponse, это, конечно, означает, что логика должна быть намного более низкого уровня. Например, вы не можете просто взять ObjectResult и скорректировать этот результат, чтобы включить другое значение. Вместо этого вам придется работать над уже сериализованным результатом JSON.

К сожалению, хотя это теоретически сработало бы, я бы на самом деле ожидал, что это будет довольно болезненно реализовать. В конце концов, вам придется десериализовать результаты, которые вы только что сериализовали, чтобы снова их сериализовать и т. Д. Это, вероятно, также повлияет на производительность.

Вместо этого я хотел бы предложить вам добавить еще один уровень абстракции, который, по сути, снимает ответственность за создание результата с платформы ASP.NET Core. Вместо того, чтобы полагаться на это для создания своего результата, а затем на необходимость его повторного выполнения для заполнения ссылочных ссылок, вместо этого вызовите что-то другое, что может построить как ваш результат, так и ссылочные ссылки отдельно. Таким образом, действие контроллера было бы просто оркестратором для этого.

Возможно, вы могли бы сделать это с MediatR , довольно простой, но мощной реализацией посредника. Это позволит вам полностью представить свою логику вне ASP.NET Core и его концепции, основанной на HTTP (что также будет полезно для тестируемости), а также даст вам возможность рекурсивно вызывать себя (чтобы поместить результаты в свои результаты). Кроме того, он имеет собственную концепцию конвейера, которая позволит вам абстрагировать это в общий компонент, так что каждому обработчику запросов придется заботиться только об уровне непосредственных запросов.

0 голосов
/ 28 мая 2019

Мне удалось повторно вызвать (часть) конвейер MVC, добавив пользовательское промежуточное ПО, которое передает пользовательский HttpContext к вызовам для добавления встроенных ресурсов.Я не уверен, позволяет ли это в полной мере использовать кеширование и авторизацию ответов, но в простом API-интерфейсе он повторно вызывает конвейер MVC.

Это, однако, довольно громоздко.Вот некоторый псевдокод ниже, чтобы показать, как это можно сделать.

public class HalMiddleware
{
    private RequestDelegate next;
    public HalMiddleware(RequestDelegate next)
    {
        // We need the MVC pipeline after our custom middleware.
        this.next = next
            ?? throw new ArgumentNullException(nameof(next));
    }

    public async Task Invoke(HttpContext context)
    {
        // Invoke the 'normal' pipeline first.
        // Note that the mvc middleware would serialize the response...
        // Prevent that by injecting a custom IActionResultExecutor<ObjectResult>, 
        // that sets the ActionResult to the context instead of serializing.
        await this.next(context);

        // Our objectresultexecutor added some information on the HttpContext.
        if (!context.Items.TryGetValue("HalFormattingContext", out object formatObject)
            ||!(formatObject is HalFormattingContext halFormattingContext))
        {
            logger.LogDebug("Hal formatting context not found, other formatters are handling this response.");
            return;
        }

        // some code to create a resource object from the result.
        var rootResource = ConstructRootResource(halFormattingContext.ObjectResult);
        halFormattingContext.ObjectResult.Value = rootResource;

        // some code to figure out which actions/routes to embed.
        var toEmbeds = GetRoutesToEmbed(halFormattingContext.ActionContext);
        var requestFeature = context.Features.Get<IHttpRequestFeature>();
        foreach (var toEmbed in toEmbeds)
        {
            var halRequestFeature = new HalHttpRequestFeature(requestFeature)
            {
                Method = "GET",
                Path = toEmbed.Path
            };

            // The HalHttpContext creates a custom request and response in the constructor 
            // and creates a new feature collection with custom request and response features.
            var halContext = new HalHttpContext(context, halRequestFeature);
            await this.next(halContext);

            // Now the custom ObjectResultExecutor set the ActionResult to a property of the custom HttpResponse.
            var embedActionResult = ((HalHttpResponse)halContext.Response).ObjectResult;

            // some code to embed the new actionresult.
            Embed(rootResource, embedActionResult, toEmbed);
        }

        // Then invoke the default ObjectResultExecutor to serialize the new result.
        await halFormattingContext.DefaultExecutor.ExecuteAsync(
            halFormattingContext.ActionContext,
            halFormattingContext.ObjectResult);
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...