Manual binding – Minimal API

The BindAsync method takes an HttpContext and a ParameterInfo parameter and returns a ValueTask<TSelf> where TSelf is the type we are writing data binding for. The HttpContext represents the source of the data (the HTTP request), and the ParameterInfo represents the delegate’s parameter, from which we could want to know something, like its name.The data-binding API supports the implementation of one of the following methods:

public static ValueTask<TSelf?> BindAsync(HttpContext context, ParameterInfo parameter);
public static ValueTask<TSelf?> BindAsync(HttpContext context);

Implementing the IBindableFromHttpContext<TSelf> interface provides the appropriate BindAsync method.

Here is an example that binds a Person from the HTTP request’s query parameters:

app.MapGet(
    “minimal-endpoint-input-Person/”,
    (Person person) => person
);
public class Person : IBindableFromHttpContext<Person>
{
    public required string Name { get; set; }
    public required DateOnly Birthday { get; set; }
    public static ValueTask<Person?> BindAsync(
        HttpContext context,
        ParameterInfo parameter)
    {
        var name = context.Request.Query[“name”].Single();
        var birthdayIsValid = DateOnly.TryParse(
            context.Request.Query[“birthday”],
            out var birthday
        );
        if (name is not null && birthdayIsValid) {
            var person = new Person() {
                Name = name,
                Birthday = birthday
            };
            return ValueTask.FromResult(person)!;
        }
        return ValueTask.FromResult(default(Person));
    }
}

The preceding code returns a JSON representation of the person. For example, if we request the following URI:

/minimal-endpoint-input-Person?name=John%20Doe&birthday=2023-06-14

The endpoint returns:

{
  “name”: “John Doe”,
  “birthday”: “2023-06-14”
}

As we can see, the BindAsync method is way more powerful than the TryParse method because we can access a broader range of options using the HttpContext, allowing us to cover complex use cases.However, in this case, we could have leveraged the [AsParameters] attribute to achieve the same result and get the data from the query without needing to write the data-binding code manually. What a great opportunity to explore this attribute; here’s the updated version of the same code:

app.MapGet(
    “minimal-endpoint-input-Person2/”,
    ([AsParameters] Person2 person) => person
);
public class Person2
{
    public required string Name { get; set; }
    public required DateOnly Birthday { get; set; }
}

That’s it; the AsParameters attribute did the work for us!Now that we covered reading the input values from different places in the HTTP request, it is time to explore how to output results.

Outputs

There are several ways to output data from our delegates:

We explore those possibilities next.

You may also like