TCIS.Mediator.Keyed 1.0.0-rc.35

This is a prerelease version of TCIS.Mediator.Keyed.
There is a newer prerelease version of this package available.
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
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="TCIS.Mediator.Keyed" Version="1.0.0-rc.35" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="TCIS.Mediator.Keyed" Version="1.0.0-rc.35" />
                    
Directory.Packages.props
<PackageReference Include="TCIS.Mediator.Keyed" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add TCIS.Mediator.Keyed --version 1.0.0-rc.35
                    
#r "nuget: TCIS.Mediator.Keyed, 1.0.0-rc.35"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package TCIS.Mediator.Keyed@1.0.0-rc.35
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=TCIS.Mediator.Keyed&version=1.0.0-rc.35&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=TCIS.Mediator.Keyed&version=1.0.0-rc.35&prerelease
                    
Install as a Cake Tool

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. WorkContext is 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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
Loading failed