Table of Contents

Class ServiceCollectionExtensions

Namespace
Trellis.Mediator
Assembly
Trellis.Mediator.dll

Extension methods for registering Trellis.Mediator pipeline behaviors.

public static class ServiceCollectionExtensions
Inheritance
ServiceCollectionExtensions
Inherited Members

Properties

PipelineBehaviors

Gets the ordered array of Trellis Result-aware pipeline behavior types contributed by this package. The pinned Mediator generator reads literal typeof expressions, not this property's value; use literal types for generator-configured pipelines.

The canonical Trellis pipeline (outermost to innermost) is:

  1. ExceptionBehavior<TMessage, TResponse> — catches unhandled exceptions and converts to typed failures.
  2. TracingBehavior<TMessage, TResponse> — emits an OpenTelemetry activity span around the message.
  3. LoggingBehavior<TMessage, TResponse> — structured logging with duration and outcome.
  4. AuthorizationContextBehavior<TMessage, TResponse> — establishes one authorization snapshot per dispatch.
  5. AuthorizationBehavior<TMessage, TResponse> — checks static permissions declared by IAuthorize.
  6. ResourceAuthorizationBehavior<TMessage, TResource, TResponse> — checks resource-bound authorization for IAuthorizeResource<TResource> commands. Inserted by AddResourceAuthorization<TMessage, TResource, TResponse>(IServiceCollection) or AddResourceAuthorization(IServiceCollection, params Assembly[]) immediately before the validation behavior so the loaded resource is checked once per request.
  7. ValidationBehavior<TMessage, TResponse> — unified validation stage. Runs Validate() when the message implements it AND every IMessageValidator<TMessage> registered in DI for the message, aggregating Error.InvalidInput failures into a single response. External validation sources (e.g., the optional Trellis.Mediator.FluentValidation package contributes FluentValidationMessageValidatorAdapter<TMessage> via AddTrellisFluentValidation()) plug in here without an extra pipeline behavior.
  8. Optional domain/tracked-aggregate event dispatch wraps the eventual command commit.
  9. TransactionalCommandBehavior<TMessage, TResponse> — runs the handler then calls IUnitOfWork.CommitAsync on success. Opt in via AddTrellisUnitOfWork<TContext>(); registration is order-independent versus AddTrellisBehaviors() so it lands innermost (closest to the handler).

This array contains the always-on behaviors (ExceptionBehavior<TMessage, TResponse>, TracingBehavior<TMessage, TResponse>, LoggingBehavior<TMessage, TResponse>, AuthorizationContextBehavior<TMessage, TResponse>, AuthorizationBehavior<TMessage, TResponse>, and ValidationBehavior<TMessage, TResponse>). The resource-authorization and transactional behaviors are opt-in and supplied by separate registration helpers. FluentValidation (and any other external validation source) participates inside the existing ValidationBehavior<TMessage, TResponse> via the IMessageValidator<TMessage> abstraction, so it does not occupy its own pipeline slot.

public static IReadOnlyList<Type> PipelineBehaviors { get; }

Property Value

IReadOnlyList<Type>

Examples

services.AddMediator(options =>
{
    options.Assemblies = [typeof(MyCommand).Assembly];
    options.ServiceLifetime = ServiceLifetime.Scoped;
});
services.AddTrellisBehaviors();

Methods

AddRelatedResourceAuthorization<TMessage, TLeaf, TOwner, TResponse>(IServiceCollection, ResolvedAuthorizationPath)

Explicitly registers indirect resource authorization with a fully-built ResolvedAuthorizationPath. Use this when the single-hop overload is insufficient (chains, plural-terminal fan-out, or composite shapes built by hand).

[UnconditionalSuppressMessage("Trimming", "IL2090:'this' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method.", Justification = "TMessage is annotated [DynamicallyAccessedMembers(Interfaces)] on the public surface; the suppression covers the call-site inside this method to GetInterfaces() reflectively.")]
public static IServiceCollection AddRelatedResourceAuthorization<TMessage, TLeaf, TOwner, TResponse>(this IServiceCollection services, ResolvedAuthorizationPath path) where TMessage : IAuthorizeResourceVia<TOwner>, IMessage where TLeaf : class where TResponse : IResult, IFailureFactory<TResponse>

Parameters

services IServiceCollection

The service collection.

path ResolvedAuthorizationPath

The resolved path. Must match the generic arguments on MessageType, LeafType, and OwnerType.

Returns

IServiceCollection

