Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/ServiceComposer/ServiceComposer.AspNetCore/llms.txt

Use this file to discover all available pages before exploring further.

Composition handlers receive the full HttpRequest object and can access incoming data in several ways — from reading the raw request body as a stream, to leveraging ASP.NET Core’s model binding infrastructure for strongly typed binding. ServiceComposer also provides declarative Bind* attributes that let you declare what you need without writing imperative binding code, and an experimental named-arguments API for retrieving bound values from the composition context.

Raw access to request data

The simplest approach is to read directly from the HttpRequest. This requires no additional configuration.
Read the body as a string and parse it manually:
[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
}
Setting Position = 0 is required because ServiceComposer enables body buffering so that multiple handlers can read the same body.

ASP.NET Core model binding

Available since v1.9.0 To use ASP.NET Core’s model binding engine you must register MVC components. Add AddControllers() (or any other MVC variant) alongside AddViewModelComposition():
builder.Services.AddViewModelComposition();
builder.Services.AddControllers();
If your application already uses MVC, Razor Pages, or controllers, model binding is already available and no additional configuration is needed.

Defining binding models

Define a body model and a composite request model using the standard ASP.NET binding attributes:
class BodyModel
{
    public string AString { get; set; }
}
Property names marked with [FromRoute] or [FromQuery] must match the route parameter names or query string key names. The class names themselves are irrelevant.

Bind<T> — bind and use immediately

Call request.Bind<T>() to bind the incoming request to a model in one step:
[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
}

TryBind<T> — bind with model state access

Available since v2.2.0 TryBind<T> returns a tuple with the bound model, a flag indicating whether the model was set, and the ModelStateDictionary for inspecting binding errors:
[HttpPost("/sample/{id}")]
public async Task Handle(HttpRequest request)
{
    var (model, isModelSet, modelState) = await request.TryBind<RequestModel>();
    //use values as needed
}
The isModelSet boolean distinguishes between a model binder that found no value and one that explicitly bound null.

Declarative model binding

Available since v4.1.0 Instead of calling Bind/TryBind imperatively, you can declare the models you need directly on the handler method using Bind* attributes. ServiceComposer resolves them before invoking Handle and makes the results available via the arguments API:
[HttpPost("/sample/{id}")]
[BindFromBody<BodyModel>]
[BindFromRoute<int>(routeValueKey: "id")]
public Task Handle(HttpRequest request)
{
    return Task.CompletedTask;
}

Available binding attributes

BindFromBody<T>

Binds type T from the request body payload.

BindFromRoute<T>

Binds type T from the route value identified by routeValueKey.

BindFromQuery<T>

Binds type T from the query string parameter identified by queryParameterName.

BindFromForm<T>

Binds type T from the form fields collection. If formFieldName is specified, only that field is used as the binding source.

BindFromServices<T>

Resolves type T from the DI container. The parameterName argument identifies the argument in the composition context.

Bind<T>

Binds type T from multiple sources. Each property on T can specify its own source using [FromBody], [FromRoute], etc.

Accessing bound values — the arguments API

Experimental API (SC0001) Once models are declared with Bind* attributes, retrieve them from the composition context using GetArguments:
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 componentGetArguments(this) scopes the lookup to the calling handler. Only instances of ICompositionRequestsHandler, ICompositionEventsSubscriber, and ICompositionEventsHandler<T> can be passed as the owner argument.
GetArguments is decorated with the [Experimental] attribute and raises compiler warning SC0001. The API is subject to change in future releases. Suppress the warning consciously with #pragma warning disable SC0001 when you choose to use it.

Contract-less handlers and model binding

When using contract-less composition handlers, model binding is declared through method parameter attributes and the source generator emits the correct Bind* attributes automatically. [FromServices] parameters are resolved from DI; other parameters follow standard ASP.NET binding conventions:
[CompositionHandler]
class SampleCompositionHandlerWithServices
{
    [HttpGet("/sample/{id}")]
    public Task SampleMethod(int id, [FromServices] IMyService myService)
    {
        return Task.CompletedTask;
    }
}
The generated wrapper wires up [BindFromRoute<int>("id")] and [BindFromServices<IMyService>("myService")] automatically, keeping the user-written class free of infrastructure concerns.

Build docs developers (and LLMs) love