Contract Keys
Contract keys can in most ways be called a ‘Cache key’. Let’s say you want to cache an HTTP request, but that request has filters and varying types of parameters. Obviously, you want to have different caches for these varying parameters. Contract keys gives you the ability to take control of HOW these keys are built right from your contract calls.
Contract keys are currently used by the following middleware:
Let’s take a look at a sample contract.
public record MyRequest(string Id, string Category) : IRequest<MyResult>, IContractKey{ // WARNING: we do not alter your key in anyway, so be sure to make it unique by adding things like the type public string GetKey() => $"MyRequest-{Id}-{Category}";}public record MyResult;Contract Key Provider
Section titled “Contract Key Provider”Starting with Shiny Mediator 4.9, we introduced the IContractKeyProvider interface. This allows you to provide a custom way of generating request keys without having to implement IContractKey on every contract.
You can implement this interface to pull values from configuration, source generation, or any other method you prefer. The default implementation uses reflection to maintain backwards compatibility, but you can create your own provider for more advanced scenarios.
public class CustomContractKeyProvider : IContractKeyProvider{ public string GetContractKey(object message) { return "Custom Key"; // careful to make sure this doesn't conflict here }}Register it with SetContractKeyProvider. It replaces the default whether you call it inside the
AddShinyMediator configurator or afterwards:
services.AddShinyMediator(x => x.SetContractKeyProvider<CustomContractKeyProvider>());Source Generated Contract Keys
Section titled “Source Generated Contract Keys”Implementing code is so “old school”. We wanted to make it easy to implement contract keys without having to write a ton of code. Source generators to the rescue! In our sample shown below, it essentially generates
all of the IContractKey logic, checks for null, ToStings values with format strings, etc saving you time!
[Shiny.Mediator.ContractKey("MyRequest_{IntValue}_{Timestamp:MMM_dd_yyyy}_{FullIntValue}")]public partial class MyRequest : Shiny.Mediator.IRequest<MyResult>{ public double NotUsed { get; set; } // not used because it is not used within the key public int? IntValue { get; set; } public int FullIntValue { get; set; }
public DateTime Timestamp { get; set;}}- Make the class (or record) partial or the source generator will error
- Mark your class/record with the
[ContractKey]attribute - Optional: specify the format of the key. Leaving the format blank will assume you want to use all properties within the class
Generated Keys for Every Contract (AOT & Trimming)
Section titled “Generated Keys for Every Contract (AOT & Trimming)”The reflection fallback isn’t trim-safe. Instead of annotating every contract, opt in to generated keys in your app project:
<PropertyGroup> <ShinyMediatorGenerateContractKeys>true</ShinyMediatorGenerateContractKeys></PropertyGroup>The source generator writes a reflection-free IContractKeySource for every contract in the project, plus
every contract your [MediatorSingleton] / [MediatorScoped] handlers use. AddMediatorRegistry()
registers it. Keys keep the reflection format (Full.TypeName_Prop_value...), so existing cache and
offline entries still match. The reflection fallback is then switched off and trimmed away. See
Source Generation → Contract Keys
for the MSBuild properties and what’s covered.