The service collection for chaining.

Type Parameters

TMessage

The command type.

TLeaf

The leaf resource type.

TOwner

The owner resource type.

TResponse

The response type returned by the message handler.

AddRelatedResourceAuthorization<TMessage, TLeaf, TLeafId, TOwner, TOwnerId, TResponse>(IServiceCollection, Func<TLeaf, TOwnerId?>)

Explicitly registers indirect (multi-hop) resource authorization for a single via-authorized command without assembly scanning. Use this in AOT / trimming scenarios where AddResourceAuthorization(IServiceCollection, params Assembly[]) cannot run. The single-hop arity covers the 80% case; consumers needing multi-hop chains can either provide a fully-built ResolvedAuthorizationPath via AddRelatedResourceAuthorization<TMessage, TLeaf, TOwner, TResponse>(IServiceCollection, ResolvedAuthorizationPath) or drop to IResourceLoader<TMessage, TResource> with a projection type.

public static IServiceCollection AddRelatedResourceAuthorization<TMessage, TLeaf, TLeafId, TOwner, TOwnerId, TResponse>(this IServiceCollection services, Func<TLeaf, TOwnerId?> extractOwnerId) where TMessage : IAuthorizeResourceVia<TOwner>, IIdentifyResource<TLeaf, TLeafId>, IMessage where TLeaf : class where TOwner : class where TOwnerId : notnull where TResponse : IResult, IFailureFactory<TResponse>

Parameters

services IServiceCollection

The service collection.

extractOwnerId Func<TLeaf, TOwnerId>

Delegate that, given a loaded TLeaf, returns the TOwnerId identifying the owner to authorize against. Returning null short-circuits authorization to Error.Forbidden.

Returns

IServiceCollection

The service collection for chaining.

Type Parameters

TMessage

The command type implementing IAuthorizeResourceVia<TOwner>.

TLeaf

The leaf resource type.

TLeafId

The leaf resource identifier type.

TOwner

The owner resource type authorization is evaluated against.

TOwnerId

The owner resource identifier type.

TResponse

The response type returned by the message handler.

Remarks

This overload assumes the consumer has already registered:

The behavior fails fast at request time if any of these are missing.

AddResourceAuthorization(IServiceCollection, Action<ResourceAuthorizationOptions>)

Configures the per-resource exposure policy for the resource-authorization pipeline (see ResourceAuthorizationOptions). Idempotent; repeated calls compose their configure delegates rather than overwriting. The IOptions<ResourceAuthorizationOptions> registration created here is also added implicitly by every other AddResourceAuthorization / AddRelatedResourceAuthorization overload, so the resource-authorization behaviors can always resolve options regardless of registration order.

public static IServiceCollection AddResourceAuthorization(this IServiceCollection services, Action<ResourceAuthorizationOptions> configure)

Parameters

services IServiceCollection

The service collection.

configure Action<ResourceAuthorizationOptions>

A delegate that configures the per-resource exposure policy.

Returns

IServiceCollection

The service collection for chaining.

AddResourceAuthorization(IServiceCollection, params Assembly[])

Scans the specified assembly for types implementing IAuthorizeResource<TResource> and automatically registers the ResourceAuthorizationBehavior<TMessage, TResource, TResponse> for each. Also scans and registers all IResourceLoader<TMessage, TResource> implementations and SharedResourceLoaderById<TResource, TId> implementations as scoped services.

[RequiresUnreferencedCode("Assembly scanning requires unreferenced types. Use explicit registration for AOT/trimming scenarios.")]
[RequiresDynamicCode("Constructs closed generic types at runtime. Use explicit registration for AOT scenarios.")]
public static IServiceCollection AddResourceAuthorization(this IServiceCollection services, params Assembly[] assemblies)

Parameters

services IServiceCollection

The service collection.

assemblies Assembly[]

The assemblies to scan. Pass both the Application assembly (containing IAuthorizeResource<TResource> commands) and the Acl assembly (containing IResourceLoader<TMessage, TResource> implementations).

Returns

IServiceCollection

The service collection for chaining.

Examples

// Scans both Application (commands) and Acl (loaders) assemblies
services.AddResourceAuthorization(
    typeof(CancelOrderCommand).Assembly,
    typeof(CancelOrderResourceLoader).Assembly);

Remarks

Despite the feature name (resource authorization), this extension lives in the Trellis.Mediator namespace — not Trellis.Authorization — because the resource is loaded by a Mediator pipeline behavior. Add using Trellis.Mediator; in your composition root (e.g., DependencyInjection.cs) to bring this method into scope.

