Minimal APIs in .NET 6: How to bind [FromQuery]
Minimal APIs are great. I love Minimal APIs.
One of the things I like is how they have discarded the controller concept (which I always felt was a remnant of MVC, and just put there to keep Web APIs looking like MVC).
But, not everything works as you would expect, for instance model bindings.
Problem
When trying to use the [FromQuery]
attribute (from the Microsoft.AspNetCore.Mvc
package) with a complex type in an endpoint:
app.MapGet("/", ([FromQuery]QueryParameters? parameters) => parameters);
This will fail with the following error message:
This is because Minimal API does not support complex types when using the [FromQuery]
attribute.
Solution
The Microsoft documentation describes how to mitigate this using custom bindings, choosing one of two ways:
TryParse
method for the type.2. Control the binding process by implementing a
BindAsync
method on a type.I've created a code example using a record QueryParameters
which contains a list of id's, skip and take, relevant for filtering/paging.
The record also contains a method BindAsyc
which does the "magic" by parsing the Http Context and mapping its values to the record values.
Voilá - the QueryParameters record can now be used by the endpoint handler.
Code:
using Microsoft.AspNetCore.Http.Json;
using System.Text.Json.Serialization;
var builder = WebApplication.CreateBuilder(args);
builder.Services.Configure<JsonOptions>(options =>
{
options.SerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
});
var app = builder.Build();
app.UseHttpsRedirection();
app.MapGet("/", (QueryParameters? parameters) => parameters);
app.Run();
public record QueryParameters(IReadOnlyList<string>? Ids, int? Skip, int? Take)
{
public static ValueTask<QueryParameters?> BindAsync(HttpContext context)
=> ValueTask.FromResult<QueryParameters?>(new (
Ids: !string.IsNullOrEmpty(context.Request.Query["ids"]) ? context.Request.Query["ids"].ToString().Split(',').ToList().AsReadOnly() : null,
Skip: int.TryParse(context.Request.Query["skip"], out var skip) ? skip : null,
Take: int.TryParse(context.Request.Query["take"], out var take) ? take : null));
}
The code is available on GitHub.
Update 2. August 2022:
David Fowler (!) actually tweeted about this blog post, pointing to how this will be handled in .NET7 using an [AsParameters]
attribute.
The blog post:
https://jaliyaudagedara.blogspot.com/2022/07/net-7-preview-5-using-asparameters.html
The tweet: