Skip to main content
Table of Contents

DI Extensions Cheatsheet

Derived page. The behaviour described here is specified by the host-composition and event-sourcing-store capabilities under openspec/specs/. Those specifications are the source; this page explains and illustrates them. Where the two disagree, the specification is right and this page is a bug.

The full menu of Add*Services() extensions Stratara exposes, by package.

Umbrella extensions (IHostApplicationBuilder)

These wire entire worker / host concerns in one call. Pick one per host.

Extension Brings Use for
builder.AddBackendServices() Mediator, Identity, Session, Security, Resilience ASP.NET API hosts
builder.AddCommandWorkerServices() Common framework + command-handling worker (interactive lane) Worker hosts that consume the command topic
builder.AddHeavyCommandWorkerServices(dop?) Common framework + dedicated heavy-command worker Worker hosts that drain long-running IHeavyCommand commands on a separate lane, so they don't starve interactive commands
builder.AddEventProjectionWorkerServices() Common framework + projection worker Worker hosts that update read-models
builder.AddSagaWorkerServices() Common framework + saga worker Worker hosts that orchestrate processes
builder.AddEventProjectionServices() The projection worker stack without the bus-fed worker; the replay worker stays Hosts whose projections read the store, such as the Orleans execution model
builder.AddSagaServices() The saga worker stack without the bus-fed worker Hosts whose sagas read the store, such as the Orleans execution model
builder.AddEventStreamHashWorkerServices() Common framework + event-stream-hash worker Worker hosts that hash event streams for tamper-evidence
builder.AddOutboxWorkerServices() Common framework + outbox-drain worker Worker hosts that publish from outbox_entry to the bus

AddCommonFrameworkServices() is called transitively by every worker / backend extension above — you don't call it yourself.

À la carte (IServiceCollection)

What the umbrellas compose. Reach for these when a host needs one concern and not the rest — a tool that dispatches commands but runs no worker, a test host, a migration runner.

Extension What it does
services.AddMediator() IMediator as a scoped service
services.AddEventSourcing() The core event-sourcing services (event source, aggregation, snapshots) as scoped, plus the default trusted-type resolver
services.AddMapping() The mapper the event-sourcing stack uses to materialize typed events from persisted rows
services.AddSessionContext() The scoped session context and its accessor. Pair with app.UseMiddleware<SessionContextMiddleware>() in an ASP.NET host
services.AddIdentity() The scoped identity accessors that resolve from the ambient session context
services.AddBackgroundTasks() The in-process IBackgroundTaskQueue and its hosted service: capacity 100 pending items (queuing waits when full), each item in its own scope, status kept for the most recent 10 000 items. See Queue Background Work
services.AddOutboxDispatcher() ICommandOutboxDispatcher + IEventBundleOutboxDispatcher (scoped) and the bus they publish through; binds Outbox (for Outbox:DurableBundles) when the host carries a configuration
services.AddAuthorizingCommandOutboxDispatcher() Wraps the dispatcher so [RequireRole] / [RequirePermission] are enforced on the outbox path too, keeping the inner dispatcher resolvable
services.AddPipelineBehaviorWithResult<T>() Registers an open-generic pipeline behaviour for the result-returning request shape
services.AddTrustedTypeResolver() The default ITrustedTypeResolver if none is registered. Idempotent
services.AddTrustedType<T>() Adds one type to the trusted-type allowlist — for types produced but never handled, such as a snapshot type no projection or saga anchors
services.AddEventUpcaster<T>() Registers an IEventUpcaster and ensures the pipeline exists
services.AddEventUpcasterPipeline() The default upcaster pipeline if none is registered. Idempotent; every AddEventUpcaster overload calls it

Workers without the composite

The hosted services the Add*WorkerServices() umbrellas wire. Register one directly when the host already has the framework services and needs a second lane.