For each concrete type that implements IAuthorizeResource<TResource>, the method extracts TResource and resolves TResponse from ICommand<TResponse>, IQuery<TResponse>, or IRequest<TResponse>. It then registers the closed-generic ResourceAuthorizationBehavior<TMessage, TResource, TResponse> as IPipelineBehavior<TMessage, TResponse>.

Closed resource-authorization behavior registrations are idempotent across repeated scans and explicit-plus-scanned overlap when both service type and implementation type match.

This method also scans for IResourceLoader<TMessage, TResource> implementations and registers them as scoped services, so you don't need to call AddResourceLoaders(IServiceCollection, Assembly) separately.

When a command implements IIdentifyResource<TResource, TId> and no explicit IResourceLoader<TMessage, TResource> is found, a Trellis.Mediator.SharedResourceLoaderAdapter<TMessage, TResource, TId> is automatically registered, bridging to the SharedResourceLoaderById<TResource, TId>. Explicit loaders always take priority. Scanned custom loaders replace framework fallback adapters even when the adapters were registered first. Application-provided implementation, factory, and instance registrations are preserved, as are keyed registrations.

AddResourceAuthorization<TMessage, TResource, TResponse>(IServiceCollection)

Registers the ResourceAuthorizationBehavior<TMessage, TResource, TResponse> for a specific command/resource pair. Duplicate registrations of the same closed behavior are ignored.

[UnconditionalSuppressMessage("Trimming", "IL2090:'this' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method.", Justification = "TMessage is annotated [DynamicallyAccessedMembers(Interfaces)] on the public surface; the suppression covers the call-site inside this method to GetInterfaces() reflectively.")]
public static IServiceCollection AddResourceAuthorization<TMessage, TResource, TResponse>(this IServiceCollection services) where TMessage : IAuthorizeResource<TResource>, IMessage where TResource : class where TResponse : IResult, IFailureFactory<TResponse>

Parameters

services IServiceCollection

The service collection.

Returns

IServiceCollection

The service collection for chaining.

Type Parameters

TMessage

The command or query type that implements IAuthorizeResource<TResource>.

TResource

The resource type loaded for authorization.

TResponse

The response type (e.g., Result<Order>). Must implement IResult and IFailureFactory<TSelf>.

Examples

services.AddResourceAuthorization<CancelOrderCommand, Order, Result<Order>>();
services.AddScoped<IResourceLoader<CancelOrderCommand, Order>, CancelOrderResourceLoader>();

Remarks

Despite the feature name (resource authorization), this extension lives in the Trellis.Mediator namespace — not Trellis.Authorization — because the resource is loaded by a Mediator pipeline behavior. Add using Trellis.Mediator; in your composition root (e.g., DependencyInjection.cs) to bring this method into scope.

Prefer AddResourceAuthorization(IServiceCollection, params Assembly[]) for automatic discovery. Use this explicit overload for AOT/trimming scenarios where assembly scanning is not available.

Repeated explicit or scanned registration of the same closed IPipelineBehavior<TMessage, TResponse> implementation is idempotent. Distinct closed generic behaviors, including different response types, remain distinct.

Also register the corresponding IResourceLoader<TMessage, TResource> as scoped, either explicitly or via AddResourceLoaders(IServiceCollection, Assembly). For shared-loader registration, use AddSharedResourceAuthorization<TMessage, TResource, TId, TResponse>(IServiceCollection) to include the adapter with the behavior and accessor.

AddResourceLoaders(IServiceCollection, Assembly)

Scans the specified assembly for types implementing IResourceLoader<TMessage, TResource> and registers them as scoped services.

[RequiresUnreferencedCode("Assembly scanning requires unreferenced types. Use explicit registration for AOT/trimming scenarios.")]
public static IServiceCollection AddResourceLoaders(this IServiceCollection services, Assembly assembly)

Parameters

services IServiceCollection

The service collection.

assembly Assembly

The assembly to scan for resource loader implementations.

Returns

IServiceCollection

The service collection for chaining.

Examples

services.AddResourceLoaders(typeof(CancelOrderResourceLoader).Assembly);

Remarks

Discovered loaders replace only unkeyed framework shared-loader adapters. Existing application-provided implementations, factories, instances, and keyed registrations are preserved.

AddSharedResourceAuthorization<TMessage, TResource, TId, TResponse>(IServiceCollection)

