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.

By default, ServiceComposer creates a shared, dynamic ExpandoObject as the ViewModel for each composition request. When you need a specific concrete type — or when different endpoints require different ViewModel structures — you can replace this default behaviour by implementing IViewModelFactory or IEndpointScopedViewModelFactory. IViewModelFactory is the global factory: exactly one implementation may be registered, and it applies to every composition endpoint. IEndpointScopedViewModelFactory extends IViewModelFactory and allows a factory to be associated with a single endpoint. Multiple endpoint-scoped factories can coexist, one per endpoint. Both interfaces are registered explicitly through ViewModelCompositionOptions — the assembly scanner does not auto-register them.

Namespace

ServiceComposer.AspNetCore

Interface definitions

IViewModelFactory

public interface IViewModelFactory
{
    object CreateViewModel(HttpContext httpContext, ICompositionContext compositionContext);
}

IEndpointScopedViewModelFactory

public interface IEndpointScopedViewModelFactory : IViewModelFactory
{
}
IEndpointScopedViewModelFactory inherits all members from IViewModelFactory. The distinction is purely in how it is registered — endpoint-scoped factories are matched to a specific endpoint by the registration call.

Methods

CreateViewModel

Called once per composition request, before any handlers execute. The returned object becomes the ViewModel shared by all handlers on that request. Handlers retrieve it via request.GetComposedResponseModel().
httpContext
HttpContext
required
The current HttpContext. Use it to inspect route values, query strings, headers, or services when deciding which ViewModel type to create.
compositionContext
ICompositionContext
required
The ICompositionContext for the current composition request. Provides the RequestId and allows raising events during ViewModel construction if needed.
Returns object — the ViewModel instance that will be shared across all handlers for this request. Must not be null.

Registration

Global factory

Register a global factory by calling RegisterGlobalViewModelFactory<T>() on ViewModelCompositionOptions:
builder.Services.AddViewModelComposition(options =>
{
    options.RegisterGlobalViewModelFactory<MyViewModelFactory>();
});
Only one global IViewModelFactory may be registered. Registering a second global factory throws an exception at startup.

Endpoint-scoped factory

Register an endpoint-scoped factory by calling RegisterEndpointScopedViewModelFactory<T>():
builder.Services.AddViewModelComposition(options =>
{
    options.RegisterEndpointScopedViewModelFactory<ProductViewModelFactory>();
});
Multiple endpoint-scoped factories may be registered. Each factory must be decorated with the same routing attributes as the handler(s) for the endpoint it targets, so ServiceComposer can match the factory to the correct endpoint.

Precedence

When both a global factory and an endpoint-scoped factory are registered, the endpoint-scoped factory takes precedence for its matched endpoint. The global factory is used for all other endpoints.

Usage examples

Custom global ViewModel factory

using Microsoft.AspNetCore.Http;
using ServiceComposer.AspNetCore;

public class DictionaryViewModelFactory : IViewModelFactory
{
    public object CreateViewModel(HttpContext httpContext, ICompositionContext compositionContext)
    {
        // Return a Dictionary instead of the default ExpandoObject
        return new Dictionary<string, object?>();
    }
}
Registration:
builder.Services.AddViewModelComposition(options =>
{
    options.RegisterGlobalViewModelFactory<DictionaryViewModelFactory>();
});

Endpoint-scoped typed ViewModel factory

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using ServiceComposer.AspNetCore;

public class ProductViewModel
{
    public string? ProductId { get; set; }
    public decimal Price { get; set; }
    public int StockLevel { get; set; }
}

public class ProductViewModelFactory : IEndpointScopedViewModelFactory
{
    [HttpGet("/product/{id}")]
    public object CreateViewModel(HttpContext httpContext, ICompositionContext compositionContext)
    {
        return new ProductViewModel();
    }
}
Registration:
builder.Services.AddViewModelComposition(options =>
{
    options.RegisterEndpointScopedViewModelFactory<ProductViewModelFactory>();
});
Handlers on GET /product/{id} can now use the strongly-typed overload:
public class SalesProductInfo : ICompositionRequestsHandler
{
    [HttpGet("/product/{id}")]
    public Task Handle(HttpRequest request)
    {
        var vm = request.GetComposedResponseModel<ProductViewModel>();
        vm.Price = 99.99m;
        return Task.CompletedTask;
    }
}
IViewModelFactory and IEndpointScopedViewModelFactory implementations are not discovered by the assembly scanner. They must be explicitly registered via RegisterGlobalViewModelFactory<T>() or RegisterEndpointScopedViewModelFactory<T>().

Build docs developers (and LLMs) love