### Start Telemetry Backend with Docker Compose Source: https://github.com/martinothamar/mediator/blob/main/samples/README.md Use this command to start a local LGTM stack for viewing telemetry data. Ensure Docker is installed and running. ```sh docker compose up -d ``` -------------------------------- ### Run Source Generation Tests with Just Source: https://github.com/martinothamar/mediator/blob/main/AGENTS.md Use the 'just' command to run source generation tests. Ensure you have 'just' installed and configured. ```bash just test-sourcegen ``` -------------------------------- ### Implement Stream Logging Behavior Source: https://context7.com/martinothamar/mediator/llms.txt Wrap streaming handlers with IStreamPipelineBehavior to add logic before and after the stream is processed. This example logs the start and end of a stream, including the item count. Register via DI. ```csharp using Mediator; using System.Runtime.CompilerServices; public sealed class StreamLoggingBehavior : IStreamPipelineBehavior where TMessage : IStreamMessage { public async IAsyncEnumerable Handle( TMessage message, StreamHandlerDelegate next, [EnumeratorCancellation] CancellationToken cancellationToken) { Console.WriteLine($"[Stream] Starting {typeof(TMessage).Name}"); int count = 0; await foreach (var item in next(message, cancellationToken).WithCancellation(cancellationToken)) { count++; yield return item; } Console.WriteLine($"[Stream] Completed {typeof(TMessage).Name} — {count} items"); } } services.AddSingleton(typeof(IStreamPipelineBehavior<,>), typeof(StreamLoggingBehavior<,>)); ``` -------------------------------- ### Install Mediator and Register Services Source: https://context7.com/martinothamar/mediator/llms.txt Install the Mediator NuGet packages and configure dependency injection using AddMediator. Specify assemblies to scan and optionally configure service lifetime, caching mode, and pipeline behaviors. ```csharp all runtime; build; native; contentfiles; analyzers ``` ```csharp using Mediator; using Microsoft.Extensions.DependencyInjection; var services = new ServiceCollection(); services.AddMediator((MediatorOptions options) => { // Explicitly list assemblies to scan (recommended for AOT and large solutions) options.Assemblies = [typeof(Ping).Assembly]; // Default: Singleton — gives best performance options.ServiceLifetime = ServiceLifetime.Singleton; // CachingMode.Eager (default) — initialize all lookups on first Mediator access // CachingMode.Lazy — initialize on demand; best for cold-start/serverless scenarios options.CachingMode = CachingMode.Lazy; // Register ordered pipeline behaviors (NativeAOT-compatible) options.PipelineBehaviors = [typeof(LoggingBehavior<,>), typeof(ValidationBehavior<,>)]; }); var sp = services.BuildServiceProvider(); var mediator = sp.GetRequiredService(); ``` -------------------------------- ### Run Full Test Suite with Just Source: https://github.com/martinothamar/mediator/blob/main/CONTRIBUTING.md If you have the 'just' command runner installed, use this command to run the complete test suite locally, mimicking the CI environment. This includes source generator, memory allocation, and a full test matrix. ```bash just test ``` -------------------------------- ### Implement Stream Pre- and Post-Processors Source: https://context7.com/martinothamar/mediator/llms.txt Use StreamMessagePreProcessor for logic before a stream starts and StreamMessagePostProcessor for logic after all items are yielded. Register them using AddSingleton. ```csharp using Mediator; // Pre-processor — execute logic once before the stream begins public sealed class AuthStreamPreProcessor : StreamMessagePreProcessor where TMessage : notnull, IStreamMessage { protected override ValueTask Handle(TMessage message, CancellationToken cancellationToken) { Console.WriteLine($"[PreProcess] Authorizing stream: {typeof(TMessage).Name}"); return default; } } // Post-processor — execute logic once after all items have been emitted public sealed class StreamAuditPostProcessor : StreamMessagePostProcessor where TMessage : notnull, IStreamMessage { protected override ValueTask Handle( TMessage message, IReadOnlyList responses, CancellationToken cancellationToken) { Console.WriteLine($"[PostProcess] Stream finished. Total items: {responses.Count}"); return default; } } // Registration services.AddSingleton(typeof(IStreamPipelineBehavior<,>), typeof(AuthStreamPreProcessor<,>)); services.AddSingleton(typeof(IStreamPipelineBehavior<,>), typeof(StreamAuditPostProcessor<,>)); ``` -------------------------------- ### Add Mediator Packages using .NET CLI Source: https://github.com/martinothamar/mediator/blob/main/README.md Install the Mediator.SourceGenerator and Mediator.Abstractions packages using the .NET CLI. Ensure you are using compatible versions. ```powershell dotnet add package Mediator.SourceGenerator --version 3.0.* dotnet add package Mediator.Abstractions --version 3.0.* ``` -------------------------------- ### Native AOT Cold Start Optimization with CachingMode Source: https://context7.com/martinothamar/mediator/llms.txt Configure Mediator for Native AOT by setting CachingMode to Lazy and specifying assemblies to scan. Use options.PipelineBehaviors for AOT-compatible pipeline setup, avoiding open-generic DI registrations. ```csharp using Mediator; // Native AOT — minimal startup cost, lazy initialization services.AddMediator((MediatorOptions options) => { // Explicit assembly list avoids scanning all referenced assemblies options.Assemblies = [typeof(Ping).Assembly]; options.CachingMode = CachingMode.Lazy; // AOT-safe pipeline: source generator emits concrete registered types options.PipelineBehaviors = [typeof(LoggingBehavior<,>), typeof(PingValidator)]; options.StreamPipelineBehaviors = [typeof(StreamLoggingBehavior<,>)]; // GenerateTypesAsInternal = true for Blazor WASM or inlined assemblies options.GenerateTypesAsInternal = true; }); // AOT-friendly record structs (value types, no heap allocation) public readonly record struct Ping(Guid Id) : IRequest; public readonly record struct Pong(Guid Id); public sealed class PingHandler : IRequestHandler { public ValueTask Handle(Ping request, CancellationToken cancellationToken) => new(new Pong(request.Id)); } ``` -------------------------------- ### Pipeline Behavior for Validation Source: https://context7.com/martinothamar/mediator/llms.txt Create a concrete pipeline behavior to validate specific message types before they are handled. This example validates a CreateOrder command. ```csharp // Concrete behavior — applies only to a specific message type public sealed class CreateOrderValidator : IPipelineBehavior { public ValueTask Handle( CreateOrder command, MessageHandlerDelegate next, CancellationToken cancellationToken) { if (command.Quantity <= 0) throw new ArgumentException("Quantity must be positive", nameof(command.Quantity)); if (string.IsNullOrWhiteSpace(command.ProductId)) throw new ArgumentException("ProductId is required", nameof(command.ProductId)); return next(command, cancellationToken); } } ``` -------------------------------- ### Stream Message Pre-Processor Logging in C# Source: https://github.com/martinothamar/mediator/blob/main/README.md Implement this pre-processor to execute logic once before a stream of messages begins. It's useful for initializing logging or other setup tasks. ```csharp public sealed class StreamLoggingPreProcessor : StreamMessagePreProcessor where TMessage : notnull, IStreamMessage { private readonly ILogger> _logger; public StreamLoggingPreProcessor(ILogger> logger) { _logger = logger; } protected override ValueTask Handle(TMessage message, CancellationToken cancellationToken) { _logger.LogInformation("Starting stream processing for {MessageType}", typeof(TMessage).Name); return default; } } ``` -------------------------------- ### Call Endpoint and Observe Header Source: https://github.com/martinothamar/mediator/blob/main/samples/use-cases/RequestScopedState/README.md Example using curl to call the weather forecast endpoint and inspect the response headers, specifically the custom 'X-App-CacheHit' header. ```console $ curl -X 'GET' 'http://localhost:5236/weatherforecast' -H 'accept: application/json' -i HTTP/1.1 200 OK Content-Type: application/json; charset=utf-8 Date: Sun, 28 Sep 2025 12:44:21 GMT Server: Kestrel Transfer-Encoding: chunked X-App-CacheHit: false [{"date":"2025-09-29","temperatureC":7,"summary":"Balmy","temperatureF":44},{"date":"2025-09-30","temperatureC":54,"summary":"Mild","temperatureF":129},{"date":"2025-10-01","temperatureC":2,"summary":"Cool","temperatureF":35},{"date":"2025-10-02","temperatureC":48,"summary":"Sweltering","temperatureF":118},{"date":"2025-10-03","temperatureC":47,"summary":"Warm","temperatureF":116}] ``` -------------------------------- ### Implement Message Validation as Pre-processor Source: https://github.com/martinothamar/mediator/blob/main/README.md Example of a message pre-processor that validates messages implementing the IValidate interface. This approach handles validation within the pre-processing step. ```csharp public sealed class MessageValidatorBehaviour : MessagePreProcessor where TMessage : IValidate { protected override ValueTask Handle(TMessage message, CancellationToken cancellationToken) { if (!message.IsValid(out var validationError)) throw new ValidationException(validationError); return default; } } ``` -------------------------------- ### Mediator Registration with Pipeline Behaviors Source: https://context7.com/martinothamar/mediator/llms.txt Register the Mediator services and configure pipeline behaviors, specifying the order in which they should be applied. The example shows LoggingBehavior wrapping CreateOrderValidator. ```csharp // Registration (order matters — LoggingBehavior wraps CreateOrderValidator) services.AddMediator((MediatorOptions options) => { options.Assemblies = [typeof(CreateOrder).Assembly]; options.PipelineBehaviors = [typeof(LoggingBehavior<,>), typeof(CreateOrderValidator)]; }); ``` -------------------------------- ### Test REST API Endpoint with HTTP Request Source: https://github.com/martinothamar/mediator/blob/main/samples/apps/ASPNET_Core_CleanArchitecture/README.md This example demonstrates how to test a REST API endpoint using an HTTP request with the 'REST Client' VSCode extension. It shows a POST request to create a todo item and the expected 400 Bad Request response due to a validation error. ```http POST http://localhost:5000/api/todos HTTP/1.1 content-type: application/json { "title": "", "text": "This is a todo without a title, we should get an error..." } HTTP/1.1 400 Bad Request Connection: close Date: Mon, 17 May 2021 10:51:45 GMT Content-Type: application/json; charset=utf-8 Server: Kestrel Transfer-Encoding: chunked { "errors": [ "'Title' must be between 1 and 40 characters. You entered 0 characters." ] } ``` -------------------------------- ### Implement Message Validation as Pipeline Behavior Source: https://github.com/martinothamar/mediator/blob/main/README.md Example of a pipeline behavior that validates messages implementing the IValidate interface before processing. Requires registration as IPipelineBehavior. ```csharp public sealed class MessageValidatorBehaviour : IPipelineBehavior where TMessage : IValidate { public ValueTask Handle( TMessage message, MessageHandlerDelegate next, CancellationToken cancellationToken ) { if (!message.IsValid(out var validationError)) throw new ValidationException(validationError); return next(message, cancellationToken); } } ``` -------------------------------- ### Implement Request Enrichment Pre-Processor Source: https://context7.com/martinothamar/mediator/llms.txt Use MessagePreProcessor to run logic before a message handler. This example enriches requests with context like correlation IDs. Register via DI or PipelineBehaviors. ```csharp using Mediator; // Runs before every IMessage handler public sealed class RequestEnrichmentPreProcessor : MessagePreProcessor where TMessage : notnull, IMessage { protected override ValueTask Handle(TMessage message, CancellationToken cancellationToken) { // e.g., set correlation ID, tenant context, etc. Console.WriteLine($"[PreProcess] {typeof(TMessage).Name}"); return default; } } // Registration via DI (non-AOT) or via PipelineBehaviors (AOT) services.AddSingleton(typeof(IPipelineBehavior<,>), typeof(RequestEnrichmentPreProcessor<,>)); // OR for Native AOT: services.AddMediator((MediatorOptions options) => { options.PipelineBehaviors = [typeof(RequestEnrichmentPreProcessor<,>)]; }); ``` -------------------------------- ### Build and Run NotificationPublisher Source: https://github.com/martinothamar/mediator/blob/main/samples/basic/NotificationPublisher/README.md Execute the sample application to observe the custom notification publisher in action. It logs exceptions thrown by notification handlers. ```console $ dotnet run Publishing! ----------------------------------- MyNotificationHandler - 6ae7d56b-8a2f-404c-a24b-c5df1e6691d2 System.Exception: Something went wrong! at MyNotificationHandler.Handle(Notification notification, CancellationToken cancellationToken) in /home/martin/code/private/Mediator/samples/basic/NotificationPublisher/Program.cs:line 79 at MyNotificationPublisher.Publish[TNotification](NotificationHandlers`1 handlers, TNotification notification, CancellationToken cancellationToken) in /home/martin/code/private/Mediator/samples/basic/NotificationPublisher/Program.cs:line 46 ----------------------------------- Finished publishing! ``` -------------------------------- ### Build and Run Autofac DI Sample Source: https://github.com/martinothamar/mediator/blob/main/samples/use-cases/Autofac_DI/README.md Instructions for building and running the Autofac DI sample application. After running, access the API at http://localhost:5000. ```console $ dotnet run Building... info: Microsoft.Hosting.Lifetime[14] Now listening on: http://localhost:5000 info: Microsoft.Hosting.Lifetime[0] Application started. Press Ctrl+C to shut down. info: Microsoft.Hosting.Lifetime[0] Hosting environment: Development info: Microsoft.Hosting.Lifetime[0] Content root path: /home/martin/code/private/Mediator/samples/use-cases/Autofac_DI ``` -------------------------------- ### Build and Run Streaming Sample Source: https://github.com/martinothamar/mediator/blob/main/samples/basic/Streaming/README.md Execute this command in the terminal to build and run the streaming sample. Observe the output showing streamed messages. ```console $ dotnet run ----------------------------------- ID: 93365b37-c597-4ebf-9f30-63327536efc8 StreamPing { Id = 93365b37-c597-4ebf-9f30-63327536efc8 } Pong { Id = 93365b37-c597-4ebf-9f30-63327536efc8 } ----------------------------------- ID: 93365b37-c597-4ebf-9f30-63327536efc8 StreamPing { Id = 93365b37-c597-4ebf-9f30-63327536efc8 } Pong { Id = 93365b37-c597-4ebf-9f30-63327536efc8 } ----------------------------------- ID: 93365b37-c597-4ebf-9f30-63327536efc8 StreamPing { Id = 93365b37-c597-4ebf-9f30-63327536efc8 } Pong { Id = 93365b37-c597-4ebf-9f30-63327536efc8 } ----------------------------------- ID: 93365b37-c597-4ebf-9f30-63327536efc8 StreamPing { Id = 93365b37-c597-4ebf-9f30-63327536efc8 } Pong { Id = 93365b37-c597-4ebf-9f30-63327536efc8 } ----------------------------------- ID: 93365b37-c597-4ebf-9f30-63327536efc8 StreamPing { Id = 93365b37-c597-4ebf-9f30-63327536efc8 } Pong { Id = 93365b37-c597-4ebf-9f30-63327536efc8 } ``` -------------------------------- ### Build Project with Formatting Source: https://github.com/martinothamar/mediator/blob/main/AGENTS.md Run this command to build the project. Formatting is automatically applied during the build process using csharpier. ```bash dotnet build ``` -------------------------------- ### Build and Run Application Source: https://github.com/martinothamar/mediator/blob/main/samples/use-cases/RequestScopedState/README.md Commands to build and run the .NET application. Observe the console output for listening addresses and application status. ```console $ dotnet run Building... info: Microsoft.Hosting.Lifetime[14] Now listening on: http://localhost:5236 info: Microsoft.Hosting.Lifetime[0] Application started. Press Ctrl+C to shut down. info: Microsoft.Hosting.Lifetime[0] Hosting environment: Development info: Microsoft.Hosting.Lifetime[0] Content root path: /home/martin/code/private/Mediator/samples/use-cases/RequestScopedState ``` -------------------------------- ### Build and Run SimpleConsole Source: https://github.com/martinothamar/mediator/blob/main/samples/basic/Console/README.md Instructions to build and run the SimpleConsole application using the .NET CLI. Displays the expected output, including handler execution and message processing. ```console $ dotnet run 1) Running logger handler 2) Running ping validator 3) Valid input! 4) Returning pong! 5) No error! ----------------------------------- ID: 4f5e8fe3-e64f-4042-9ed3-33b894be8776 Ping { Id = 4f5e8fe3-e64f-4042-9ed3-33b894be8776 } Pong { Id = 4f5e8fe3-e64f-4042-9ed3-33b894be8776 } ``` -------------------------------- ### Test Specific Configuration Combination Source: https://github.com/martinothamar/mediator/blob/main/CONTRIBUTING.md Run the full test suite with a precise combination of .NET framework, lifetime, publisher, and other configuration options. Refer to the Justfile for all available parameters. ```bash just test-config net10.0 Singleton ForeachAwait Default Eager ``` -------------------------------- ### Test Specific .NET Framework Source: https://github.com/martinothamar/mediator/blob/main/CONTRIBUTING.md Run the full test suite against a specific .NET framework version. Replace 'net10.0' with 'net9.0' or 'net8.0' as needed. ```bash just test-framework net10.0 ``` ```bash just test-framework net9.0 ``` ```bash just test-framework net8.0 ``` -------------------------------- ### Run Blazor App Source: https://github.com/martinothamar/mediator/blob/main/samples/apps/ASPNET_CORE_Blazor/README.md Navigate to the application directory and run the Blazor application using the dotnet CLI. ```sh cd AspNetCoreBlazor dotnet run ``` -------------------------------- ### Run Quick Local Tests Source: https://github.com/martinothamar/mediator/blob/main/CONTRIBUTING.md Execute this command for basic local testing during development. For comprehensive testing, refer to the full test suite or CI pipelines. ```bash dotnet test ``` -------------------------------- ### Build and Run ConsoleAOT Application Source: https://github.com/martinothamar/mediator/blob/main/samples/basic/ConsoleAOT/README.md Build the ConsoleAOT application using `dotnet publish` with a specified Runtime Identifier (RID) and then run the compiled executable. Replace `` with your target runtime, such as `win-x64` or `linux-x64`. ```console $ dotnet publish -r -c Release $ ./bin/Release/net10.0//ConsoleAOT ``` -------------------------------- ### Run Notifications Showcase Source: https://github.com/martinothamar/mediator/blob/main/samples/basic/Notifications/README.md Execute the .NET application to see the notification publishing process and handler outputs. This demonstrates the basic workflow of sending notifications and how different handlers process them. ```console $ dotnet run Publishing! ----------------------------------- CatchAllNotificationHandler - 360412c0-e7cf-452c-97f3-0e4be166b26c ConcreteNotificationHandler - 360412c0-e7cf-452c-97f3-0e4be166b26c GenericNotificationHandler`1 - 360412c0-e7cf-452c-97f3-0e4be166b26c ----------------------------------- Finished publishing! ``` -------------------------------- ### Failed Namespace Resolution in MediatorOptions Source: https://github.com/martinothamar/mediator/blob/main/test/Mediator.SourceGenerator.Tests/_snapshots/ReportingTests.Test_Unassigned_Namespace_Variable_In_Config.verified.txt This error occurs when MediatorGenerator cannot resolve the namespace configuration because it's not a compile-time constant. The example shows an attempt to assign a non-constant variable to the options.Namespace property. ```csharp options.Namespace = MediatorNamespace; ``` -------------------------------- ### Build and Run Console Application Source: https://github.com/martinothamar/mediator/blob/main/samples/Showcase/README.md Execute the .NET console application to observe its output, including successful message handling and error scenarios. ```console $ dotnet run Got the right ID: (Ping { Id = 34dd8a5f-9ed5-4bde-b675-188155b9ace1 }, Pong { Id = 34dd8a5f-9ed5-4bde-b675-188155b9ace1 }) Error handling message: Invalid input Done! ``` -------------------------------- ### Query Dispatching with IQuery / IQueryHandler Source: https://context7.com/martinothamar/mediator/llms.txt Define queries for read operations and handlers to retrieve data. Queries are functionally equivalent to commands in the pipeline. Usage involves sending the query via the mediator. ```csharp using Mediator; public sealed record SearchProducts(string Term) : IQuery>; public sealed class SearchProductsHandler : IQueryHandler> { public ValueTask> Handle(SearchProducts query, CancellationToken cancellationToken) { var results = new[] { $"Product matching '{query.Term}'", "Another Product" }; return new ValueTask>(results); } } // ASP.NET Core controller integration [ApiController, Route("products")] public class ProductsController : ControllerBase { private readonly IMediator _mediator; public ProductsController(IMediator mediator) => _mediator = mediator; [HttpGet("search")] public async Task Search([FromQuery] string term, CancellationToken ct) { var results = await _mediator.Send(new SearchProducts(term), ct); return Ok(results); } } // --- Usage --- var products = await mediator.Send(new SearchProducts("laptop")); // ["Product matching 'laptop'", "Another Product"] ``` -------------------------------- ### Test Specific Publisher Configuration Source: https://github.com/martinothamar/mediator/blob/main/CONTRIBUTING.md Run tests focusing on a specific publisher implementation. Supported options are ForeachAwait and TaskWhenAll. ```bash just test-publisher ForeachAwait ``` ```bash just test-publisher TaskWhenAll ``` -------------------------------- ### Run Targeted Integration Tests Source: https://github.com/martinothamar/mediator/blob/main/AGENTS.md Execute specific integration tests for .NET 10.0 using the dotnet test command. ```bash dotnet test -f net10.0 ./test/Mediator.Tests/ ``` -------------------------------- ### ConsoleAOT Benchmark Comparison Source: https://github.com/martinothamar/mediator/blob/main/samples/basic/ConsoleAOT/README.md Benchmark the ConsoleAOT application against the standard Console application using hyperfine. This command compares the execution speed of both applications. Note the warnings about potential inaccuracies for very fast commands and statistical outliers. ```console $ hyperfine './Console/bin/Release/net10.0/linux-x64/publish/Console' './ConsoleAOT/bin/Release/net10.0/linux-x64/publish/ConsoleAOT' ``` -------------------------------- ### Scaffold Blazor App Source: https://github.com/martinothamar/mediator/blob/main/samples/apps/ASPNET_CORE_Blazor/README.md Use the dotnet CLI to scaffold a new Blazor application with interactive server-side rendering and WebAssembly support. ```sh dotnet new blazor -int Auto ``` -------------------------------- ### Test Specific Lifetime Configuration Source: https://github.com/martinothamar/mediator/blob/main/CONTRIBUTING.md Execute the test suite with a particular dependency injection lifetime configuration. Options include Singleton, Scoped, and Transient. ```bash just test-lifetime Singleton ``` ```bash just test-lifetime Scoped ``` ```bash just test-lifetime Transient ``` -------------------------------- ### Implement FireAndForgetNotificationPublisher Source: https://github.com/martinothamar/mediator/blob/main/README.md A custom INotificationPublisher implementation that dispatches notifications to handlers in parallel using Task.WhenAll. Exceptions during notification handling are caught and logged, making the process fire-and-forget. ```csharp public sealed class FireAndForgetNotificationPublisher : INotificationPublisher { public async ValueTask Publish( NotificationHandlers handlers, TNotification notification, CancellationToken cancellationToken ) where TNotification : INotification { try { await Task.WhenAll(handlers.Select(handler => handler.Handle(notification, cancellationToken).AsTask())); } catch (Exception ex) { // Notifications should be fire-and-forget, we just need to log it! // This way we don't have to worry about exceptions bubbling up when publishing notifications Console.Error.WriteLine(ex); // NOTE: not necessarily saying this is a good idea! } } } ``` -------------------------------- ### Registering Stream Pipeline Behaviors in C# Source: https://github.com/martinothamar/mediator/blob/main/README.md Register stream pre- and post-processors as singleton stream pipeline behaviors. Ensure to use the correct generic types for your message and response. ```csharp services.AddSingleton(typeof(IStreamPipelineBehavior<,>), typeof(StreamLoggingPreProcessor<,>)) services.AddSingleton(typeof(IStreamPipelineBehavior<,>), typeof(StreamLoggingPostProcessor<,>)) ``` -------------------------------- ### Send a Request with Mediator Source: https://github.com/martinothamar/mediator/blob/main/README.md Demonstrates sending a request (`Ping`) using the `IMediator` service and receiving a response (`Pong`). The source generator automatically handles DI registrations for message types and handlers once they are defined. ```csharp services.AddMediator((MediatorOptions options) => options.Assemblies = [typeof(Ping)]); using var serviceProvider = services.BuildServiceProvider(); var mediator = serviceProvider.GetRequiredService(); var ping = new Ping(Guid.NewGuid()); var pong = await mediator.Send(ping); Debug.Assert(ping.Id == pong.Id); // ... public sealed record Ping(Guid Id) : IRequest; public sealed record Pong(Guid Id); public sealed class PingHandler : IRequestHandler { public ValueTask Handle(Ping request, CancellationToken cancellationToken) { return new ValueTask(new Pong(request.Id)); } } ``` -------------------------------- ### Command Dispatching with ICommand / ICommandHandler Source: https://context7.com/martinothamar/mediator/llms.txt Define commands for write operations and handlers to process them. Commands can return a response. Usage involves sending the command via the mediator. ```csharp using Mediator; // Command with response public sealed record CreateOrder(string ProductId, int Quantity) : ICommand; public sealed record OrderId(Guid Value); public sealed class CreateOrderHandler : ICommandHandler { public ValueTask Handle(CreateOrder command, CancellationToken cancellationToken) { var id = new OrderId(Guid.NewGuid()); Console.WriteLine($"Order created: {id.Value} for {command.Quantity}x {command.ProductId}"); return new ValueTask(id); } } // Command with no return value public sealed record CancelOrder(Guid OrderId) : ICommand; public sealed class CancelOrderHandler : ICommandHandler { public ValueTask Handle(CancelOrder command, CancellationToken cancellationToken) { Console.WriteLine($"Order {command.OrderId} cancelled"); return Unit.ValueTask; } } // --- Usage --- var orderId = await mediator.Send(new CreateOrder("SKU-42", 3)); await mediator.Send(new CancelOrder(orderId.Value)); ``` -------------------------------- ### Pipeline Behavior for Logging Source: https://context7.com/martinothamar/mediator/llms.txt Implement a generic pipeline behavior to log information about message handling, including execution time and errors. This behavior wraps other behaviors and handlers. ```csharp using Mediator; using Microsoft.Extensions.Logging; // Generic open behavior — applies to ALL messages implementing IMessage public sealed class LoggingBehavior : IPipelineBehavior where TMessage : notnull, IMessage { private readonly ILogger> _logger; public LoggingBehavior(ILogger> logger) => _logger = logger; public async ValueTask Handle( TMessage message, MessageHandlerDelegate next, CancellationToken cancellationToken) { _logger.LogInformation("Handling {MessageType}", typeof(TMessage).Name); var sw = System.Diagnostics.Stopwatch.StartNew(); try { var response = await next(message, cancellationToken); _logger.LogInformation("Handled {MessageType} in {ElapsedMs}ms", typeof(TMessage).Name, sw.ElapsedMilliseconds); return response; } catch (Exception ex) { _logger.LogError(ex, "Error handling {MessageType}", typeof(TMessage).Name); throw; } } } ``` -------------------------------- ### Run Memory Allocation Tests Source: https://github.com/martinothamar/mediator/blob/main/CONTRIBUTING.md Run tests designed to measure memory allocations. This is important for performance-sensitive code. ```bash just test-memory ``` -------------------------------- ### Configure Mediator with AddMediator Delegate Source: https://github.com/martinothamar/mediator/blob/main/README.md Use this delegate within the `AddMediator` function to configure Mediator options at runtime. This method allows for dynamic configuration and supports options not available via assembly attributes, such as arrays and lists. ```csharp services.AddMediator((MediatorOptions options) => { options.Namespace = "SimpleConsole.Mediator"; options.ServiceLifetime = ServiceLifetime.Singleton; // Only available from v3: options.GenerateTypesAsInternal = true; options.NotificationPublisherType = typeof(Mediator.ForeachAwaitPublisher); options.Assemblies = [typeof(...)]; options.Types = [typeof(IModuleMarker)]; options.PipelineBehaviors = []; options.StreamPipelineBehaviors = []; // Only available from v3.1: options.CachingMode = CachingMode.Eager; }); ``` -------------------------------- ### Define and Handle Request with Response Source: https://context7.com/martinothamar/mediator/llms.txt Define a request type implementing IRequest and its corresponding handler implementing IRequestHandler. The source generator automatically discovers and registers the handler. ```csharp using Mediator; using Microsoft.Extensions.DependencyInjection; // --- Message & response types --- public sealed record GetUserById(Guid UserId) : IRequest; public sealed record UserDto(Guid Id, string Name); // --- Handler (automatically discovered and registered by source generator) --- public sealed class GetUserByIdHandler : IRequestHandler { public ValueTask Handle(GetUserById request, CancellationToken cancellationToken) { // Simulate DB lookup var user = new UserDto(request.UserId, "Alice"); return new ValueTask(user); } } // --- Usage --- var services = new ServiceCollection(); services.AddMediator((MediatorOptions o) => o.Assemblies = [typeof(GetUserById).Assembly]); var sp = services.BuildServiceProvider(); var mediator = sp.GetRequiredService(); var user = await mediator.Send(new GetUserById(Guid.NewGuid())); Console.WriteLine(user.Name); // Alice ``` ```csharp // IRequest (no return value) — returns ValueTask public sealed record DeleteUser(Guid UserId) : IRequest; public sealed class DeleteUserHandler : IRequestHandler { public ValueTask Handle(DeleteUser request, CancellationToken cancellationToken) { Console.WriteLine($"Deleted {request.UserId}"); return Unit.ValueTask; } } ``` -------------------------------- ### Configure Notification Publisher Strategy Source: https://context7.com/martinothamar/mediator/llms.txt Control notification dispatch by setting NotificationPublisherType in MediatorOptions. Options include sequential (ForeachAwaitPublisher), parallel (TaskWhenAllPublisher), or a custom implementation like FireAndForgetPublisher. ```csharp using Mediator; // Built-in: ForeachAwaitPublisher (sequential, default) services.AddMediator((MediatorOptions options) => { options.NotificationPublisherType = typeof(ForeachAwaitPublisher); // default }); // Built-in: TaskWhenAllPublisher (parallel) services.AddMediator((MediatorOptions options) => { options.NotificationPublisherType = typeof(TaskWhenAllPublisher); }); // Custom: fire-and-forget — exceptions logged but not rethrown public sealed class FireAndForgetPublisher : INotificationPublisher { public async ValueTask Publish( NotificationHandlers handlers, TNotification notification, CancellationToken cancellationToken) where TNotification : INotification { try { await Task.WhenAll(handlers.Select(h => h.Handle(notification, cancellationToken).AsTask())); } catch (Exception ex) { Console.Error.WriteLine($"Notification handler failed: {ex}"); // Swallowed intentionally — fire-and-forget } } } services.AddMediator((MediatorOptions options) => { options.Assemblies = [typeof(OrderShipped).Assembly]; options.NotificationPublisherType = typeof(FireAndForgetPublisher); }); ``` -------------------------------- ### Object-Based Send/Publish with Dynamic Dispatch Source: https://context7.com/martinothamar/mediator/llms.txt Use object-based overloads on ISender and IPublisher when message types are unknown at compile time. This enables runtime type checks for routing messages to handlers. Handles exceptions for invalid or missing message handlers. ```csharp using Mediator; // ISender.Send(object) — returns ValueTask object unknownMessage = new GetUserById(Guid.NewGuid()); // from deserialization, reflection, etc. try { object? result = await mediator.Send(unknownMessage); Console.WriteLine(result); // UserDto { Id = ..., Name = Alice } } catch (InvalidMessageException ex) { // Thrown when the object does not implement IMessage Console.Error.WriteLine($"Not a valid Mediator message: {ex.MediatorMessage?.GetType().FullName}"); } catch (MissingMessageHandlerException ex) { // Thrown when no handler is registered for this message type Console.Error.WriteLine($"No handler found: {ex.MediatorMessage?.GetType().FullName}"); } // IPublisher.Publish(object) — fan-out to all INotificationHandlers object notification = new OrderShipped(Guid.NewGuid(), "1Z999AA"); await mediator.Publish(notification); // ISender.CreateStream(object) — returns IAsyncEnumerable object streamMsg = new StreamLogs("api", DateTimeOffset.UtcNow.AddHours(-1)); await foreach (var item in mediator.CreateStream(streamMsg)) { Console.WriteLine(item); // LogEntry { ... } } ``` -------------------------------- ### Add Mediator Packages using PackageReference Source: https://github.com/martinothamar/mediator/blob/main/README.md Include the Mediator.SourceGenerator and Mediator.Abstractions packages in your project file using the PackageReference format. The SourceGenerator package includes necessary assets for build-time integration. ```xml all runtime; build; native; contentfiles; analyzers ``` -------------------------------- ### Configure FireAndForgetNotificationPublisher Source: https://github.com/martinothamar/mediator/blob/main/README.md Configure the Mediator to use a custom FireAndForgetNotificationPublisher. This publisher dispatches notifications in parallel using Task.WhenAll and handles exceptions by logging them, ensuring notifications are fire-and-forget. ```csharp services.AddMediator((MediatorOptions options) => { options.NotificationPublisherType = typeof(FireAndForgetNotificationPublisher); }); ``` -------------------------------- ### Enable OpenTelemetry Telemetry in Mediator Source: https://context7.com/martinothamar/mediator/llms.txt Configure Mediator to emit OpenTelemetry metrics and traces by setting EnableMetrics and EnableTracing to true. Customize MeterName and ActivitySourceName if needed. Register the Mediator Meter and ActivitySource with the OpenTelemetry SDK. ```csharp using Mediator; using OpenTelemetry.Metrics; using OpenTelemetry.Trace; // Enable telemetry in Mediator configuration services.AddMediator((MediatorOptions options) => { options.Assemblies = [typeof(GetUserById).Assembly]; options.Telemetry.EnableMetrics = true; options.Telemetry.EnableTracing = true; options.Telemetry.MeterName = "MyApp.Mediator"; // default: "Mediator" options.Telemetry.ActivitySourceName = "MyApp.Mediator"; // default: "Mediator" options.Telemetry.HistogramBuckets = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0]; }); // Wire into OpenTelemetry SDK builder.Services .AddOpenTelemetry() .ConfigureResource(r => r.AddService("MyApp")) .WithMetrics(metrics => metrics .AddAspNetCoreInstrumentation() .AddMeter(Mediator.Mediator.MeterName) // use static property from generated class .AddOtlpExporter()) .WithTracing(tracing => tracing .AddAspNetCoreInstrumentation() .AddSource(Mediator.Mediator.ActivitySourceName) .AddOtlpExporter()); // Emitted signals: // Metric: messaging.process.duration (Histogram, seconds) // Traces: "send GetUserById" | "publish OrderShipped" | "createstream StreamLogs" // Attributes: // messaging.system = mediator // messaging.destination.name = // messaging.operation.type = process // messaging.mediator.message.kind = request|command|query|notification|stream* // error.type = (only on failure) ``` -------------------------------- ### Enable Mediator Telemetry Source: https://github.com/martinothamar/mediator/blob/main/README.md Configure Mediator to enable metrics and tracing using BCL APIs. You can optionally set custom names for the Meter and ActivitySource. ```csharp services.AddMediator((MediatorOptions options) => { options.Telemetry.EnableMetrics = true; options.Telemetry.EnableTracing = true; options.Telemetry.MeterName = ""; options.Telemetry.ActivitySourceName = ""; }); ``` -------------------------------- ### Implement Cache Response Post-Processor Source: https://context7.com/martinothamar/mediator/llms.txt Use MessagePostProcessor for response-aware side effects after a handler executes, such as caching or audit logging. Register via DI. ```csharp using Mediator; public sealed class CacheResponsePostProcessor : MessagePostProcessor where TMessage : notnull, IMessage { protected override ValueTask Handle(TMessage message, TResponse response, CancellationToken cancellationToken) { // Cache the response keyed by message content Console.WriteLine($"[PostProcess] Caching response of type {typeof(TResponse).Name} for {typeof(TMessage).Name}"); return default; } } services.AddSingleton(typeof(IPipelineBehavior<,>), typeof(CacheResponsePostProcessor<,>)); ``` -------------------------------- ### Consume Streaming Messages with IAsyncEnumerable Source: https://github.com/martinothamar/mediator/blob/main/README.md Demonstrates how to consume streaming messages using IAsyncEnumerable. The mediator's CreateStream method returns an IAsyncEnumerable that can be iterated over using await foreach. ```csharp var mediator = serviceProvider.GetRequiredService(); var ping = new StreamPing(Guid.NewGuid()); await foreach (var pong in mediator.CreateStream(ping)) { Debug.Assert(ping.Id == pong.Id); Console.WriteLine("Received pong!"); // Should log 5 times } ``` -------------------------------- ### Implement Open Generic Error Logger Pipeline Behavior Source: https://github.com/martinothamar/mediator/blob/main/README.md An open generic pipeline behavior that logs exceptions during message handling and publishes an ErrorMessage notification. It is constrained to messages implementing IMessage. ```csharp public sealed record ErrorMessage(Exception Exception) : INotification; public sealed record SuccessfulMessage() : INotification; public sealed class ErrorLoggerHandler : IPipelineBehavior where TMessage : IMessage // Constrained to IMessage, or constrain to IBaseCommand or any custom interface you've implemented { private readonly ILogger> _logger; private readonly IMediator _mediator; public ErrorLoggerHandler(ILogger> logger, IMediator mediator) { _logger = logger; _mediator = mediator; } public async ValueTask Handle(TMessage message, MessageHandlerDelegate next, CancellationToken cancellationToken) { try { var response = await next(message, cancellationToken); return response; } catch (Exception ex) { _logger.LogError(ex, "Error handling message"); await _mediator.Publish(new ErrorMessage(ex), cancellationToken); throw; } } } ```