Extension What it runs
services.AddMediatorWorker() The interactive command worker — subscribes to the command topic, restores the session context, dispatches through IMediator
services.AddHeavyCommandWorker(dop?) The heavy-command lane, draining the heavy-command topic so IHeavyCommand work cannot starve the interactive lane
services.AddOutboxWorker(configuration) The outbox-drain hosted service; binds OutboxOptions from configuration
services.AddProjectionWorker(configuration) The projection runtime and its hosted service; binds ProjectionOptions
services.AddSagaWorker(configuration) The saga runtime and its hosted service; binds SagaOptions
services.AddProjectionHandling(configuration) The projection runtime and the replay worker, without the bus-fed worker; binds ProjectionOptions
services.AddSagaHandling(configuration) The saga runtime without its hosted service; binds SagaOptions
services.AddEventStreamHashWorker() The event-stream hashing worker and the anchor services behind it

Domain registration (IServiceCollection)

These tell Stratara what to dispatch / project / saga. Call once per assembly that contains the relevant types.

Extension Discovers Side-effect
services.AddCommandHandlersFromAssemblyContaining<T>() ICommandHandler<TCmd> + IQueryHandler<TCmd, TResult> (the unified contract) Per-handler AddScoped
services.AddQueryHandlersFromAssemblyContaining<T>() IQueryHandler<TQuery, TResult> Per-handler AddScoped
services.AddProjectionsFromAssemblyContaining<T>() IProjection impls + their HandleAsync(SomeEvent) overloads Per-projection AddScoped<IProjection> — a fresh instance per bundle scope — + event-allowlist registration
services.AddSagasFromAssemblyContaining<T>() ISaga impls + their HandleAsync(SomeEvent) overloads Per-saga AddScoped<ISaga> — a fresh instance per bundle scope — + event-allowlist registration
services.AddAggregatesFromAssemblyContaining<T>() IAggregate impls + their Apply(SomeEvent) methods Adds each aggregate and each apply-target event type to ITrustedTypeResolver
services.AddDomainEventTypesFromAssemblyContaining<T>() The Apply(SomeEvent) parameter types of the assembly's aggregates Adds only those event types to ITrustedTypeResolver — no aggregate types, no handler classes. For event-only hosts (projection/saga workers) that must deserialize bus/stream payloads without wiring handler dependencies

Security + integrity

Extension What it does
services.AddStrataraFileKeyStore(configuration) Registers the production file-backed EnvelopeFileKeyStore (KEK-wrapped, versioned per-KeyScope DEKs) + FileMasterKeyProvider + the AES-GCM ISecureBlobEncryptor. Lives in Stratara.Security (dependency-light). Call before AddSecurity() so it wins the TryAdd race.
services.AddSecurity() Wires ISecureJsonSerializer ([EncryptData]), the AES-GCM blob encryptor, and a Development-only DummyKeyStore fallback (TryAdd, so a real IKeyStore registered first wins). Adds the KeyStoreStartupProbe fail-fast guard.
services.AddBusEnvelopeIntegrity(opts) Opt-in HMAC signing of CommandEnvelope + EventBundle
services.AddStrataraBlobEncryption() The AES-GCM blob encryptor and its factory on their own, without the rest of AddSecurity()
services.AddStrataraErasure() Composes the membership, API-key, setting and key-material sweeps into one erasure operation. Registers no store of its own — the four it sweeps must already be registered

Validation

Extension What it does
services.AddStrataraValidation() Registers the validation pipeline behavior. Call before other AddPipelineBehavior* so it runs outermost.
services.AddValidatorsFromAssemblyContaining<T>() Discovers + registers every concrete IValidator<T> in the marker's assembly as scoped.

Tenant isolation

Extension What it does
services.AddStrataraTenantIsolation() Registers the tenant-isolation pipeline behavior. Acts only on requests implementing ITenantScopedRequest; rejects a request whose TenantId ≠ the session's data-owner tenant with TenantAccessDeniedException (→ HTTP 403). Call after AddStrataraValidation().
services.AddStrataraTenantIsolation(o => o.Mode = TenantIsolationMode.Strict) Strict mode — additionally routes every cross-tenant operation (actor tenant ≠ data-owner tenant) through ICrossTenantAuthorizer. The shipped default denies all; register your own ICrossTenantAuthorizer to grant the cross-tenant case (e.g. a platform admin).

Resilience

