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.

ServiceComposer lets any assembly that participates in composition configure its own options at startup — no changes to the host application’s Program.cs required. The assembly scanner discovers every type that implements IViewModelCompositionOptionsCustomization and calls its Customize method before the application begins handling requests, making this pattern ideal for self-contained service packages and plugins.

The IViewModelCompositionOptionsCustomization interface

The contract is a single method:
namespace ServiceComposer.AspNetCore
{
    public interface IViewModelCompositionOptionsCustomization
    {
        void Customize(ViewModelCompositionOptions options);
    }
}
Types implementing IViewModelCompositionOptionsCustomization are not managed by the IoC container. Dependency injection is not available inside Customize. Use options.Configuration for external values instead.

Basic usage

Place a class that implements the interface anywhere in a class-library assembly. When the host calls AddViewModelComposition(), the scanner finds it automatically and invokes Customize.
// In a class library assembly (e.g. Sales.ViewModelComposition.dll)
public class SalesCompositionOptionsCustomization : IViewModelCompositionOptionsCustomization
{
    public void Customize(ViewModelCompositionOptions options)
    {
        options.AssemblyScanner.AddAssemblyFilter(name =>
            name.StartsWith("Sales.")
                ? AssemblyScanner.FilterResults.Include
                : AssemblyScanner.FilterResults.Exclude);
    }
}
The host only needs:
builder.Services.AddViewModelComposition();
The library’s customization is picked up automatically — no explicit registration is needed.

Accessing configuration

ViewModelCompositionOptions.Configuration is null by default. If you attempt to read it without first supplying an IConfiguration instance, ServiceComposer throws an ArgumentException. To enable it, pass the configuration object when registering ServiceComposer:
var builder = WebApplication.CreateBuilder();
builder.Services.AddViewModelComposition(builder.Configuration);
With that in place, a customization class can read any configuration section:
public class SalesCompositionOptionsCustomizationWithConfig : IViewModelCompositionOptionsCustomization
{
    public void Customize(ViewModelCompositionOptions options)
    {
        var section = options.Configuration.GetSection("Sales:Composition");
        // use section values to conditionally configure options
    }
}

Custom DI registration with AddServicesConfigurationHandler

By default, ServiceComposer registers every discovered composition handler with its own AddTransient call. AddServicesConfigurationHandler lets you override that behaviour for a specific handler type — for example, to register it as a singleton, supply constructor arguments, or use a factory method.
services.AddViewModelComposition(options =>
{
    options.AddServicesConfigurationHandler(
        typeof(MySpecialHandler),
        (type, serviceCollection) =>
        {
            serviceCollection.AddSingleton(type);
        });
});
Only one AddServicesConfigurationHandler can be registered per type. Registering a second handler for the same type throws a NotSupportedException.

Custom type filtering with AddTypesRegistrationHandler

AddTypesRegistrationHandler gives you full control over which types the scanner should pick up and how they should be registered. You supply a predicate that receives each candidate Type, and a registration callback that receives the matching types as a collection.
1

Define the type filter

Write a predicate that returns true for every type your handler should own:
Func<Type, bool> typesFilter = type =>
{
    var typeInfo = type.GetTypeInfo();
    return !typeInfo.IsInterface
           && !typeInfo.IsAbstract
           && typeof(IMyCustomContract).IsAssignableFrom(type);
};
2

Define the registration callback

Write the callback that registers the matched types into the DI container:
Action<IEnumerable<Type>> registrationHandler = types =>
{
    foreach (var type in types)
    {
        services.AddScoped(typeof(IMyCustomContract), type);
    }
};
3

Register the handler

Pass both delegates to AddTypesRegistrationHandler inside your IViewModelCompositionOptionsCustomization.Customize implementation or directly in AddViewModelComposition:
services.AddViewModelComposition(options =>
{
    options.AddTypesRegistrationHandler(typesFilter, registrationHandler);
});
ServiceComposer itself uses AddTypesRegistrationHandler internally to register ICompositionRequestsHandler, ICompositionEventsSubscriber, IViewModelPreviewHandler, and IViewModelFactory types. Your custom handlers run alongside these built-in registrations.

How discovery works

1

Host calls AddViewModelComposition()

The host application calls builder.Services.AddViewModelComposition(), optionally passing an IConfiguration instance.
2

Assembly scanner runs

ServiceComposer scans all loaded assemblies for types that implement IViewModelCompositionOptionsCustomization.
3

Customize is invoked

Each discovered type is instantiated (without DI) and its Customize(ViewModelCompositionOptions) method is called, allowing the library to configure assembly filters, DI overrides, or type registration handlers.
4

Application starts

After all customizations have run, the configured options are sealed and the application begins accepting requests.

Build docs developers (and LLMs) love