Skip to content

Registering with IServiceCollection

Wire handlers and behaviors into Microsoft.Extensions.DependencyInjection, and control their lifetimes.

3 min read

Immediate.Handlers targets Microsoft.Extensions.DependencyInjection.Abstractions directly. Two generated extension methods do all the work:

Program.cs
builder.Services.AddApplicationBehaviors();
builder.Services.AddApplicationHandlers();

AddApplicationHandlers registers every [Handler] class in the assembly. AddApplicationBehaviors registers every type referenced by any [Behaviors] attribute in the assembly — assembly-level, handler-level and bundle attributes alike.

The Application in the middle is the assembly identifier: by default the assembly name with ., and - removed, overridable with [assembly: ImmediateAssemblyIdentifier("...")]. See The assembly identifier for the derivation rules, and note that the same name is used by Immediate.Apis, Immediate.Cache and Immediate.Injections — changing it changes all of them at once.

Both methods live on a generated HandlerServiceCollectionExtensions class placed in your project’s root namespace.

Lifetimes

AddXxxHandlers takes an optional lifetime, which defaults to ServiceLifetime.Scoped:

Program.cs
builder.Services.AddApplicationHandlers(ServiceLifetime.Transient);

A single handler can opt out of the app-wide default by passing a lifetime to [Handler]:

GetConfigurationQuery.cs
[Handler(ServiceLifetime.Singleton)]
public static partial class GetConfigurationQuery
{
	// ..
}

The attribute wins. The generated registration code emits the literal lifetime for that handler and ignores whatever was passed to AddXxxHandlers.

What gets registered per handler

For a handler Application.GetUsersQuery with request Query and response IEnumerable<User>:

Service typeImplementation typeLifetime
GetUsersQuery.HandlerGetUsersQuery.Handleras configured
IHandler<GetUsersQuery.Query, IEnumerable<User>>GetUsersQuery.Handleras configured
GetUsersQuery.HandleBehaviorGetUsersQuery.HandleBehavioras configured
GetUsersQuery (sealed handlers only)GetUsersQueryas configured

Streaming handlers register IStreamingHandler<TRequest, TResponse> in place of IHandler<,>.

The container class in the last row is only registered for sealed instance handlers — a static handler has nothing to construct.

Behaviors are registered as their open generic definition, so typeof(LoggingBehavior<,>) is registered once and the container closes it per handler.

Registering a single handler

Each handler also gets its own static AddHandlers method, which is what AddXxxHandlers calls internally. It is public, so you can register one handler in isolation — useful in tests:

GetUsersQueryTests.cs
var services = new ServiceCollection();
services.AddSingleton<UsersService>();
GetUsersQuery.AddHandlers(services);

var handler = services.BuildServiceProvider()
	.GetRequiredService<GetUsersQuery.Handler>();

Note it is a plain static method rather than an extension method, and it does not register behaviors — if the handler’s pipeline includes any, register those types yourself.

Registering a subset

AddXxxHandlers also accepts tags, which let one assembly’s handlers be registered selectively per host. See Tagged registration.