Extension What it does
services.AddResiliencePipelines() Registers the six Polly named pipelines — ResilienceNames.MessageBus, .CommandDispatcher, .EventBundleDispatcher, .ConcurrencyConflict, .PrecedingFact, .ProjectionReplayBatch
services.AddStrataraResilienceBehavior() Mediator behavior that dispatches IResilientRequest through its chosen pipeline

Use the ResilienceNames constants rather than the literal pipeline strings.

Outbox transport (pick one per host)

Extension Bus
builder.AddMessaging() RabbitMQ — extends IHostApplicationBuilder, and is what the worker composites call. Binds Messaging, BusEnvelopeJson and MessageRetry (the redelivery bounds, validated at start-up)
services.AddAzureServiceBus(connectionString) Azure Service Bus (connection-string)
services.AddAzureServiceBusWithManagedIdentity(...) Azure Service Bus (DefaultAzureCredential)

One transport per host — the explicit one wins. AddMessaging() registers IMessageBus for RabbitMQ; the Azure Service Bus extensions replace it, so an explicit AddAzureServiceBus takes effect even after a worker composite wired the RabbitMQ umbrella. Order no longer decides the transport, but registering both in one host is still a smell — pick one.

Write store + database contexts (Stratara.EventSourcing.EntityFrameworkCore)

Extension What it does
services.AddNpgsqlWriteDbContextFactory<TContext>() Npgsql-backed IDbContextFactory<TContext> for the write-store context, plus a scoped IWriteUnitOfWork over it unless the host registered its own, plus the IStoreConflictDetector that makes a PostgreSQL unique violation a ConcurrencyException. The unit of work also needs ISessionContextProvider and ISecureJsonSerializer from AddSessionContext() / AddSecurity(), which every worker composite applies. A host on another provider registers its own IStoreConflictDetector; detectors accumulate
services.AddNpgsqlReadDbContextFactory<TContext>() The same for a read-store context, plus a scoped IProjectionsUnitOfWork / IReadUnitOfWork over it unless the host registered its own
services.AddNpgsqlIdentityDbContextFactory<TContext>() The same for an identity-store context, plus a scoped resolution of the context itself so ASP.NET Identity can inject it directly
services.AddWriteStore(configuration) Binds EventSourcingOptions from the EventSourcing section, which carries no settings today. Snapshot cadence is the registered ISnapshotStrategyVersionThresholdSnapshotStrategy (every 50 events) unless you register another
services.AddCommandAuditing() CommandAuditBehavior for both command shapes — persists an audit row per dispatched command; queries pass through (Stratara.EventSourcing.Pipeline.CommandAudit)

The schema comes with the context. A context derived from WriteDbContext<TContext> carries the store's tables and their constraints, so a migration generated from it makes the database refuse a second event at the same stream version (unique over bucket_id, stream_id, version on event_stream_entry, and the same on snapshot) and a second integrity anchor at the same sequence number (unique over bucket_id, sequence_number on event_chain_anchor). The framework's write, read and identity contexts share one assembly and each filters ApplyConfigurationsFromAssembly by namespace; a context of yours that shares an assembly with its siblings needs the same predicate, or its model drifts from its migrations unnoticed until it meets a real database. See The store declares its own schema.

Outbox coordination + projection replay (Stratara.Outbox.RabbitMQ)

Extension What it does
services.AddRedisOutboxLock() Replaces the no-op NullOutboxLock with the Redis-backed one, which is what makes more than one outbox-worker replica safe. Needs an IConnectionMultiplexerAddCaching() from Stratara.Infrastructure registers one. Lease it via OutboxOptions.LockLeaseSeconds
services.AddProjectionReplayState() Registers the projection-replay state — shared over Redis where an IConnectionMultiplexer is registered, held in process otherwise (warning 104_012) — and ProjectionReplayOptions with its defaults, so the replay marking is leased (LeaseSeconds, default 300) rather than outliving a crashed replay. Idempotent

Orleans execution model (Stratara.Orleans, Stratara.Orleans.EntityFrameworkCore)

Each role is adopted with one call after the composite the host already has; the host's silo is registered with UseOrleans. See Choose an Execution Model.

