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 = JsonNode.Parse(body);
//use the content object instance as needed
}
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;
}
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();
}
If, for reasons outside the scope of composing responses, there is the need to use MVC, Razor Pages, or just controllers and views, any 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; }
}
The class name is irrelevant
Once we have a model for the body, a model representing the incoming request is needed. 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(Name = "id")] public int Id { get; set; }
[FromBody] public BodyModel Body { get; set; }
}
The class name is irrelevant. The name of the properties marked as
[FromRoute]
or[FromQueryString]
must match the route data names or query string key names. The name for the body, form, or 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
}
For more information and options when using model binding, refer to the Microsoft official documentation.
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
}
The TryBind
return value is a tuple containing the binding result (the model), a boolean detailing if the model was set or not (helpful to distinguish between a model binder that does not find a value and the case where a model binder sets the null
value), and the ModelStateDictionary
to access binding errors.
Available starting with v4.1.0
Instead of directly using the Bind
/TryBind
API to exercise the model binding engine, it is possible to use a set of Bind*
attributes to declare the models the composition handlers want to bind. The following snippet demonstrates a composition handler that declares the binding to two models:
[HttpPost("/sample/{id}")]
[BindFromBody<BodyModel>]
[BindFromRoute<int>(routeValueKey: "id")]
public Task Handle(HttpRequest request)
{
return Task.CompletedTask;
}
The first model, of type BodyModel
, a user type, is bound from the request body payload using the BindFromBody<T>
attribute. The second model is an integer bound from the request route path using the "id" route value key.
The declarative model binding supports several binding sources through the following attributes:
BindFromBodyAttribute<T>
: Binds the given type T to the incoming request body payload.BindFromRouteAttribute<T>
: Binds the given type T to the incoming route element identified by therouteValueKey
argument.BindFromQueryAttribute<T>
: Binds the given type T to the incoming query string parameter value identified by thequeryParameterName
argument.BindFromFormAttribute<T>
: Binds the given type T to the incoming form fields collection. If the optionalformFieldName
is specified the binding operation only takes into account the specified form field as binding source; otherwise, the binding operation expects to bind to aIFormCollection
type.BindAttribute<T>
: Binds the given type T from multiple sources. EachT
type property can specify the source using the variousFromBody
,FromForm
,FromRoute
, etc., default ASP.Net binding attributes.
Once model binding is declared, the bound models are accessible from the ICompositionContext
arguments API, as demonstrated by the following snippet:
var ctx = request.GetCompositionContext();
var arguments = ctx.GetArguments(this);
var findValueByType = arguments.Argument<BodyModel>();
var findValueByTypeAndName = arguments.Argument<int>(name: "id");
var findValueByTypeAndSource = arguments.Argument<int>(bindingSource: BindingSource.Header);
var findValueByTypeSourceAndName = arguments.Argument<string>(name: "user", bindingSource: BindingSource.Query);
Arguments are segregated by component, to access them the arguments owner instance must be passed to the GetArguments(owner)
method. Owners are restricted to a limited set of types: ICompositionRequestsHandler
, ICompositionEventsSubscriber
, and ICompositionEventsHandler<T>
.
The API to search for arguments exposed by the composition context is experimental and, as such, subject to change. The GetArguments(ownerComponent)
method is decorated with the Experimental
attribute and will raise an SC0001
warning. The warning can be suppressed with a regular pragma directive, e.g., #pragma warning disable SC0001
.