TCIS.Mediator.Keyed
1.0.0-rc.35
See the version list below for details.
dotnet add package TCIS.Mediator.Keyed --version 1.0.0-rc.35
NuGet\Install-Package TCIS.Mediator.Keyed -Version 1.0.0-rc.35
<PackageReference Include="TCIS.Mediator.Keyed" Version="1.0.0-rc.35" />
<PackageVersion Include="TCIS.Mediator.Keyed" Version="1.0.0-rc.35" />
<PackageReference Include="TCIS.Mediator.Keyed" />
paket add TCIS.Mediator.Keyed --version 1.0.0-rc.35
#r "nuget: TCIS.Mediator.Keyed, 1.0.0-rc.35"
#:package TCIS.Mediator.Keyed@1.0.0-rc.35
#addin nuget:?package=TCIS.Mediator.Keyed&version=1.0.0-rc.35&prerelease
#tool nuget:?package=TCIS.Mediator.Keyed&version=1.0.0-rc.35&prerelease
TCIS.Mediator.Keyed
Routes mediator handlers by site code, so a port-specific plugin can override or extend the standard business without the Platform knowing it exists.
Core mediator behaviour — pipeline, validation, notifications — is documented in
TCIS.Mediator.
1. Registration
dotnet add package TCIS.Mediator.Keyed
builder.Services.AddTKeyedMediator(options =>
{
options.Assemblies = [typeof(Program).Assembly];
options.AddTEcosystem();
});
One call does four things:
| # | Work |
|---|---|
| 1 | Registers WorkContextMediatorKeyProvider (scoped) — resolves the site code from IWorkContext |
| 2 | Replaces IMediator with KeyedMediator |
| 3 | Registers handlers from the given assemblies under the DEFAULT key |
| 4 | Registers MediatorDiscoveryHook so the Pluggable engine registers plugin handlers under their own site code |
AddTKeyedMediator and AddTMediator may be called in either order — the core registers IMediator with TryAdd, so it never overwrites the keyed one.
2. Send overrides · Publish adds
This is the distinction to keep in mind:
| Resolution | Meaning | |
|---|---|---|
Send |
site handler → DEFAULT handler → non-keyed |
The plugin replaces the platform handler |
Publish |
non-keyed + site handlers + DEFAULT handlers |
The plugin adds a reaction alongside the platform's |
// Send — CATLAI's handler wins if it exists, otherwise the platform's runs
var result = await mediator.Send(new CalculateGateFee(transactionId), ct);
// Publish — BOTH the platform reaction and CATLAI's reaction run
await mediator.Publish(new GateInCompleted(transactionId), ct);
That asymmetry is the point of the 80/20 model: a port overrides the parts it needs to change, and adds the parts that are extra.
3. The key is read per resolution
KeyedMediator calls IMediatorKeyProvider.GetCurrentKey() each time a service is resolved, not once when it is constructed.
That matters for anything holding an IMediator longer than one request — a background worker that resolves once and then processes messages for several tenants, or any field-injected mediator. Freezing the key at construction would have run the wrong site's business with no signal at all.
Keys are normalised (Trim().ToUpperInvariant()) before lookup, so a custom key provider returning "catlai" still matches handlers registered as CATLAI instead of silently falling back to the platform handler.
4. Writing a plugin handler
Plugin assemblies are registered by the Pluggable engine's discovery pass, keyed by the module's SiteCode:
// In the CATLAI plugin assembly
public sealed class CatLaiCalculateGateFeeHandler : IRequestHandler<CalculateGateFee, decimal>
{
public Task<decimal> Handle(CalculateGateFee request, CancellationToken ct) { … }
}
Nothing else is needed — no attribute, no manual registration. The handler is discovered because the assembly was passed to AddSitePlugins, and it wins over the platform handler for that site only.
5. Custom key source
The default provider reads the site code from IWorkContext. Replace it when the key comes from somewhere else:
public sealed class HeaderMediatorKeyProvider(IHttpContextAccessor accessor) : IMediatorKeyProvider
{
public string GetCurrentKey()
=> accessor.HttpContext?.Request.Headers["X-Site-Code"].FirstOrDefault() ?? Sites.Default;
}
builder.Services.AddScoped<IMediatorKeyProvider, HeaderMediatorKeyProvider>(); // before AddTKeyedMediator
Prefer the default.
WorkContextis the single source of truth for the site code across every ingress — HTTP, gRPC, Hangfire and EventBus all restore it through context propagation. A second source is how one site's steps end up running with another site's rules.
6. Pitfalls
| # | Pitfall | Consequence |
|---|---|---|
| 1 | Expecting Publish to pick only the plugin handler |
It runs platform and plugin — that is Publish semantics |
| 2 | Expecting Send to run both |
It picks one: plugin first, platform as fallback |
| 3 | A custom key provider that does not normalise case | Handled — the mediator normalises before lookup |
| 4 | A plugin module left at SiteCode = "DEFAULT" |
Its handlers apply to every site. Rejected at startup by AddSitePlugins |
| 5 | Holding IMediator in a singleton |
The scoped key provider cannot be resolved from a singleton — inject IServiceScopeFactory and resolve per unit of work |
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- FluentValidation (>= 11.11.0)
- Microsoft.Extensions.Configuration.Abstractions (>= 9.0.0)
- Microsoft.Extensions.DependencyInjection (>= 9.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 9.0.0)
- TCIS.Mediator (>= 1.0.0-rc.35)
- TCIS.Pluggable.Abstractions (>= 1.0.0-rc.35)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0-rc.37 | 0 | 8/27/2026 |
| 1.0.0-rc.36 | 0 | 8/27/2026 |
| 1.0.0-rc.35 | 27 | 8/26/2026 |
| 1.0.0-rc.34 | 32 | 8/26/2026 |
| 1.0.0-rc.33 | 43 | 8/21/2026 |
| 1.0.0-rc.32 | 49 | 8/21/2026 |
| 1.0.0-rc.31 | 52 | 8/21/2026 |
| 1.0.0-rc.30 | 55 | 8/21/2026 |
| 1.0.0-rc.29 | 50 | 8/21/2026 |
| 1.0.0-rc.28 | 55 | 8/21/2026 |
| 1.0.0-rc.27 | 51 | 8/21/2026 |
| 1.0.0-rc.26 | 47 | 8/20/2026 |
| 1.0.0-rc.25 | 50 | 8/20/2026 |
| 1.0.0-rc.24 | 48 | 8/20/2026 |
| 1.0.0-rc.23 | 52 | 8/20/2026 |
| 1.0.0-rc.22 | 55 | 8/19/2026 |
| 1.0.0-rc.21 | 54 | 8/19/2026 |
| 1.0.0-rc.20 | 66 | 8/18/2026 |
| 1.0.0-rc.19 | 66 | 8/13/2026 |
| 1.0.0-rc.18 | 51 | 8/13/2026 |