This guide walks you from an empty project to a working ViewModel Composition gateway in about five minutes. You will create two independent composition handlers — one for a Sales service and one for a Marketing service — wire them into an ASP.NET Core gateway, and observe the merged JSON response that a client receives from a single HTTP request. No shared database, no client-side merging, no service coupling.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.
Prerequisites
Before starting, make sure you have:- .NET 10 or later installed (download here)
- An ASP.NET Core web application project to act as the API gateway (a minimal API project works perfectly)
- Basic familiarity with ASP.NET Core and the
dotnetCLI
This single package contains everything the gateway needs: the composition pipeline, the assembly scanner, and the endpoint routing integration.
Composition handler projects — the class libraries that each service ships — also reference this package to access
ICompositionRequestsHandler and GetComposedResponseModel(). The package is a shared contract, not just a gateway-side dependency.using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using ServiceComposer.AspNetCore;
var builder = WebApplication.CreateBuilder();
builder.Services.AddRouting();
builder.Services.AddViewModelComposition();
var app = builder.Build();
app.MapCompositionHandlers();
app.Run();
AddViewModelComposition() — registers the ServiceComposer services and sets up the assembly scannerMapCompositionHandlers() — discovers all ICompositionRequestsHandler implementations in loaded assemblies and maps them as endpointsServiceComposer scans at startup — there is no manual handler registration and no route configuration in
Program.cs.Create a class library project for the Sales service’s composition handlers (e.g.
Sales.ViewModelComposition). Add a package reference to ServiceComposer.AspNetCore, then create the handler class:using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using ServiceComposer.AspNetCore;
public class SalesProductInfo : ICompositionRequestsHandler
{
[HttpGet("/product/{id}")]
public Task Handle(HttpRequest request)
{
var vm = request.GetComposedResponseModel();
//retrieve product details from the sales database or service
vm.ProductId = request.HttpContext.GetRouteValue("id").ToString();
vm.ProductPrice = 100;
return Task.CompletedTask;
}
}
ICompositionRequestsHandler, which requires a single Task Handle(HttpRequest request) method[HttpGet("/product/{id}")] attribute tells ServiceComposer which route and HTTP verb this handler responds torequest.GetComposedResponseModel() returns the shared dynamic view model for this requestProductId and ProductPriceusing System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using ServiceComposer.AspNetCore;
public class MarketingProductInfo : ICompositionRequestsHandler
{
[HttpGet("/product/{id}")]
public Task Handle(HttpRequest request)
{
var vm = request.GetComposedResponseModel();
//retrieve product details from the marketing database or service
vm.ProductName = "Sample product";
vm.ProductDescription = "This is a sample product";
return Task.CompletedTask;
}
}
This handler targets the same route —
/product/{id} — but it is completely independent of SalesProductInfo. Neither class imports nor references the other. Each service evolves its handler in isolation.Each service should write to distinct, non-overlapping properties on the view model. When two handlers run in parallel and both write to the same property, the result is a data race and the output is unpredictable. Assign unique property names per service.
ServiceComposer’s assembly scanner only discovers handlers in assemblies that are actually loaded by the gateway process. Add project references to both handler class libraries from the gateway
.csproj:<!-- In the gateway .csproj -->
<ItemGroup>
<ProjectReference Include="..\Sales.ViewModelComposition\Sales.ViewModelComposition.csproj" />
<ProjectReference Include="..\Marketing.ViewModelComposition\Marketing.ViewModelComposition.csproj" />
</ItemGroup>
In production, composition handler class libraries are typically distributed as NuGet packages rather than project references. The gateway project simply references the packages, and the scanner picks up all handlers at startup without any further configuration.
{
"productId": "1",
"productPrice": 100,
"productName": "Sample product",
"productDescription": "This is a sample product"
}
How It Works
When a request arrives at a composition endpoint, ServiceComposer executes the following pipeline:- Resolve handlers — all
ICompositionRequestsHandlerimplementations registered for the matching route and HTTP verb are resolved from the DI container - Run in parallel — all
Handle()methods are invoked concurrently viaTask.WhenAll, so handler latency does not compound - Shared view model — every handler in the same request receives the same
dynamicobject fromGetComposedResponseModel(); each writes its own properties onto it - Serialize and return — once all handlers complete, ServiceComposer serializes the composed view model (using
System.Text.Jsonby default) and writes it to the HTTP response
Next Steps
Composition Handlers
Explore advanced handler patterns: contract-less handlers, composition over MVC controllers, and custom HTTP status codes.
Events Between Handlers
Learn how handlers can publish and subscribe to in-request events to coordinate complex compositions such as assembling lists.
Model Binding
Bind route values, query strings, and request bodies to strongly typed C# models inside your composition handlers using ASP.NET Core Model Binding.
Strongly Typed View Models
Replace the default dynamic view model with a concrete C# class using a view model factory, enabling compile-time safety across handlers.