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.

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.

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 dotnet CLI

1
Install the NuGet Package
2
Add ServiceComposer.AspNetCore to your gateway project:
3
dotnet add package ServiceComposer.AspNetCore
4
This single package contains everything the gateway needs: the composition pipeline, the assembly scanner, and the endpoint routing integration.
5
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.
6
Configure the Gateway in Program.cs
7
Open (or create) your gateway’s Program.cs and register ServiceComposer with two calls:
8
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();
9
That’s all the gateway project needs:
10
  • AddViewModelComposition() — registers the ServiceComposer services and sets up the assembly scanner
  • MapCompositionHandlers() — discovers all ICompositionRequestsHandler implementations in loaded assemblies and maps them as endpoints
  • 11
    ServiceComposer scans at startup — there is no manual handler registration and no route configuration in Program.cs.
    12
    Write the Sales Composition Handler
    13
    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:
    14
    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;
        }
    }
    
    15
    Key points about this handler:
    16
  • It implements ICompositionRequestsHandler, which requires a single Task Handle(HttpRequest request) method
  • The [HttpGet("/product/{id}")] attribute tells ServiceComposer which route and HTTP verb this handler responds to
  • request.GetComposedResponseModel() returns the shared dynamic view model for this request
  • The handler writes only the properties it owns: ProductId and ProductPrice
  • 17
    Write the Marketing Composition Handler
    18
    Create a second class library (e.g. Marketing.ViewModelComposition) with its own handler:
    19
    using 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;
        }
    }
    
    20
    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.
    21
    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.
    22
    Reference the Handler Assemblies from the Gateway
    23
    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:
    24
    <!-- In the gateway .csproj -->
    <ItemGroup>
      <ProjectReference Include="..\Sales.ViewModelComposition\Sales.ViewModelComposition.csproj" />
      <ProjectReference Include="..\Marketing.ViewModelComposition\Marketing.ViewModelComposition.csproj" />
    </ItemGroup>
    
    25
    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.
    26
    Run the Gateway and Test
    27
    Start the gateway:
    28
    dotnet run
    
    29
    Issue a GET request to the composition endpoint:
    30
    curl http://localhost:5000/product/1
    
    31
    The response is a single, merged JSON object combining properties from both handlers:
    32
    {
      "productId": "1",
      "productPrice": 100,
      "productName": "Sample product",
      "productDescription": "This is a sample product"
    }
    
    33
    The client made one request. No client-side merging happened. Neither handler knew about the other.

    How It Works

    When a request arrives at a composition endpoint, ServiceComposer executes the following pipeline:
    1. Resolve handlers — all ICompositionRequestsHandler implementations registered for the matching route and HTTP verb are resolved from the DI container
    2. Run in parallel — all Handle() methods are invoked concurrently via Task.WhenAll, so handler latency does not compound
    3. Shared view model — every handler in the same request receives the same dynamic object from GetComposedResponseModel(); each writes its own properties onto it
    4. Serialize and return — once all handlers complete, ServiceComposer serializes the composed view model (using System.Text.Json by default) and writes it to the HTTP response
    This architecture means that adding a new service never requires changing an existing handler. You create a new handler class library, reference it from the gateway, and it is automatically incorporated into the composition for every route it targets.

    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.

    Build docs developers (and LLMs) love