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 over controllers lets you add ViewModel Composition to an existing ASP.NET Core MVC application without introducing a separate composition gateway or replacing your existing controllers. When enabled, ServiceComposer injects an MVC filter that intercepts every controller invocation. If a route matches both a regular controller action and one or more composition handlers, ServiceComposer invokes the matching handlers after the controller executes and before the view is rendered — giving handlers the opportunity to enrich the view model produced by the controller. This technique is particularly useful when migrating a monolithic MVC application toward a services-based architecture incrementally, or when you want to use Razor as a templating engine while sourcing parts of the view model from independent services.

Enabling composition over controllers

Call EnableCompositionOverControllers() inside your AddViewModelComposition options callback:
builder.Services.AddViewModelComposition(options =>
{
    options.EnableCompositionOverControllers();
});
Once enabled, ServiceComposer registers a global MVC filter. No further configuration is needed on the controller side — existing controllers continue to work exactly as before.

How it works

1

Request arrives

An incoming HTTP request is matched by ASP.NET Core’s routing system to a controller action as normal.
2

Controller executes

The controller action runs and produces its view model, populating ViewData, ViewBag, or a strongly typed model.
3

Composition handlers run

If the resolved route also matches one or more ServiceComposer composition handlers, those handlers are invoked in parallel. Each handler can read from and write to the composed view model, adding properties sourced from downstream services.
4

View renders

The enriched view model is passed to the Razor view (or whichever view engine is configured), which renders the final response.

Supported handler types

Composition over controllers supports both handler styles:

ICompositionRequestsHandler

Standard composition handlers that implement the ICompositionRequestsHandler interface, including full model binding support.

Contract-less handlers

Contract-less composition request handlers, also with full model binding support. These handlers do not need to declare an explicit interface and are discovered through assembly scanning.

Route matching

ServiceComposer matches composition handlers to controller routes using the same route template system. Routes are matched case-insensitively by default. To opt out of case-insensitive matching, pass useCaseInsensitiveRouteMatching: false to EnableCompositionOverControllers:
builder.Services.AddViewModelComposition(options =>
{
    options.EnableCompositionOverControllers(useCaseInsensitiveRouteMatching: false);
});
Because ServiceComposer relies on ASP.NET Core’s endpoint routing to match handlers to routes, any route constraints or defaults defined on the controller action are respected during handler matching.

Typical use cases

A ProductController fetches core product data (name, description, price) from its own data store. A InventoryHandler and a ReviewsHandler registered with ServiceComposer add stock level and review summary to the same view model — all without modifying the controller.
As functionality is extracted to separate services, composition handlers are added alongside the existing controller. The controller handles what has not yet been migrated; handlers cover what has been extracted. The Razor view does not change.
Teams that prefer Razor over returning raw JSON can keep their MVC views and routing intact while still sourcing parts of the view model from independent services through composition handlers.
Composition over controllers requires MVC services to be registered. Make sure builder.Services.AddControllersWithViews() or builder.Services.AddMvc() is called before AddViewModelComposition.

Build docs developers (and LLMs) love