{"id":15131209,"url":"https://github.com/martinothamar/mediator","last_synced_at":"2026-02-08T20:15:46.451Z","repository":{"id":37555365,"uuid":"353349406","full_name":"martinothamar/Mediator","owner":"martinothamar","description":"A high performance implementation of Mediator pattern in .NET using source generators.","archived":false,"fork":false,"pushed_at":"2024-06-23T08:02:12.000Z","size":921,"stargazers_count":2324,"open_issues_count":40,"forks_count":83,"subscribers_count":41,"default_branch":"main","last_synced_at":"2025-04-03T22:05:50.666Z","etag":null,"topics":["csharp","csharp-sourcegenerator","dotnet","dotnet-core","dotnet-standard","dotnetcore","mediator","mediator-pattern","source-gen","source-generation","source-generators","sourcegenerator"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/martinothamar.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-03-31T12:30:30.000Z","updated_at":"2025-04-03T20:54:53.000Z","dependencies_parsed_at":"2024-01-06T02:09:59.973Z","dependency_job_id":"a493592a-7091-4660-84de-9684c3f1bdb5","html_url":"https://github.com/martinothamar/Mediator","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/martinothamar%2FMediator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/martinothamar%2FMediator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/martinothamar%2FMediator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/martinothamar%2FMediator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/martinothamar","download_url":"https://codeload.github.com/martinothamar/Mediator/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248324465,"owners_count":21084719,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["csharp","csharp-sourcegenerator","dotnet","dotnet-core","dotnet-standard","dotnetcore","mediator","mediator-pattern","source-gen","source-generation","source-generators","sourcegenerator"],"created_at":"2024-09-26T03:24:30.478Z","updated_at":"2026-02-08T20:15:46.442Z","avatar_url":"https://github.com/martinothamar.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![GitHub](https://img.shields.io/github/license/martinothamar/Mediator?style=flat-square)](https://github.com/martinothamar/Mediator/blob/main/LICENSE)\n[![Downloads](https://img.shields.io/nuget/dt/mediator.abstractions?style=flat-square)](https://www.nuget.org/packages/Mediator.Abstractions/)\u003cbr/\u003e\n[![Abstractions NuGet current](https://img.shields.io/nuget/v/Mediator.Abstractions?label=Mediator.Abstractions)](https://www.nuget.org/packages/Mediator.Abstractions)\n[![SourceGenerator NuGet current](https://img.shields.io/nuget/v/Mediator.SourceGenerator?label=Mediator.SourceGenerator)](https://www.nuget.org/packages/Mediator.SourceGenerator)\u003cbr/\u003e\n[![Abstractions NuGet prerelease](https://img.shields.io/nuget/vpre/Mediator.Abstractions?label=Mediator.Abstractions)](https://www.nuget.org/packages/Mediator.Abstractions)\n[![SourceGenerator NuGet prerelease](https://img.shields.io/nuget/vpre/Mediator.SourceGenerator?label=Mediator.SourceGenerator)](https://www.nuget.org/packages/Mediator.SourceGenerator)\u003cbr/\u003e\n\n\u003e [!NOTE]\n\u003e **Want to contribute?** See the [Contributing Guide](CONTRIBUTING.md) for information on building, testing, and submitting changes.\n\n# Mediator\n\nThis is a high performance .NET implementation of the Mediator pattern using [source generators](https://devblogs.microsoft.com/dotnet/introducing-c-source-generators/).\nIt provides a similar API to the great [MediatR](https://github.com/LuckyPennySoftware/MediatR) library while delivering better performance and full Native AOT support.\nPackages target .NET Standard 2.0 and .NET 8.\n\nThe mediator pattern is great for implementing cross cutting concerns (logging, metrics, etc) and avoiding \"fat\" constructors due to lots of injected services.\n\nGoals for this library\n* High performance\n  * Efficient and fast by default, slower configurations are opt-in (in the spirit of [non-pessimization](https://stackoverflow.com/questions/32618848/what-is-pessimization))\n  * See [benchmarks](#2-benchmarks) for comparisons\n* AOT friendly\n  * Full Native AOT support without reflection or runtime code generation\n  * Cold start performance matters for lots of scenarios (serverless, edge, apps, mobile)\n* Build time errors instead of runtime errors\n  * The generator includes diagnostics (example: if a handler is not defined for a request, a warning is emitted)\n  * Catch configuration mistakes during development, not during runtime\n* Stability\n  * Stable API that only changes for good reason - fewer changes means less patching for you\n  * Follows [semantic versioning](#7-versioning) strictly\n\nIn particular, a source generator in this library is used to\n* Generate code for DI registration\n* Generate code for the `IMediator` implementation\n  * Request/Command/Query `Send` methods are monomorphized\n  * Fast dictionary lookups for methods taking `object`/generic arguments (takes effect above a certain project size threshold)\n  * You can use both `IMediator` and the concrete `Mediator` class, the latter allows for better performance\n* Generate diagnostics related messages and message handlers\n\nNuGet packages:\n\n```pwsh\ndotnet add package Mediator.SourceGenerator --version 3.0.*\ndotnet add package Mediator.Abstractions --version 3.0.*\n```\nor\n```xml\n\u003cPackageReference Include=\"Mediator.SourceGenerator\" Version=\"3.0.*\"\u003e\n  \u003cPrivateAssets\u003eall\u003c/PrivateAssets\u003e\n  \u003cIncludeAssets\u003eruntime; build; native; contentfiles; analyzers\u003c/IncludeAssets\u003e\n\u003c/PackageReference\u003e\n\u003cPackageReference Include=\"Mediator.Abstractions\" Version=\"3.0.*\" /\u003e\n```\n\nSee this great video by [@Elfocrash / Nick Chapsas](https://github.com/Elfocrash), covering both similarities and differences between Mediator and MediatR\n\n[![Using MediatR in .NET? Maybe replace it with this](https://img.youtube.com/vi/aaFLtcf8cO4/0.jpg)](https://www.youtube.com/watch?v=aaFLtcf8cO4)\n\n## Table of Contents\n\n- [Mediator](#mediator)\n  - [Table of Contents](#table-of-contents)\n  - [2. Benchmarks](#2-benchmarks)\n  - [3. Usage and abstractions](#3-usage-and-abstractions)\n    - [3.1. Message types](#31-message-types)\n    - [3.2. Handler types](#32-handler-types)\n    - [3.3. Pipeline types](#33-pipeline-types)\n      - [3.3.1. Message validation example](#331-message-validation-example)\n      - [3.3.2. Error logging example](#332-error-logging-example)\n      - [3.3.3. Stream message processing example](#333-stream-message-processing-example)\n    - [3.4. Configuration](#34-configuration)\n  - [4. Getting started](#4-getting-started)\n    - [4.1. Add packages](#41-add-packages)\n    - [4.2. Add Mediator to DI container](#42-add-mediator-to-di-container)\n    - [4.3. Create `IRequest\u003c\u003e` type](#43-create-irequest-type)\n    - [4.4. Use pipeline behaviors](#44-use-pipeline-behaviors)\n    - [4.5. Constrain `IPipelineBehavior\u003c,\u003e` message with open generics](#45-constrain-ipipelinebehavior-message-with-open-generics)\n    - [4.6. Use notifications](#46-use-notifications)\n    - [4.7. Polymorphic dispatch with notification handlers](#47-polymorphic-dispatch-with-notification-handlers)\n    - [4.8. Notification handlers also support open generics](#48-notification-handlers-also-support-open-generics)\n    - [4.9. Notification publishers](#49-notification-publishers)\n    - [4.10. Use streaming messages](#410-use-streaming-messages)\n  - [5. Diagnostics](#5-diagnostics)\n  - [6. Differences from MediatR](#6-differences-from-mediatr)\n  - [7. Versioning](#7-versioning)\n  - [8. Related projects](#8-related-projects)\n\n## 2. Benchmarks\n\nHere is a brief comparison benchmark hightighting the difference between MediatR and this library using `Singleton` lifetime.\nNote that this library yields the best performance when using the `Singleton` service lifetime.\n\n* `\u003cColdStart | Notification | Request | StreamRequest\u003e_Mediator`: the concrete `Mediator` class generated by this library\n* `\u003cColdStart | Notification | Request | StreamRequest\u003e_IMediator`: call through the `IMediator` interface in this library\n* `\u003cColdStart | Notification | Request | StreamRequest\u003e_MediatR`: the [MediatR](https://github.com/jbogard/MediatR) library\n\nBenchmark category descriptions\n* `ColdStart` - time to resolve `IMediator` from `IServiceProvider` and send a single request\n* `Notification` - publish a single notification\n* `Request` - publish a single request\n* `StreamRequest` - stream a single request which yields 3 responses without delay\n\nSee the [benchmarks/ folder](/benchmarks/README.md) for more detailed information, including varying lifetimes and project sizes.\n\n![Benchmarks](/img/benchmarks.png \"Benchmarks\")\n\n## 3. Usage and abstractions\n\nThere are two NuGet packages needed to use this library\n* Mediator.SourceGenerator\n  * To generate the `IMediator` implementation and dependency injection setup.\n* Mediator.Abstractions\n  * Message types (`IRequest\u003c,\u003e`, `INotification`), handler types (`IRequestHandler\u003c,\u003e`, `INotificationHandler\u003c\u003e`), pipeline types (`IPipelineBehavior`)\n\nYou install the source generator package into your edge/outermost project (i.e. ASP.NET Core application, Background worker project),\nand then use the `Mediator.Abstractions` package wherever you define message types and handlers.\nStandard message handlers are automatically picked up and added to the DI container in the generated `AddMediator` method.\n*Pipeline behaviors need to be added manually (including pre/post/exception behaviors).*\n\nFor example implementations, see the [/samples](/samples) folder.\nSee the [ASP.NET Core clean architecture sample](/samples/apps/ASPNET_Core_CleanArchitecture) for a more real world setup.\n\n### 3.1. Message types\n\n* `IMessage` - marker interface\n* `IStreamMessage` - marker interface\n* `IBaseRequest` - marker interface for requests\n* `IRequest` - a request message, no return value (`ValueTask\u003cUnit\u003e`)\n* `IRequest\u003cout TResponse\u003e` - a request message with a response (`ValueTask\u003cTResponse\u003e`)\n* `IStreamRequest\u003cout TResponse\u003e` - a request message with a streaming response (`IAsyncEnumerable\u003cTResponse\u003e`)\n* `IBaseCommand` - marker interface for commands\n* `ICommand` - a command message, no return value (`ValueTask\u003cUnit\u003e`)\n* `ICommand\u003cout TResponse\u003e` - a command message with a response (`ValueTask\u003cTResponse\u003e`)\n* `IStreamCommand\u003cout TResponse\u003e` - a command message with a streaming response (`IAsyncEnumerable\u003cTResponse\u003e`)\n* `IBaseQuery` - marker interface for queries\n* `IQuery\u003cout TResponse\u003e` - a query message with a response (`ValueTask\u003cTResponse\u003e`)\n* `IStreamQuery\u003cout TResponse\u003e` - a query message with a streaming response (`IAsyncEnumerable\u003cTResponse\u003e`)\n* `INotification` - a notification message, no return value (`ValueTask`)\n\nAs you can see, you can achieve the exact same thing with requests, commands and queries. But I find the distinction in naming useful if you for example use the CQRS pattern or for some reason have a preference on naming in your application.\n\n### 3.2. Handler types\n\n* `IRequestHandler\u003cin TRequest\u003e`\n* `IRequestHandler\u003cin TRequest, TResponse\u003e`\n* `IStreamRequestHandler\u003cin TRequest, out TResponse\u003e`\n* `ICommandHandler\u003cin TCommand\u003e`\n* `ICommandHandler\u003cin TCommand, TResponse\u003e`\n* `IStreamCommandHandler\u003cin TCommand, out TResponse\u003e`\n* `IQueryHandler\u003cin TQuery, TResponse\u003e`\n* `IStreamQueryHandler\u003cin TQuery, out TResponse\u003e`\n* `INotificationHandler\u003cin TNotification\u003e`\n\nThese types are used in correlation with the message types above.\n\n### 3.3. Pipeline types\n\n* `IPipelineBehavior\u003cTMessage, TResponse\u003e`\n* `IStreamPipelineBehavior\u003cTMessage, TResponse\u003e`\n* `MessagePreProcessor\u003cTMessage, TResponse\u003e`\n* `MessagePostProcessor\u003cTMessage, TResponse\u003e`\n* `StreamMessagePreProcessor\u003cTMessage, TResponse\u003e`\n* `StreamMessagePostProcessor\u003cTMessage, TResponse\u003e`\n* `MessageExceptionHandler\u003cTMessage, TResponse, TException\u003e`\n\n#### 3.3.1. Message validation example\n\n```csharp\n// As a normal pipeline behavior\npublic sealed class MessageValidatorBehaviour\u003cTMessage, TResponse\u003e : IPipelineBehavior\u003cTMessage, TResponse\u003e\n    where TMessage : IValidate\n{\n    public ValueTask\u003cTResponse\u003e Handle(\n        TMessage message,\n        MessageHandlerDelegate\u003cTMessage, TResponse\u003e next,\n        CancellationToken cancellationToken\n    )\n    {\n        if (!message.IsValid(out var validationError))\n            throw new ValidationException(validationError);\n\n        return next(message, cancellationToken);\n    }\n}\n\n// Or as a pre-processor\npublic sealed class MessageValidatorBehaviour\u003cTMessage, TResponse\u003e : MessagePreProcessor\u003cTMessage, TResponse\u003e\n    where TMessage : IValidate\n{\n    protected override ValueTask Handle(TMessage message, CancellationToken cancellationToken)\n    {\n        if (!message.IsValid(out var validationError))\n            throw new ValidationException(validationError);\n\n        return default;\n    }\n}\n\n// Register as IPipelineBehavior\u003c,\u003e in either case\n// NOTE: for NativeAOT, you should use the pipeline configuration on `MediatorOptions` instead (during `AddMediator`)\nservices.AddSingleton(typeof(IPipelineBehavior\u003c,\u003e), typeof(MessageValidatorBehaviour\u003c,\u003e))\n```\n\n#### 3.3.2. Error logging example\n\n```csharp\n// As a normal pipeline behavior\npublic sealed class ErrorLoggingBehaviour\u003cTMessage, TResponse\u003e : IPipelineBehavior\u003cTMessage, TResponse\u003e\n    where TMessage : IMessage\n{\n    private readonly ILogger\u003cErrorLoggingBehaviour\u003cTMessage, TResponse\u003e\u003e _logger;\n\n    public ErrorLoggingBehaviour(ILogger\u003cErrorLoggingBehaviour\u003cTMessage, TResponse\u003e\u003e logger)\n    {\n        _logger = logger;\n    }\n\n    public async ValueTask\u003cTResponse\u003e Handle(\n        TMessage message,\n        MessageHandlerDelegate\u003cTMessage, TResponse\u003e next,\n        CancellationToken cancellationToken\n    )\n    {\n        try\n        {\n            return await next(message, cancellationToken);\n        }\n        catch (Exception ex)\n        {\n            _logger.LogError(ex, \"Error handling message of type {messageType}\", message.GetType().Name);\n            throw;\n        }\n    }\n}\n\n// Or as an exception handler\npublic sealed class ErrorLoggingBehaviour\u003cTMessage, TResponse\u003e : MessageExceptionHandler\u003cTMessage, TResponse\u003e\n    where TMessage : notnull, IMessage\n{\n    private readonly ILogger\u003cErrorLoggingBehaviour\u003cTMessage, TResponse\u003e\u003e _logger;\n\n    public ErrorLoggingBehaviour(ILogger\u003cErrorLoggingBehaviour\u003cTMessage, TResponse\u003e\u003e logger)\n    {\n        _logger = logger;\n    }\n\n    protected override ValueTask\u003cExceptionHandlingResult\u003cTResponse\u003e\u003e Handle(\n        TMessage message,\n        Exception exception,\n        CancellationToken cancellationToken\n    )\n    {\n        _logger.LogError(exception, \"Error handling message of type {messageType}\", message.GetType().Name);\n        // Let the exception bubble up by using the base class helper NotHandled:\n        return NotHandled;\n        // Or if the exception is properly handled, you can just return your own response,\n        // using the base class helper Handle().\n        // This requires you to know something about TResponse,\n        // so TResponse needs to be constrained to something,\n        // typically with a static abstract member acting as a consructor on an interface or abstract class.\n        return Handled(null!);\n    }\n}\n\n// Register as IPipelineBehavior\u003c,\u003e in either case\nservices.AddSingleton(typeof(IPipelineBehavior\u003c,\u003e), typeof(ErrorLoggingBehaviour\u003c,\u003e))\n```\n\n#### 3.3.3. Stream message processing example\n\nFor streaming messages (e.g., `IStreamRequest\u003cTResponse\u003e`, `IStreamCommand\u003cTResponse\u003e`, `IStreamQuery\u003cTResponse\u003e`), you can use `StreamMessagePreProcessor` and `StreamMessagePostProcessor` to handle pre/post-processing logic.\nNOTE: `StreamMessagePostProcessor` implementations will need to buffer all responses so that they can be passed as an `IReadOnlyList\u003c\u003e` to the `Handle` method.\n\n```csharp\n// Stream message pre-processor - runs once before stream starts\npublic sealed class StreamLoggingPreProcessor\u003cTMessage, TResponse\u003e : StreamMessagePreProcessor\u003cTMessage, TResponse\u003e\n    where TMessage : notnull, IStreamMessage\n{\n    private readonly ILogger\u003cStreamLoggingPreProcessor\u003cTMessage, TResponse\u003e\u003e _logger;\n\n    public StreamLoggingPreProcessor(ILogger\u003cStreamLoggingPreProcessor\u003cTMessage, TResponse\u003e\u003e logger)\n    {\n        _logger = logger;\n    }\n\n    protected override ValueTask Handle(TMessage message, CancellationToken cancellationToken)\n    {\n        _logger.LogInformation(\"Starting stream processing for {MessageType}\", typeof(TMessage).Name);\n        return default;\n    }\n}\n\n// Stream message post-processor - runs once after stream completes\npublic sealed class StreamLoggingPostProcessor\u003cTMessage, TResponse\u003e : StreamMessagePostProcessor\u003cTMessage, TResponse\u003e\n    where TMessage : notnull, IStreamMessage\n{\n    private readonly ILogger\u003cStreamLoggingPostProcessor\u003cTMessage, TResponse\u003e\u003e _logger;\n\n    public StreamLoggingPostProcessor(ILogger\u003cStreamLoggingPostProcessor\u003cTMessage, TResponse\u003e\u003e logger)\n    {\n        _logger = logger;\n    }\n\n    protected override ValueTask Handle(TMessage message, IReadOnlyList\u003cTResponse\u003e responses, CancellationToken cancellationToken)\n    {\n        _logger.LogInformation(\"Stream completed with {Count} items\", responses.Count);\n        return default;\n    }\n}\n\n// Register as IStreamPipelineBehavior\u003c,\u003e\n// NOTE: for NativeAOT, you should use the pipeline configuration on `MediatorOptions` instead (during `AddMediator`)\nservices.AddSingleton(typeof(IStreamPipelineBehavior\u003c,\u003e), typeof(StreamLoggingPreProcessor\u003c,\u003e))\nservices.AddSingleton(typeof(IStreamPipelineBehavior\u003c,\u003e), typeof(StreamLoggingPostProcessor\u003c,\u003e))\n```\n\n### 3.4. Configuration\n\nThere are two ways to configure Mediator. Configuration values are needed during compile-time since this is a source generator:\n* Assembly level attribute for configuration: `MediatorOptionsAttribute`\n* Options configuration delegate in `AddMediator` function.\n\n```csharp\nservices.AddMediator((MediatorOptions options) =\u003e\n{\n    options.Namespace = \"SimpleConsole.Mediator\";\n    options.ServiceLifetime = ServiceLifetime.Singleton;\n    // Only available from v3:\n    options.GenerateTypesAsInternal = true;\n    options.NotificationPublisherType = typeof(Mediator.ForeachAwaitPublisher);\n    options.Assemblies = [typeof(...)];\n    options.PipelineBehaviors = [];\n    options.StreamPipelineBehaviors = [];\n    // Only available from v3.1:\n    options.CachingMode = CachingMode.Eager;\n});\n\n// or\n\n[assembly: MediatorOptions(Namespace = \"SimpleConsole.Mediator\", ServiceLifetime = ServiceLifetime.Singleton, CachingMode = CachingMode.Eager)]\n```\n\n* `Namespace` - where the `IMediator` implementation is generated\n* `ServiceLifetime` - the DI service lifetime\n  * `Singleton` - (default value) everything registered as singletons, minimal allocations\n  * `Transient` - mediator and handlers registered as transient\n  * `Scoped`    - mediator and handlers registered as scoped\n* `GenerateTypesAsInternal` - makes all generated types `internal` as opposed to `public`, which may be necessary depending on project setup (e.g. for [Blazor sample](/samples/apps/ASPNET_CORE_Blazor))\n* `NotificationPublisherType` - the type used for publishing notifications (`ForeachAwaitPublisher` and `TaskWhenAllPublisher` are built in)\n* `Assemblies` - which assemblies the source generator should scan for messages and handlers. When not used the source generator will scan all references assemblies (same behavior as v2)\n* `PipelineBehaviors`/`StreamPipelineBehaviors` - ordered array of types used for the pipeline\n  * The source generator adds DI regristrations manually as oppposed to open generics registrations, to support NativeAOT. You can also manually add pipeline behaviors to the DI container if you are not doing AoT compilation.\n* `CachingMode` - controls when Mediator initialization occurs\n  * `Eager` (default) - all handler wrappers and lookups are initialized on first Mediator access, best for long-running applications where startup cost is amortized\n  * `Lazy` - handler wrappers are initialized on-demand as messages are processed, best for cold start scenarios (serverless, Native AOT) where minimal initialization is preferred\n\nNote that since parsing of these options is done during compilation/source generation, all values must be compile time constants.\nIn addition, since some types are not valid attributes parameter types (such as arrays/lists), some configuration is only available through `AddMediator`/`MediatorOptions` and not the assembly attribute.\n\nSingleton lifetime is highly recommended as it yields the best performance.\nEvery application is different, but it is likely that a lot of your message handlers doesn't keep state and have no need for transient or scoped lifetime.\nIn a lot of cases those lifetimes only allocate lots of memory for no particular reason.\n\n## 4. Getting started\n\nIn this section we will get started with Mediator and go through a sample\nillustrating the various ways the Mediator pattern can be used in an application.\n\nSee the full runnable sample code in the [Showcase sample](/samples/Showcase/).\n\n### 4.1. Add packages\n\n```pwsh\ndotnet add package Mediator.SourceGenerator --version 3.0.*\ndotnet add package Mediator.Abstractions --version 3.0.*\n```\nor\n```xml\n\u003cPackageReference Include=\"Mediator.SourceGenerator\" Version=\"3.0.*\"\u003e\n  \u003cPrivateAssets\u003eall\u003c/PrivateAssets\u003e\n  \u003cIncludeAssets\u003eruntime; build; native; contentfiles; analyzers\u003c/IncludeAssets\u003e\n\u003c/PackageReference\u003e\n\u003cPackageReference Include=\"Mediator.Abstractions\" Version=\"3.0.*\" /\u003e\n```\n\n### 4.2. Add Mediator to DI container\n\nIn `ConfigureServices` or equivalent, call `AddMediator` (unless `MediatorOptions` is configured, default namespace is `Mediator`).\nThis registers your handler below.\n\n```csharp\nusing Mediator;\nusing Microsoft.Extensions.DependencyInjection;\nusing System;\n\nvar services = new ServiceCollection(); // Most likely IServiceCollection comes from IHostBuilder/Generic host abstraction in Microsoft.Extensions.Hosting\n\nservices.AddMediator();\nusing var serviceProvider = services.BuildServiceProvider();\n```\n\n### 4.3. Create `IRequest\u003c\u003e` type\n\n```csharp\nservices.AddMediator((MediatorOptions options) =\u003e options.Assemblies = [typeof(Ping)]);\nusing var serviceProvider = services.BuildServiceProvider();\nvar mediator = serviceProvider.GetRequiredService\u003cIMediator\u003e();\nvar ping = new Ping(Guid.NewGuid());\nvar pong = await mediator.Send(ping);\nDebug.Assert(ping.Id == pong.Id);\n\n// ...\n\npublic sealed record Ping(Guid Id) : IRequest\u003cPong\u003e;\n\npublic sealed record Pong(Guid Id);\n\npublic sealed class PingHandler : IRequestHandler\u003cPing, Pong\u003e\n{\n    public ValueTask\u003cPong\u003e Handle(Ping request, CancellationToken cancellationToken)\n    {\n        return new ValueTask\u003cPong\u003e(new Pong(request.Id));\n    }\n}\n```\n\nAs soon as you code up message types, the source generator will add DI registrations automatically (inside `AddMediator`).\nP.S - You can inspect the code yourself - open `Mediator.g.cs` in VS from Project -\u003e Dependencies -\u003e Analyzers -\u003e Mediator.SourceGenerator -\u003e Mediator.SourceGenerator.MediatorGenerator,\nor just F12 through the code.\n\n### 4.4. Use pipeline behaviors\n\nThe pipeline behavior below validates all incoming `Ping` messages.\nPipeline behaviors currently must be added manually.\n\n```csharp\nservices.AddMediator((MediatorOptions options) =\u003e\n{\n    options.Assemblies = [typeof(Ping)];\n    options.PipelineBehaviors = [typeof(PingValidator)];\n});\n\npublic sealed class PingValidator : IPipelineBehavior\u003cPing, Pong\u003e\n{\n    public ValueTask\u003cPong\u003e Handle(Ping request, MessageHandlerDelegate\u003cPing, Pong\u003e next, CancellationToken cancellationToken)\n    {\n        if (request is null || request.Id == default)\n            throw new ArgumentException(\"Invalid input\");\n\n        return next(request, cancellationToken);\n    }\n}\n```\n\n### 4.5. Constrain `IPipelineBehavior\u003c,\u003e` message with open generics\n\nAdd open generic handler to process all or a subset of messages passing through Mediator.\nThis handler will log any error that is thrown from message handlers (`IRequest`, `ICommand`, `IQuery`).\nIt also publishes a notification allowing notification handlers to react to errors.\nMessage pre- and post-processors along with the exception handlers can also constrain the generic type parameters in the same way.\n\n```csharp\nservices.AddMediator((MediatorOptions options) =\u003e\n{\n    options.Assemblies = [typeof(ErrorMessage)];\n    options.PipelineBehaviors = [typeof(ErrorLoggerHandler\u003c,\u003e)];\n});\n\npublic sealed record ErrorMessage(Exception Exception) : INotification;\npublic sealed record SuccessfulMessage() : INotification;\n\npublic sealed class ErrorLoggerHandler\u003cTMessage, TResponse\u003e : IPipelineBehavior\u003cTMessage, TResponse\u003e\n    where TMessage : IMessage // Constrained to IMessage, or constrain to IBaseCommand or any custom interface you've implemented\n{\n    private readonly ILogger\u003cErrorLoggerHandler\u003cTMessage, TResponse\u003e\u003e _logger;\n    private readonly IMediator _mediator;\n\n    public ErrorLoggerHandler(ILogger\u003cErrorLoggerHandler\u003cTMessage, TResponse\u003e\u003e logger, IMediator mediator)\n    {\n        _logger = logger;\n        _mediator = mediator;\n    }\n\n    public async ValueTask\u003cTResponse\u003e Handle(TMessage message, MessageHandlerDelegate\u003cTMessage, TResponse\u003e next, CancellationToken cancellationToken)\n    {\n        try\n        {\n            var response = await next(message, cancellationToken);\n            return response;\n        }\n        catch (Exception ex)\n        {\n            _logger.LogError(ex, \"Error handling message\");\n            await _mediator.Publish(new ErrorMessage(ex), cancellationToken);\n            throw;\n        }\n    }\n}\n```\n\n### 4.6. Use notifications\n\nWe can define a notification handler to catch errors from the above pipeline behavior.\n\n```csharp\n// Notification handlers are automatically added to DI container\n\npublic sealed class ErrorNotificationHandler : INotificationHandler\u003cErrorMessage\u003e\n{\n    public ValueTask Handle(ErrorMessage error, CancellationToken cancellationToken)\n    {\n        // Could log to application insights or something...\n        return default;\n    }\n}\n```\n\n### 4.7. Polymorphic dispatch with notification handlers\n\nWe can also define a notification handler that receives all notifications.\nNote that polymorphic dispatch does not work with struct notifications.\n\n```csharp\n\npublic sealed class StatsNotificationHandler : INotificationHandler\u003cINotification\u003e // or any other interface deriving from INotification\n{\n    private long _messageCount;\n    private long _messageErrorCount;\n\n    public (long MessageCount, long MessageErrorCount) Stats =\u003e (_messageCount, _messageErrorCount);\n\n    public ValueTask Handle(INotification notification, CancellationToken cancellationToken)\n    {\n        Interlocked.Increment(ref _messageCount);\n        if (notification is ErrorMessage)\n            Interlocked.Increment(ref _messageErrorCount);\n        return default;\n    }\n}\n```\n\n### 4.8. Notification handlers also support open generics\n\n```csharp\npublic sealed class GenericNotificationHandler\u003cTNotification\u003e : INotificationHandler\u003cTNotification\u003e\n    where TNotification : INotification // Generic notification handlers will be registered as open constrained types automatically\n{\n    public ValueTask Handle(TNotification notification, CancellationToken cancellationToken)\n    {\n        return default;\n    }\n}\n```\n\n### 4.9. Notification publishers\n\nNotification publishers are responsible for dispatching notifications to a collection of handlers.\nThere are two built in implementations:\n\n* `ForeachAwaitPublisher` - the default, dispatches the notifications to handlers in order 1-by-1\n* `TaskWhenAllPublisher` - dispatches notifications in parallel\n\nBoth of these try to be efficient by handling a number of special cases (early exit on sync completion, single-handler, array of handlers).\nBelow we implement a custom one by simply using `Task.WhenAll`.\n\n```csharp\nservices.AddMediator((MediatorOptions options) =\u003e\n{\n    options.NotificationPublisherType = typeof(FireAndForgetNotificationPublisher);\n});\n\npublic sealed class FireAndForgetNotificationPublisher : INotificationPublisher\n{\n    public async ValueTask Publish\u003cTNotification\u003e(\n        NotificationHandlers\u003cTNotification\u003e handlers,\n        TNotification notification,\n        CancellationToken cancellationToken\n    )\n        where TNotification : INotification\n    {\n        try\n        {\n            await Task.WhenAll(handlers.Select(handler =\u003e handler.Handle(notification, cancellationToken).AsTask()));\n        }\n        catch (Exception ex)\n        {\n            // Notifications should be fire-and-forget, we just need to log it!\n            // This way we don't have to worry about exceptions bubbling up when publishing notifications\n            Console.Error.WriteLine(ex);\n\n            // NOTE: not necessarily saying this is a good idea!\n        }\n    }\n}\n```\n\n\n### 4.10. Use streaming messages\n\nSince version 1.* of this library there is support for streaming using `IAsyncEnumerable`.\n\n```csharp\nvar mediator = serviceProvider.GetRequiredService\u003cIMediator\u003e();\n\nvar ping = new StreamPing(Guid.NewGuid());\n\nawait foreach (var pong in mediator.CreateStream(ping))\n{\n    Debug.Assert(ping.Id == pong.Id);\n    Console.WriteLine(\"Received pong!\"); // Should log 5 times\n}\n\n// ...\n\npublic sealed record StreamPing(Guid Id) : IStreamRequest\u003cPong\u003e;\n\npublic sealed record Pong(Guid Id);\n\npublic sealed class PingHandler : IStreamRequestHandler\u003cStreamPing, Pong\u003e\n{\n    public async IAsyncEnumerable\u003cPong\u003e Handle(StreamPing request, [EnumeratorCancellation] CancellationToken cancellationToken)\n    {\n        for (int i = 0; i \u003c 5; i++)\n        {\n            await Task.Delay(1000, cancellationToken);\n            yield return new Pong(request.Id);\n        }\n    }\n}\n```\n\n## 5. Diagnostics\n\nSince this is a source generator, diagnostics are also included. Examples below\n\n* Missing request handler\n\n![Missing request handler](/img/missing_request_handler.png \"Missing request handler\")\n\n* Multiple request handlers found\n\n![Multiple request handlers found](/img/multiple_request_handlers.png \"Multiple request handlers found\")\n\n\n## 6. Differences from [MediatR](https://github.com/LuckyPennySoftware/MediatR)\n\nThis is a work in progress list on the differences between this library and MediatR.\n\n* `RequestHandlerDelegate\u003cTResponse\u003e()` -\u003e `MessageHandlerDelegate\u003cTMessage, TResponse\u003e(TMessage message, CancellationToken cancellationToken)`\n  * This is to avoid excessive closure allocations. I think it's worthwhile when the cost is simply passing along the message and the cancellation token.\n* No `ServiceFactory`\n  * This library relies on the `Microsoft.Extensions.DependencyInjection`, so it only works with DI containers that integrate with those abstractions.\n* Singleton service lifetime by default\n  * MediatR in combination with `MediatR.Extensions.Microsoft.DependencyInjection` does transient service registration by default, which leads to a lot of allocations. Even if it is configured for singleton lifetime, `IMediator` and `ServiceFactory` services are registered as transient (not configurable).\n* Methods return `ValueTask\u003cT\u003e` instead of `Task\u003cT\u003e`, to allow for fewer allocations (for example if the handler completes synchronously, or using async method builder pooling/`PoolingAsyncValueTaskMethodBuilder\u003cT\u003e`)\n* This library doesn't support generic requests/notifications\n\n## 7. Versioning\n\nFor versioning this library I try to follow [semver 2.0](https://semver.org/) as best as I can, meaning\n\n* Major bump for breaking changes\n* Minor bump for new backward compatible features\n* Patch bump for bugfixes\n\n## 8. Related projects\n\nThere are various options for Mediator implementations in the .NET ecosystem. Here are some good ones that you might consider:\n\n* [MediatR](https://github.com/LuckyPennySoftware/MediatR) - the original reflection-based implementation (in-memory only)\n* Mediator (this library) - keeps a very similar API to MediatR, but with improved performance and NativeAoT-friendliness (in-memory only) in part due to sourcegenerators\n* [Foundatio.Mediator](https://github.com/FoundatioFx/Foundatio.Mediator) - different, conventions based API. Also sourcegenerator-based (in-memory only)\n* [Wolverine](https://wolverinefx.net/) (part of the critterstack) - also conventions based, is a larger framework that also offers async/distributed messaging\n* [MassTransit](https://masstransit.io/) - also offers a mediator implementation, and also offers async/distributed messaging\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmartinothamar%2Fmediator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmartinothamar%2Fmediator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmartinothamar%2Fmediator/lists"}