C# 使用来自资源筛选器的另一个路由重新执行MVC管道 TL;博士
如何使用其他路由从IAsyncResourceFilter重新调用MVC路由引擎和MvcHandler?我基本上希望调用管道两次(并行?) 背景 我正在为ASP.Net核心API构建一个实现。为了实现HAL,返回的资源应该能够嵌入其他资源,以减少客户端和服务器之间的聊天。响应如下所示:C# 使用来自资源筛选器的另一个路由重新执行MVC管道 TL;博士,c#,asp.net-mvc,asp.net-core,asp.net-core-webapi,C#,Asp.net Mvc,Asp.net Core,Asp.net Core Webapi,如何使用其他路由从IAsyncResourceFilter重新调用MVC路由引擎和MvcHandler?我基本上希望调用管道两次(并行?) 背景 我正在为ASP.Net核心API构建一个实现。为了实现HAL,返回的资源应该能够嵌入其他资源,以减少客户端和服务器之间的聊天。响应如下所示: { "_links": { "self": { "href": "/myResource/1" } }, "name": "myResource",
{
"_links": {
"self": {
"href": "/myResource/1"
}
},
"name": "myResource",
"foo": "bar",
"_embedded": {
"relatedResource": {
"_links": {
"self": {
"href": "/relatedResources/5"
}
},
"name": "relatedResource",
"baz": "foo"
}
}
}
我想将资源的嵌入与控制器分离,所以我想
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);
}
}
我在中找到的这个图意味着应该可以通过某种方式从资源过滤器重新执行管道:
那么,如何使用另一个url重新执行MVC管道,同时也能够为当前url执行管道?您正在查看的图像并没有真正显示他们可以重新执行管道。相反,映像试图说明的是,资源筛选器可以使管道短路(例如,通过返回缓存的结果),并且它将在结果筛选器之后再次执行(例如,为以后的请求缓存结果) 重新执行MVC过滤器管道本身是不可能的。这主要是因为无法为单独的执行重新配置执行上下文 但是,您可以做的是重新执行中间件管道。例如,当捕捉到异常时,会显示异常处理端点的结果。它通过更改请求参数(例如
HttpContext.request.Path
)并再次调用中间件管道(即next()
)来实现这一点
由于中间件直接作用于HttpRequest和HttpResponse对象,这当然意味着逻辑必须要低得多。例如,您不能仅获取一个ObjectResult
并调整该结果以包含另一个值。相反,您必须在已经序列化的JSON结果之上工作
不幸的是,虽然这在理论上是可行的,但实际上我认为实施起来会相当痛苦。毕竟,您将对刚刚序列化的结果进行反序列化,以便再次对其进行序列化等等。这可能也会对性能产生影响
相反,我建议您添加另一个抽象层,基本上将构建结果的责任从ASP.NET核心框架移开。不要依赖它来构建结果,然后必须再次执行它来填充引用的链接,而是调用其他可以分别构建结果和引用的链接的东西。因此控制器操作将只是一个协调器
您可能可以使用一个相当简单但功能强大的中介实现来实现这一点。它允许您在ASP.NET核心及其基于HTTP的概念之外完整地表示您的逻辑(这对于可测试性也很好),同时还允许您递归地调用自身(将结果放入结果中)。它还附带了自己的管道概念,允许您将其抽象为一个公共组件,这样每个请求处理程序只需关心即时请求层。您正在查看的图像并不能真正显示它们可以重新执行管道。相反,映像试图说明的是,资源筛选器可以使管道短路(例如,通过返回缓存的结果),并且它将在结果筛选器之后再次执行(例如,为以后的请求缓存结果) 重新执行MVC过滤器管道本身是不可能的。这主要是因为无法为单独的执行重新配置执行上下文 但是,您可以做的是重新执行中间件管道。例如,当捕捉到异常时,会显示异常处理端点的结果。它通过更改请求参数(例如
HttpContext.request.Path
)并再次调用中间件管道(即next()
)来实现这一点
由于中间件直接作用于HttpRequest和HttpResponse对象,这当然意味着逻辑必须要低得多。例如,您不能仅获取一个ObjectResult
并调整该结果以包含另一个值。相反,您必须在已经序列化的JSON结果之上工作
不幸的是,虽然这在理论上是可行的,但实际上我认为实施起来会相当痛苦。毕竟,您将对刚刚序列化的结果进行反序列化,以便再次对其进行序列化等等。这可能也会对性能产生影响
相反,我建议您添加另一个抽象层,基本上将构建结果的责任从ASP.NET核心框架移开。不要依赖它来构建结果,然后必须再次执行它来填充引用的链接,而是调用其他可以分别构建结果和引用的链接的东西。因此控制器操作将只是一个协调器
您可能可以使用一个相当简单但功能强大的中介实现来实现这一点。它将允许你代表你的客户
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);
}
}