Extension What it does
silo.AddStrataraOrleans((s, name) => …) Extends ISiloBuilder. Registers the storage-backed grain directory the model's single-activation grains use, under the name it passes, and publishes the silo's singleton work in its metadata. A silo that runs the model's grains without it fails at start naming this call
services.AddStrataraAggregateGrains() Runs every command that names an aggregate in that aggregate's grain. Register it after every other pipeline behaviour
services.AddStrataraOrleansCommandDispatcher(opts?) Replaces the undecorated ICommandOutboxDispatcher with the durable-intent one: a command is recorded before the call returns and resumed after a crash, a bounded number of times. Composes with AddAuthorizingCommandOutboxDispatcher() in either order. Needs an intent store
services.AddStrataraIntentStore<TWriteContext>() The ICommandIntentStore in the write context's outbox table. The dispatcher's host fails at start without an intent store
services.ConfigureStrataraHeavyWork(o => …) The cluster-wide limit, the permit retry and the permit lease of heavy work
services.AddStrataraProjectionGrains(opts?, hybrid?) Runs every projection in grains that read the store in commit order from a checkpoint. Call after builder.AddEventProjectionServices(); register the host's IProjectionViewTruncator before it
services.AddStrataraSagaGrains(opts?, hybrid?) Runs every saga, and every stateful process, in grains that read the store. Call after builder.AddSagaServices()
services.AddStrataraProjectionCheckpoints<TReadContext>() Keeps the store readers' checkpoints in the read context, keyed by consumer and partition. Deployments sharing a read store need distinct projection names, and at most one of them runs saga grains
services.AddStrataraPortableCounterReader<TWriteContext>() The commit-order reader for any relational provider, and a start check that refuses a store holding an entry without a position. The write context adds PartitionCounterInterceptor itself, and a store with existing entries runs PartitionCounterBackfill.RunAsync once before the first start
services.AddStrataraDurableTimers(opts?) IDurableTimers over the silo's reminder service. The host supplies one ITimerOwners and one ITimerHandler, before or after this call
services.AddStrataraSingletonWork<TWork>(opts?) Runs an ISingletonWork once per cluster at its period, only on silos that registered it
services.AddStrataraExecutionModelReset<TReadContext>(runtimeConnectionString, clearDirectory) IExecutionModelReset: clears the reminders and membership of the host's deployment, the checkpoints of the projections and sagas it registers, and the grain directory through the host's callback. Run it while no silo of the cluster runs

Every setting these calls bind is validated when the host starts, and an invalid one fails the start naming itself.

Observability (Stratara.ServiceDefaults)

Extension What it does
builder.ConfigureOpenTelemetry() OpenTelemetry logging, metrics and tracing with the default instrumentation (HTTP client, EF Core, RabbitMQ, runtime); wires the OTLP exporter when OTEL_EXPORTER_OTLP_ENDPOINT is set
builder.ConfigureAspNetOpenTelemetry() Adds ASP.NET Core request instrumentation on top, filtering /health and /alive out of tracing
builder.ConfigureSerilog() Serilog as the host's logging provider with Stratara's defaults (destructuring attributes, async console sink, OTLP sink when configured), reading the Serilog configuration section

Test support (Stratara.Testing.EntityFrameworkCore)

Extension What it does
services.AddStrataraTestingEventStore() The event-sourcing write stack over an in-memory store, plus a test key store, encryptor and session context, plus the SQLite IStoreConflictDetector so a duplicate stream version is a ConcurrencyException in tests as it is on PostgreSQL. Register your aggregates with AddAggregatesFromAssemblyContaining<T>() so event payloads resolve

Health checks

Extension What it does
builder.AddDefaultHealthChecks() Baseline self check, tagged live — surfaces on both /health and /alive once MapDefaultEndpoints() is called
healthChecks.AddEventStoreHealthCheck(...) Verifies the write-side database is reachable. Needs the write store registered
healthChecks.AddOutboxHealthCheck(...) Reports depth and age of the outbox backlog, degrading above a pending-entry threshold you pass in

Identity directory (Stratara.Identity.EntityFrameworkCore)

TContext is any DbContext whose model includes the directory tables — derive from IdentityDirectoryDbContext<TContext> or call modelBuilder.ApplyIdentityDirectoryModel() in your own OnModelCreating.

