DKNet ships no AddDKNet() and no DKNetOptions. Every package registers itself, and where it needs
configuration it exposes its own options type. This page is about how those independent registrations compose:
which conventions they follow, what is bound from IConfiguration and what is not, what has to be registered
before what, and which page owns each package's own option table.
For a single package's exhaustive option list, follow the link in the last table on this page. Nothing here duplicates those tables.
- The four registration conventions
- Where each extension method lives
- What is bound from IConfiguration
- Registration order that matters
- A worked Program.cs
- Environment and secrets
- Test configuration
- Per-package option tables
Every DKNet registration falls into one of four shapes. Recognising which one a package uses tells you where its configuration comes from without reading its page.
| Convention | How you configure it | Packages |
|---|---|---|
| Bound from a config section | pass IConfiguration; the package binds its own named section |
the three blob adapters — AddAzureStorageAdapter, AddS3BlobService, AddLocalDirectoryBlobService |
| Configured by a delegate | pass Action<TOptions> |
AddIdempotentKey (IdempotencyOptions), AddTransformerService (TransformOptions), AddPdfGenerator, and the Action<AzureStorageOptions> overload of AddAzureStorageAdapter |
| Configured by a required argument | pass the value itself | AddAesGcmEncryption(base64Key), AddRsaEncryption(privateKeyBase64), AddIdempotencyWithMsSqlStore(connectionString) and its Npgsql/Redis siblings |
| Configured by a type you supply | pass a type parameter DKNet resolves from DI | AddEfCoreEncryption<TKeyProvider>, AddDataOwnerProvider<TDbContext, TProvider>, AddCurrentUserProvider<TDbContext, TProvider>, AddEventPublisher<TDbContext, TImpl>, AddEfCoreAuditLogs<TDbContext, TPublisher>, AddIdempotentKey<TStore>, AddBackgroundJob<TJob> |
Four calls need no configuration at all — AddSpecRepo<TDbContext>(), AddEncryptionServices() (which
registers only IShaHashing and IHmacHashing), AddSlimBusEfCoreInterceptor<T>() /
AddSlimBusEventPublisher<T>() (nothing but a type parameter), and AddContextualRequestPopulation(): an unresolved declared member always holds its type's default, and a
host that wants a value of its own registers a custom IContextualValueResolver ahead of the built-in ones
instead of configuring an options type.
One extension is deliberately not on IServiceCollection: UseAutoConfigModel<TContext>() is a
DbContextOptionsBuilder<TContext> extension. It configures the model, so it belongs inside the
AddDbContext/AddDbContextWithHook callback — never in OnModelCreating, where the required
DbContextOptionsBuilder does not exist.
Several DKNet extensions are declared in Microsoft.* namespaces and need no using beyond what an ASP.NET Core
project already has; others need their own. This is the single most common "why does this not compile" cause.
| Method | Namespace to import |
|---|---|
UseAutoConfigModel<TContext>() |
Microsoft.EntityFrameworkCore |
AddEventPublisher<TDbContext, TImpl>() |
Microsoft.Extensions.DependencyInjection |
AddSlimBusForMediator(...), AddSlimBusForEfCore<T>(...), AddSlimBusEfCoreInterceptor<T>(), AddSlimBusEventPublisher<T>() |
Microsoft.Extensions.DependencyInjection |
AddAzureStorageAdapter(...), AddLocalDirectoryBlobService(...) |
Microsoft.Extensions.DependencyInjection |
AddDbContextWithHook<T>(), AddHook<...>() |
DKNet.EfCore.Hooks |
AddSpecRepo<TDbContext>() |
DKNet.EfCore.Specifications |
AddDataOwnerProvider<TDbContext, TProvider>() |
DKNet.EfCore.DataAuthorization |
AddEfCoreAuditLogs<TDbContext, TPublisher>(), AddCurrentUserProvider<TDbContext, TProvider>() |
DKNet.EfCore.AuditLogs |
AddIdempotentKey(...), .RequiredIdempotentKey() |
DKNet.AspCore.Idempotency |
AddIdempotencyWith*Store(...), AddIdempotency*Store(...) |
DKNet.AspCore.Idempotency.MsSqlStore / .NpgsqlStore / .RedisStore — the store's own namespace, not the base package's |
AddS3BlobService(...) |
DKNet.Svc.BlobStorage.AwsS3 |
AddEncryptionServices(), AddAesGcmEncryption(...), AddRsaEncryption(...) |
DKNet.Svc.Encryption |
AddEfCoreEncryption<TKeyProvider>() |
DKNet.EfCore.Encryption |
AddBackgroundJob<TJob>(), AddPdfGenerator(...), AddTransformerService(...) |
Microsoft.Extensions.DependencyInjection |
AddContextualRequestPopulation() |
DKNet.AspCore.Extensions.ModelBinding |
.Response(...) on a Result |
DKNet.AspCore.Extensions.Responses |
UseEndpointConfigs(...) |
DKNet.AspCore.Extensions.Endpoints |
DefaultEntityTypeConfiguration<TEntity> |
DKNet.EfCore.Extensions.Configurations |
Specification<TEntity> (base class for your specs) |
DKNet.EfCore.Specifications.Definitions — not DKNet.EfCore.Specifications, which holds only AddSpecRepo |
Only the blob adapters read a configuration section, and each one owns its section name as a static string Name
on its options type. Nothing else in DKNet reads IConfiguration on your behalf.
| Package | Options type | Section (Options.Name) |
|---|---|---|
DKNet.Svc.BlobStorage.AzureStorage |
AzureStorageOptions |
BlobService:AzureStorage |
DKNet.Svc.BlobStorage.AwsS3 |
S3Options |
BlobService:S3 |
DKNet.Svc.BlobStorage.Local |
LocalDirectoryOptions |
BlobStorage:LocalFolder |
All three derive from BlobServiceOptions, so the validation keys are shared:
{
"BlobService": {
"AzureStorage": {
"ConnectionString": "DefaultEndpointsProtocol=https;AccountName=***;AccountKey=***",
"ContainerName": "documents",
"IncludedExtensions": [ ".pdf", ".png" ],
"MaxFileNameLength": 128,
"MaxFileSizeInMb": 25
},
"S3": {
"ConnectionString": "https://s3.us-east-1.amazonaws.com",
"BucketName": "documents",
"RegionEndpointName": "us-east-1",
"AccessKey": "***",
"Secret": "***"
}
},
"BlobStorage": {
"LocalFolder": {
"RootFolder": "/var/lib/myapp/blobs",
"MaxFileSizeInMb": 25
}
}
}The
Action<AzureStorageOptions>overload ofAddAzureStorageAdapterregisters the options object directly rather than throughIOptions<>. Prefer theIConfigurationoverload shown above.
Everything else — connection strings for the idempotency stores, the AES key for AddAesGcmEncryption, the
idempotency header name — reaches DKNet as an argument or a delegate. Read it from your own configuration and
pass it in:
using DKNet.AspCore.Idempotency.RedisStore;
using DKNet.Svc.Encryption;
builder.Services.AddIdempotencyWithRedisStore(
builder.Configuration.GetConnectionString("Redis")!,
options => options.Expiration = TimeSpan.FromHours(24));
builder.Services.AddAesGcmEncryption(builder.Configuration["Crypto:AesKey"]!);Most DKNet registrations are order-independent. These are the exceptions, and each one is a real failure mode:
AddIdempotentKey/AddIdempotencyWith*Storeat service level,.RequiredIdempotentKey()per route. The route call always adds the endpoint filter; the service call registers theIIdempotencyKeyStoreand theIdempotencyOptionsthe filter depends on. Miss the service call and the endpoint fails at request time when the filter cannot be constructed — a loud failure, but only on the first request to that route, so cover it with a start-up test rather than relying on the DI container to catch it.AddIdempotentKeyis first-registration-wins between two named stores. It returns early if anIIdempotencyKeyStoreis already registered, so a second call naming a different store, with different options, is ignored rather than overriding the first. Register the store you want exactly once. The one exception is the parameterlessAddIdempotentKey()overload: it registers an in-process store (no database, cache, or Redis) that any explicitly named store replaces, whichever order the two calls run in — so shared composition code can register it without blocking a real store later. Keys in that store are process-local, lost on restart, and not shared between instances, which makes it a local-development and unit-test default only; the app logs a startup warning while it is the store serving requests.AddDbContextWithHookinstead ofAddDbContext, whenever anything hooksSaveChanges. Domain events, audit logs, and ownership stamping all run as hooks; a plainAddDbContextregisters no hook interceptor, so they never fire.UseAutoConfigModel<TContext>()inside theAddDbContext*callback.DKNet.EfCore.DataAuthorizationattaches its query filter through the model builders that call collects, so a context that skips it gets no ownership filter.AddDataOwnerProvideraffects everyDbContextin the process. It registers its filter inEfCoreSetup.GlobalModelBuilders, which is static: every context callingUseAutoConfigModel()applies it, not just the one passed asTDbContext. A second context whose model containsIOwnedByentities must also implementIDataOwnerDbContext, or keep those entities out of its model. See the Migration Guide.AddCurrentUserProvideris application-wide, and the first registration wins. It registersICurrentUserProviderun-keyed behind anIsRegistered<ICurrentUserProvider>()guard, so a second call with a different provider type silently keeps the first — only the audit-hook attachment to the newTDbContexttakes effect. It also attaches the audit hook itself, so it needs noAddEfCoreAuditLogscall alongside it, and it never overwrites anAuditLogOptionsan earlierAddEfCoreAuditHook/AddEfCoreAuditLogsregistered. Once one is registered,CreatedBy/UpdatedBycome from the signed-in user for every save whereGetCurrentUser()returns a value, and fromAddDataOwnerProvider's ownership key for every save where it does not.- An
IMapperregistration is required beforeAddEvent<TEvent>()or[RaisesEvent]can work. Both map the entity onto the event type; without a mapper the save throwsEventException.AddEvent(instance)needs none.
One host wiring five packages. Every call below is in the namespace listed in the table above.
using DKNet.AspCore.Idempotency; // RequiredIdempotentKey
using DKNet.AspCore.Idempotency.RedisStore; // AddIdempotencyWithRedisStore
using DKNet.EfCore.AuditLogs; // AddEfCoreAuditLogs
using DKNet.EfCore.Hooks; // AddDbContextWithHook
using DKNet.EfCore.Specifications; // AddSpecRepo
using Microsoft.EntityFrameworkCore; // UseAutoConfigModel, UseSqlServer
using SlimMessageBus; // IMessageBus
var builder = WebApplication.CreateBuilder(args);
// DbContext + the shared hook interceptor + convention-based model build.
builder.Services.AddDbContextWithHook<AppDbContext>(options => options
.UseSqlServer(builder.Configuration.GetConnectionString("Default")!)
.UseAutoConfigModel<AppDbContext>());
// Querying and persistence.
builder.Services.AddSpecRepo<AppDbContext>();
// Field-level change history, handed to a publisher you write.
builder.Services.AddEfCoreAuditLogs<AppDbContext, ConsoleAuditLogPublisher>();
// In-memory SlimMessageBus + handler discovery, SaveChanges after a successful write, and domain events
// forwarded onto the bus (DKNet.SlimBus.Extensions.EfCore). Without EF Core: AddSlimBusForMediator(...).
builder.Services.AddSlimBusForEfCore<AppDbContext>(typeof(Program).Assembly);
// Retry protection, backed by Redis. The service call must come before the route call.
builder.Services.AddIdempotencyWithRedisStore(
builder.Configuration.GetConnectionString("Redis")!,
options => options.Expiration = TimeSpan.FromHours(24));
// Blob storage, the one family that binds its own config section.
builder.Services.AddAzureStorageAdapter(builder.Configuration);
var app = builder.Build();
app.MapPost("/products", (IMessageBus bus, CreateProduct command) => bus.Send(command))
.RequiredIdempotentKey();
app.Run();AddSlimBusForEfCore wires SlimMessageBus's in-memory provider and discovers handlers from the assemblies you pass —
the MediatR-style setup. For an out-of-process transport (an Azure Service Bus provider, Kafka, …) call
SlimMessageBus's own AddSlimMessageBus(...) instead and add AddSlimBusEfCoreInterceptor<AppDbContext>() /
AddSlimBusEventPublisher<AppDbContext>() individually. There is no per-handler DKNet registration, and no
validation pipeline — request validation is bring-your-own.
DKNet adds nothing here; use the standard .NET configuration providers. The values worth keeping out of
appsettings.json are the blob adapter connection strings, the idempotency store connection string, and the AES
or RSA key material passed to DKNet.Svc.Encryption and DKNet.EfCore.Encryption.
- Azure Key Vault or AWS Secrets Manager for hosted environments
- Environment variables for containers —
BlobService__AzureStorage__ConnectionStringmaps onto the section above - User secrets (
dotnet user-secrets) for local development
Logging is standard Microsoft.Extensions.Logging. DKNet's hooks and interceptors log under their own type
names, so DKNet as a category prefix filters them:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"DKNet": "Debug",
"Microsoft.EntityFrameworkCore": "Warning"
}
}
}The DKNet test suite itself runs against a real SQL Server via TestContainers rather than the EF Core in-memory provider, because in-memory masks provider behaviour that several packages depend on — see Testing Strategy. Apply the same rule to your own tests of DKNet-backed persistence: in-memory is fine for pure domain logic, but global query filters, generated SQL, and sequences need a real provider.
Two DKNet-specific points when configuring a test host:
- Keep
UseAutoConfigModel<TContext>(). Dropping it in tests silently removes global query filters, so an ownership-filtered query returns rows it never would in production. - Register a fake
IDataOwnerProviderrather than bypassing the filter. The filter is attached at model-build time and is not something a test can turn off per query.
Each page below owns the exhaustive table for its own package. This page does not repeat them.
| Package | Its configuration surface |
|---|---|
| DKNet.EfCore.Extensions | UseAutoConfigModel, seeding, GUID v7 keys, [Sequence] |
| DKNet.EfCore.Hooks | AddDbContextWithHook, AddHook, hook disabling |
| DKNet.EfCore.Events | AddEventPublisher, IEventPublisher, [RaisesEvent] |
| DKNet.EfCore.AuditLogs | AddEfCoreAuditLogs, AddCurrentUserProvider, ICurrentUserProvider, [AuditLog], [SensitiveData], [IgnoreAuditLog] |
| DKNet.EfCore.DataAuthorization | AddDataOwnerProvider, IDataOwnerDbContext, IOwnedBy |
| DKNet.EfCore.Encryption | AddEfCoreEncryption<TKeyProvider>, [Encrypted] |
| DKNet.EfCore.Specifications | AddSpecRepo, Specification<T> builder methods, Ops |
| DKNet.EfCore.DtoGenerator | [GenerateDto], and the MSBuild properties in the Global Exclusions Guide |
| DKNet.SlimBus.Extensions | AddSlimBusForMediator |
| DKNet.SlimBus.Extensions.EfCore | AddSlimBusForEfCore, AddSlimBusEfCoreInterceptor, AddSlimBusEventPublisher |
| DKNet.AspCore.Extensions | AddContextualRequestPopulation, EndpointRegistrationOptions, [FromClaim], ListQueryOptions (DKNet:ListQuery) |
| DKNet.AspCore.Idempotency | IdempotencyOptions in full |
| DKNet.AspCore.Tasks | AddBackgroundJob, AddBackgroundJobFrom |
| DKNet.Svc.BlobStorage.Abstractions | BlobServiceOptions validation keys shared by all adapters |
| DKNet.Svc.Encryption | AddEncryptionServices, AddAesGcmEncryption, AddRsaEncryption |
| DKNet.Svc.PdfGenerators | AddPdfGenerator and the page/margin options |
| DKNet.Svc.Transformation | AddTransformerService, TransformOptions |
| Aspire.Hosting.ServiceBus | AddServiceBus and the emulator resource |