Class ServiceCollectionExtensions
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:
- ExceptionBehavior<TMessage, TResponse> — catches unhandled exceptions and converts to typed failures.
- TracingBehavior<TMessage, TResponse> — emits an OpenTelemetry activity span around the message.
- LoggingBehavior<TMessage, TResponse> — structured logging with duration and outcome.
- AuthorizationContextBehavior<TMessage, TResponse> — establishes one authorization snapshot per dispatch.
- AuthorizationBehavior<TMessage, TResponse> — checks static permissions declared by IAuthorize.
- 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.
- 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.FluentValidationpackage contributesFluentValidationMessageValidatorAdapter<TMessage>viaAddTrellisFluentValidation()) plug in here without an extra pipeline behavior. - Optional domain/tracked-aggregate event dispatch wraps the eventual command commit.
- TransactionalCommandBehavior<TMessage, TResponse>
— runs the handler then
calls
IUnitOfWork.CommitAsyncon success. Opt in viaAddTrellisUnitOfWork<TContext>(); registration is order-independent versusAddTrellisBehaviors()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
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
servicesIServiceCollectionThe service collection.
pathResolvedAuthorizationPathThe resolved path. Must match the generic arguments on MessageType, LeafType, and OwnerType.
Returns
- IServiceCollection
The service collection for chaining.
Type Parameters
TMessageThe command type.
TLeafThe leaf resource type.
TOwnerThe owner resource type.
TResponseThe 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
servicesIServiceCollectionThe service collection.
extractOwnerIdFunc<TLeaf, TOwnerId>Delegate that, given a loaded
TLeaf, returns theTOwnerIdidentifying the owner to authorize against. Returningnullshort-circuits authorization to Error.Forbidden.
Returns
- IServiceCollection
The service collection for chaining.
Type Parameters
TMessageThe command type implementing IAuthorizeResourceVia<TOwner>.
TLeafThe leaf resource type.
TLeafIdThe leaf resource identifier type.
TOwnerThe owner resource type authorization is evaluated against.
TOwnerIdThe owner resource identifier type.
TResponseThe response type returned by the message handler.
Remarks
This overload assumes the consumer has already registered:
- SharedResourceLoaderById<TResource, TId> for the leaf.
- SharedResourceLoaderById<TResource, TId> for the owner.
- An IResourceLoader<TMessage, TResource> for the command (typically via AddSharedResourceLoader<TMessage, TResource, TId>(IServiceCollection)).
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
servicesIServiceCollectionThe service collection.
configureAction<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
servicesIServiceCollectionThe service collection.
assembliesAssembly[]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
servicesIServiceCollectionThe service collection.
Returns
- IServiceCollection
The service collection for chaining.
Type Parameters
TMessageThe command or query type that implements IAuthorizeResource<TResource>.
TResourceThe resource type loaded for authorization.
TResponseThe 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
servicesIServiceCollectionThe service collection.
assemblyAssemblyThe 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
servicesIServiceCollectionThe service collection.
Returns
- IServiceCollection
The service collection for chaining.
Type Parameters
TMessageThe message that authorizes and identifies the resource.
TResourceThe resource type loaded for authorization.
TIdThe resource identifier type.
TResponseThe 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
servicesis 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
servicesIServiceCollectionThe service collection.
Returns
- IServiceCollection
The service collection for chaining.
Type Parameters
TMessageThe command type implementing IIdentifyResource<TResource, TId>. The constraint is intentionally limited to IIdentifyResource<TResource, TId> only — both IAuthorizeResource<TResource> commands (where
TResourceis the resource the command authorizes against) and IAuthorizeResourceVia<TOwner> via-commands (whereTResourceis the via-command's leaf resource type) reuse this bridge.TResourceThe resource type.
TIdThe 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
servicesIServiceCollectionThe 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
servicesIServiceCollectionThe service collection.
configureAction<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.