I looked into what MapGet()
is doing and found that one of the overloads takes a Delegate
type (class, not keyword, and not a RequestDelegate
), but then pass it on to MapMethods()
, which takes a RequestDelegate
. What I find very confusing is how this cast takes place. A RequestDelegate
has the signature delegate Task RequestDelegate(HttpContext httpContext)
. When casting a delegate to another, don't they a) need the same signature, b) actually need to pass a reference to the Invoke()
method of the delegate (that is to say you can't just convert a delegate to another despite their signatures matching, or so I read).
Another thing I find extremely confusing is how one can pass in an arbitrary request handler to the Minimal API and it somehow being converted to a RequestDelegate
. The documentation from Microsoft shows examples where the handler has no arguments like () => ...
, and also arbitrary arguments which will be resolved through the dependency injection mechanism like (MyClass a, HttpContext ctx, DbContext<MyClass> dbCtx) => ...
. I would assume this handler is somehow wrapped in another lambda that fits the RequestDelegate
signature, so that ASP.NET Core can pass in a HttpContext
and the wrapping lambda can then analyze the lambda passed in by the developer and decide how to resolve its depencies and such. But I have no clue at all where or how this happens. It literally just takes in a Delegate and passes it into another method which takes a RequestDelegate
, and magically it's now a valid RequestDelegate
?
Anyone knows?
CodePudding user response:
First, there is no magic. There are 2 distinct set of overloads for these routing methods. Ones that take RequestDelegate
and have been around since .NET Core 3.1, and the new minimal API methods that take Delegate
. For the first half of your question, there's no way to cast an arbitrary Delegate
to RequestDelegate
but the other way does work. I'm not sure what code you were reading but it would help if you linked to it :).
As for the second part, that's basically how minimal APIs work. We convert an arbitrary Delegate
into a RequestDelegate
. That process is complex and generates code at runtime based on the parameters and the return value of the Delegate
passed in. The magic happens in the RequestDelegateFactory. The input is a Delegate
and the output is a RequestDelegate
. The RequestDelegate
that is returned follows these rules for parameter binding, and for how to interpret the return values.
Under the covers we use dynamic code generation to create the RequestDelegate
. A simple way to think about it is that we generate the code you would have written by hand to read data from the incoming request and then we call your function with this data. Here's an example:
app.MapGet("/hello", (string? name) => $"Hello {name ?? "World"}");
Under this will get turned into:
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);
});
To show one more slightly more complex example:
app.MapPost("/api/products", (Product p) =>
{
...
return Results.Ok(p);
});
Turns into:
app.MapGet("/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);
});
This translation is encapsulated in the RequestDelegateFactory
so you can even do things like this:
app.Use(next =>
{
RequestDelegateResult result = RequestDelegateFactory.Create(() => "Hello World");
return result.RequestDelegate;
});
This middleware will write hello world to the response from the generated RequestDelegate
and will follow the same rules as minimal APIs but without the routing piece.
Hopefully that explains some of the "magic".