Registers resource authorization and the shared-loader adapter for a specific message, without assembly scanning. Also registers the authorized-resource accessor.

public static IServiceCollection AddSharedResourceAuthorization<TMessage, TResource, TId, TResponse>(this IServiceCollection services) where TMessage : IAuthorizeResource<TResource>, IIdentifyResource<TResource, TId>, IMessage where TResource : class where TResponse : IResult, IFailureFactory<TResponse>

Parameters

services IServiceCollection

The service collection.

Returns

IServiceCollection

The service collection for chaining.

Type Parameters

TMessage

The message that authorizes and identifies the resource.

TResource

The resource type loaded for authorization.

TId

The resource identifier type.

TResponse

The message's result response type.

Remarks

Register the SharedResourceLoaderById<TResource, TId> implementation separately. An existing IResourceLoader<TMessage, TResource> registration is preserved. A later loader scan may replace the framework adapter with a discovered custom loader. Repeated registrations are idempotent and retain the canonical pipeline order. This composes AddResourceAuthorization<TMessage, TResource, TResponse>(IServiceCollection) and AddSharedResourceLoader<TMessage, TResource, TId>(IServiceCollection); it does not register an actor provider, Mediator handlers, or the standard Trellis behaviors.

Exceptions

ArgumentNullException

Thrown when services is null.

InvalidOperationException

Thrown when the message also implements IAuthorizeResourceVia<TOwner>.

AddSharedResourceLoader<TMessage, TResource, TId>(IServiceCollection)

Registers a Trellis.Mediator.SharedResourceLoaderAdapter<TMessage, TResource, TId> for a specific command, bridging to a SharedResourceLoaderById<TResource, TId>. Use this for AOT/trimming scenarios where assembly scanning is not available.

public static IServiceCollection AddSharedResourceLoader<TMessage, TResource, TId>(this IServiceCollection services) where TMessage : IIdentifyResource<TResource, TId>

Parameters

services IServiceCollection

The service collection.

Returns

IServiceCollection

The service collection for chaining.

Type Parameters

TMessage

The command type implementing IIdentifyResource<TResource, TId>. The constraint is intentionally limited to IIdentifyResource<TResource, TId> only — both IAuthorizeResource<TResource> commands (where TResource is the resource the command authorizes against) and IAuthorizeResourceVia<TOwner> via-commands (where TResource is the via-command's leaf resource type) reuse this bridge.

TResource

The resource type.

TId

The identifier type.

Examples

services.AddScoped<SharedResourceLoaderById<Order, OrderId>, OrderResourceLoader>();
services.AddSharedResourceLoader<CancelOrderCommand, Order, OrderId>();
services.AddSharedResourceLoader<ReturnOrderCommand, Order, OrderId>();

Remarks

The SharedResourceLoaderById<TResource, TId> implementation must be registered separately (e.g., services.AddScoped<SharedResourceLoaderById<Order, OrderId>, OrderResourceLoader>()).

AddTrellisBehaviors(IServiceCollection)

Registers Trellis Result-aware pipeline behaviors as open generic Mediator.IPipelineBehavior<TMessage, TResponse> implementations. Use this when NOT configuring the standard behaviors through MediatorOptions.PipelineBehaviors.

public static IServiceCollection AddTrellisBehaviors(this IServiceCollection services)

Parameters

services IServiceCollection

The service collection.

Returns

IServiceCollection

The service collection for chaining.

Remarks

Idempotent: calling this method more than once registers each behavior exactly once, so an extension method that defensively calls it as a precondition will not produce duplicate pipeline entries when the consumer also calls it explicitly. Native AOT hosts using value-type Result responses require literal source-generator configuration that emits closed behaviors instead of this open-generic registration.

AddTrellisBehaviors(IServiceCollection, Action<TrellisMediatorTelemetryOptions>)

Registers Trellis pipeline behaviors and configures TrellisMediatorTelemetryOptions for logging/tracing redaction. Equivalent to calling AddTrellisBehaviors(IServiceCollection) followed by mutating the resolved singleton via configure.

public static IServiceCollection AddTrellisBehaviors(this IServiceCollection services, Action<TrellisMediatorTelemetryOptions> configure)

Parameters

services IServiceCollection

The service collection.

configure Action<TrellisMediatorTelemetryOptions>

Callback invoked with the singleton telemetry options instance. Use this to opt in to including Detail in log messages and activity descriptions.

Returns

IServiceCollection

The service collection for chaining.