Extension What it does
services.AddTenantMembershipStore<TContext>() EF ITenantMembershipStore (tenant_membership, active_tenant) — shares the request's context
services.AddTenantMembershipStoreFromContextFactory<TContext>() Same store, a fresh context per operation (needs AddDbContextFactory<TContext>())
services.AddMembershipAuthorization() IAuthorizationProvider over tenant-scoped membership roles
services.AddMembershipAuthorization<TUser>() Above ∪ global ASP.NET Identity roles
services.AddMembershipCrossTenantAuthorizer(opts?) ICrossTenantAuthorizer for strict tenant isolation (membership OR a configured platform role)
services.AddPermissionCatalog(c => …) Declares the permission vocabulary + role grants (throws on an undeclared grant)
services.AddCatalogPermissionResolver() IPermissionResolver — membership roles through the catalog
services.AddCatalogPermissionResolver<TUser>() Above ∪ global ASP.NET Identity roles
services.AddSettingCatalog(c => …) Declares the setting vocabulary (defaults, IsInherited, IsEncrypted)
services.AddSettingStore<TContext>() EF ISettingStore (setting_entry) + the ISettingProvider fallback facade
services.AddSettingStoreFromContextFactory<TContext>() Same pair, a fresh context per operation
services.AddApiKeyStore<TContext>() EF IApiKeyStore (api_key) — issue / import / validate / revoke / sweep
services.AddApiKeyStoreFromContextFactory<TContext>() Same store, a fresh context per operation

The …FromContextFactory variants exist because the plain registrations share one context across every directory store in a request: a database context serves one operation at a time, and a store's commit also commits whatever you have left unsaved on that context. A context per operation removes both, and in exchange a store write no longer joins a transaction you opened on your own scoped context. Calling both variants for the same store leaves whichever ran first in place. See Tenant Membership.

[RequirePermission] is only enforced when the host also registers an authorizing mediator (services.AddAuthorizingMediator<MembershipAuthorizationProvider>()). Without it — or without an IPermissionResolver — the mediator's startup validator throws rather than let a guarded request through unchecked.

ASP.NET specific

Extension What it does
builder.AddAspNetIdentity<TUser, TIdentityDbContext>() Channel-agnostic ASP.NET Core identity wiring (password/schema-v3/passkey defaults — no lockout)
builder.AddAspNetIdentityWithSignInManager<TUser, TIdentityDbContext>() Above + lockout defaults + IStrataraSignInManager wrapper + localization
builder.AddDevelopmentNoOpEmailSender<TUser>() Stub IEmailSender for Development — throws on every other environment name, including Staging (3.4.0; Production-only before)
services.AddMembershipTenantClaim<TUser>() Stamps stratara:tenant_id into every issued principal (claims-factory decorator)
services.AddMembershipTenantClaimsTransformation() Resolves stratara:tenant_id live per request — a tenant switch applies without re-issuing the sign-in
services.AddStrataraPermissionPolicies() Turns every catalog permission into an on-demand policy → [Authorize("sims.read")]
services.AddStrataraExternalLoginProvisioning<TUser>(opts?) JIT create/link of the local account on first external sign-in (fail-closed)
services.AddStrataraProblemDetails() Turns a validation rejection into a 400 with the failures grouped by field, and an authorization or tenant-access refusal into a 403 — one shape for all three
app.MapDefaultEndpoints() /health + /alive endpoints (Stratara.ServiceDefaults.AspNetCore)

Authentication schemes (AuthenticationBuilder)

Extension Scheme
.AddStrataraOpenIdConnect(configuration) Interactive external login, from Identity:OpenIdConnect
.AddStrataraJwtBearer(configuration) API access tokens, from Identity:JwtBearer (multi-issuer by iss)
.AddStrataraApiKey(opts?) StrataraApiKeyX-Api-Key header (opt-in query parameter)
.AddStrataraAuthSchemeSelector(opts?) Policy scheme routing by request shape: API key → Bearer → cookie

The builder.Add* identity rows are extension members of IHostApplicationBuilder in the Microsoft.Extensions.Hosting namespace (Microsoft convention since v3.0.15). The services.Add* rows and the authentication-scheme extensions above live in Microsoft.Extensions.DependencyInjection.