在 ASP.NET Core 中,Delegate 如何转换为 RequestDelegate?

nem*_*mui 12 c# asp.net-web-api asp.net-core

我查看了MapGet()正在执行的操作,发现其中一个重载采用类型Delegate(类,不是关键字,也不是RequestDelegate),但随后将其传递给MapMethods(),后者采用RequestDelegate. 我觉得非常令人困惑的是这个演员阵容是如何发生的。ARequestDelegate有签名delegate Task RequestDelegate(HttpContext httpContext)。当将一个委托转换为另一个委托时,他们不是a)需要相同的签名,b)实际上需要传递对Invoke()委托方法的引用(也就是说,你不能只将一个委托转换为另一个委托,而不管它们的签名)匹配,或者我是这么读的)。

我发现极其令人困惑的另一件事是如何将任意请求处理程序传递给 Minimal API,并以某种方式将其转换为RequestDelegate. Microsoft 的文档显示了处理程序没有诸如 之类的参数的示例() => ...,以及将通过诸如 之类的依赖注入机制解析的任意参数(MyClass a, HttpContext ctx, DbContext<MyClass> dbCtx) => ...。我假设这个处理程序以某种方式包装在另一个适合签名的 lambda 中RequestDelegate,以便 ASP.NET Core 可以传入 a HttpContext,然后包装 lambda 可以分析开发人员传入的 lambda 并决定如何解决其依赖关系等。但我完全不知道这是在哪里或如何发生的。它实际上只是接受一个 Delegate 并将其传递给另一个接受 的方法RequestDelegate,神奇的是它现在是一个有效的RequestDelegate?

有谁知道?

dav*_*owl 23

首先,没有魔法。这些路由方法有 2 组不同的重载。RequestDelegate自 .NET Core 3.1 以来就一直存在的方法,以及采用Delegate. 对于问题的前半部分,无法任意投射,DelegateRequestDelegate另一种方法确实有效。我不确定您正在阅读什么代码,但如果您链接到它将会有所帮助:)。

至于第二部分,这基本上就是最小 API 的工作原理。我们将任意值转换DelegateRequestDelegate. 该过程很复杂,并在运行时根据Delegate传入的参数和返回值生成代码。神奇的事情发生在RequestDelegateFactory中。输入是 a Delegate,输出是 a RequestDelegate。返回RequestDelegate的 遵循这些参数绑定规则以及如何解释返回值。

在幕后,我们使用动态代码生成来创建RequestDelegate. 一种简单的思考方法是,我们生成您手动编写的代码,以从传入请求中读取数据,然后使用这些数据调用您的函数。这是一个例子:

app.MapGet("/hello", (string? name) => $"Hello {name ?? "World"}");
Run Code Online (Sandbox Code Playgroud)

在此情况下将变成:

app.MapGet("/hello", (HttpContext context) =>
{
    string name = context.Request.Query["name"];
    // handler is the original delegate
    string result = handler(name);
    return context.Response.WriteAsync(result);
});
Run Code Online (Sandbox Code Playgroud)

展示一个稍微复杂一点的例子:

app.MapPost("/api/products", (Product p) =>
{
    ...
    return Results.Ok(p);
});
Run Code Online (Sandbox Code Playgroud)

变成:

app.MapPost("/api/products", async (HttpContext context) =>
{
    var product = await context.Request.ReadFromJsonAsync<Product>();
    if (product is null)
    {
        context.Response.StatusCode = 400;
        return;
    }
    var result = handler(product);
    await result.ExecuteAsync(context);
});
Run Code Online (Sandbox Code Playgroud)

这个翻译被封装在中,RequestDelegateFactory所以你甚至可以做这样的事情:

app.Use(next =>
{
    RequestDelegateResult result = RequestDelegateFactory.Create(() => "Hello World");
    return result.RequestDelegate;
});
Run Code Online (Sandbox Code Playgroud)

该中间件会将 hello world 写入生成的响应RequestDelegate,并将遵循与最小 API 相同的规则,但没有路由部分。

希望这能解释一些“魔法”。