ServiceComposer.AspNetCore

Model Binding

Available starting with v1.9.0

Composition handlers get access to the incoming HttpRequest. If the incoming request contains a body, the handler can access a body like the following:

{
    "AString": "Some value"
}

by using the following code:

[HttpPost("/sample/{id}")]
public async Task Handle(HttpRequest request)
{
    request.Body.Position = 0;
    using var reader = new StreamReader(request.Body, Encoding.UTF8, leaveOpen: true );
    var body = await reader.ReadToEndAsync();
    var content = JObject.Parse(body);

    //use the content object instance as needed
}

snippet source | anchor

Something similar applies to getting data from the route, or from the query string, or the incoming form. For example:

[HttpPost("/sample/{id}")]
public Task Handle(HttpRequest request)
{
    var routeData = request.HttpContext.GetRouteData();
    var id = int.Parse(routeData.Values["id"].ToString());

    //use the id value as needed

    return Task.CompletedTask;
}

snippet source | anchor

Use model binding

It’s possible to leverage ASP.Net built-in support for model binding to avoid accessing raw values of the incoming HttpRequest, such as the body.

To start using model binding configure the ASP.Net application to add MVC components:

public void ConfigureServices(IServiceCollection services)
{
    services.AddViewModelComposition();
    services.AddControllers();
}

snippet source | anchor

If, for reasons outside the scope of composing responses, there is the need to use MVC, or Razor Pages, or just controllers and views, any of the MVC configuration options will add support for model binding.

THe first step is to define the models, for example in the above sample the body model will look like the following C# class:

class BodyModel
{
    public string AString { get; set; }
}

snippet source | anchor

The class name is irrelevant

Once we have a model for the body, a model that represent the incoming request is needed. In in a scenario in which there is the need to bind the body and the id from the route, the following request model can be used:

class RequestModel
{
    [FromRoute] public int id { get; set; }
    [FromBody] public BodyModel Body { get; set; }
}

snippet source | anchor

The class name is irrelevant. The name of the properties marked as [FromRoute] or [FromQueryString] must match the route data names or query string keys names. The name for the body, or form, property is irrelevant.

Once the models are defined they can be used as follows:

[HttpPost("/sample/{id}")]
public async Task Handle(HttpRequest request)
{
    var requestModel = await request.Bind<RequestModel>();
    var body = requestModel.Body;
    var aString = body.AString;
    var id = requestModel.id;

    //use values as needed
}

snippet source | anchor

For more information and options when using model binding refer to the Microsoft official documentation.

Try bind to access the model state dictionary

Available starting with v2.2.0

The TryBind model binding option allows binding the incoming request and at the same time access additional information about the binding process:

[HttpPost("/sample/{id}")]
public async Task Handle(HttpRequest request)
{
    var (model, isModelSet, modelState) = await request.TryBind<RequestModel>();
    //use values as needed
}

snippet source | anchor

The TryBind return value is a tuple containing the binding result (the model), a boolena detailing if the model was set or not (useful to distinguish between a model binder which does not find a value and the case where a model binder sets the null value), and the ModelStateDictionary to access binding errors.