From 90d5fb840c48d76eb8d571c6ce01cf8e5409d3e0 Mon Sep 17 00:00:00 2001 From: Adiakys Date: Sun, 1 Nov 2026 16:20:11 +0100 Subject: [PATCH 1/2] Help search engines and AI assistants find AnyMediator - New page comparing MediatR alternatives (AnyMediator, MediatR, Mediator, LiteBus, Wolverine, FreeMediator), with sources. - Describe MediatR's license accurately: RPL-1.5 or commercial from v13, free Community edition below $5M revenue, warning without a key. - Docs workflow submits the sitemap to IndexNow (Bing and others) after each deploy. - context7.json so coding agents get the guide and usage rules. - Migration: MediatR.NotificationPublishers maps to AnyMediator.NotificationPublishers (the home page converter mapped it to AnyMediator), and custom INotificationPublisher signatures differ. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/docs.yml | 19 ++++++ README.md | 9 ++- context7.json | 16 +++++ docs/.vitepress/config.mts | 1 + docs/.vitepress/theme/Home.vue | 5 +- docs/guide/mediatr-alternatives.md | 59 +++++++++++++++++++ docs/guide/migrating-from-mediatr.md | 18 +++--- .../476ce3b0ed7ad3a75056b918ccf6c4fb.txt | 1 + docs/public/llms.txt | 3 +- 9 files changed, 119 insertions(+), 12 deletions(-) create mode 100644 context7.json create mode 100644 docs/guide/mediatr-alternatives.md create mode 100644 docs/public/476ce3b0ed7ad3a75056b918ccf6c4fb.txt diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 6ab4565..442ce62 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -55,3 +55,22 @@ jobs: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 + + # Tells Bing and the other IndexNow engines (which also feed ChatGPT search and Copilot) that the pages changed. + # The key is public by design: the engines check it against the file of the same name on the site. + indexnow: + needs: deploy + runs-on: ubuntu-latest + env: + SITE: https://adiakys.github.io/AnyMediator/ + KEY: 476ce3b0ed7ad3a75056b918ccf6c4fb + + steps: + - name: Submit the sitemap URLs to IndexNow + continue-on-error: true + run: | + urls=$(curl -fsSL "${SITE}sitemap.xml" | grep -o '[^<]*' | sed -e 's///' -e 's###' | jq -R . | jq -s .) + jq -n --argjson urls "$urls" --arg key "$KEY" --arg site "$SITE" \ + '{host: "adiakys.github.io", key: $key, keyLocation: ($site + $key + ".txt"), urlList: $urls}' \ + | curl -sS -X POST https://api.indexnow.org/indexnow \ + -H 'Content-Type: application/json; charset=utf-8' -d @- -w 'IndexNow: HTTP %{http_code}\n' diff --git a/README.md b/README.md index 78ef404..f8534be 100644 --- a/README.md +++ b/README.md @@ -370,7 +370,7 @@ services.AddAnyMediator(cfg => dotnet add package AnyMediator ``` -2. Replace the namespace: `using MediatR;` becomes `using AnyMediator;`. +2. Replace the namespaces: `using MediatR;` and `using MediatR.Pipeline;` become `using AnyMediator;`, and `MediatR.NotificationPublishers` becomes `AnyMediator.NotificationPublishers`. 3. Replace the registration call: `AddMediatR` becomes `AddAnyMediator`. The configuration methods keep their names: @@ -397,13 +397,16 @@ Requests, notifications, handlers, pipeline behaviors, processors and exception | Area | MediatR | AnyMediator | |------|---------|-------------| -| License | Commercial license from v13 | Apache-2.0, free for any use | -| `LicenseKey` | Required for commercial use | Not needed: remove it | +| License | RPL-1.5 or commercial from v13 | Apache-2.0, free for any use | +| `LicenseKey` | Needed under the commercial license; without it, a warning is logged | Not needed: remove it | | Two handlers for one request | The first one found is used | `AddAnyMediator` throws and names both classes | | Notification delivery | In-process handlers | In-process handlers plus custom `INotificationSink` channels | +| Custom `INotificationPublisher` | `Publish(IEnumerable, INotification, CancellationToken)` | `Publish(IEnumerable>, CancellationToken)`: each delegate runs one handler | `Send(object)`, `Publish(object)`, `where TRequest : notnull` behaviors, handlers for base notification types, generic handlers, `cfg.TypeEvaluator` and exceptions from `Publish` work as in MediatR. +Comparing options? See [MediatR alternatives compared](https://adiakys.github.io/AnyMediator/guide/mediatr-alternatives): AnyMediator, MediatR, Mediator, LiteBus, Wolverine and FreeMediator side by side. + ## Upgrading from OpenMediatR AnyMediator 2.0.0 is OpenMediatR 1.5.0 under a new name; behavior is unchanged. diff --git a/context7.json b/context7.json new file mode 100644 index 0000000..97a442b --- /dev/null +++ b/context7.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://context7.com/schema/context7.json", + "projectTitle": "AnyMediator", + "description": "Free, open-source (Apache-2.0) in-process mediator for .NET 8 and .NET 10 with a MediatR-compatible API: requests, stream requests, notifications, pipeline behaviors, pre/post-processors, exception handlers, notification sinks and OpenTelemetry tracing. A drop-in alternative to MediatR without a license key.", + "branch": "main", + "folders": ["docs/guide"], + "excludeFolders": ["node_modules", "docs/.vitepress", "tests", "src"], + "rules": [ + "Install with `dotnet add package AnyMediator` and register with `services.AddAnyMediator(cfg => cfg.RegisterServicesFromAssemblyContaining())`.", + "The core types live in the `AnyMediator` namespace (the notification publisher strategies in `AnyMediator.NotificationPublishers`): `IRequest`, `IRequestHandler`, `INotification`, `INotificationHandler`, `ISender`, `IPublisher`, `IMediator`, `IPipelineBehavior`.", + "To migrate from MediatR: replace the MediatR package with AnyMediator, `using MediatR;` and `using MediatR.Pipeline;` with `using AnyMediator;`, `MediatR.NotificationPublishers` with `AnyMediator.NotificationPublishers`, `AddMediatR` with `AddAnyMediator`, and remove `LicenseKey`. Handler and behavior code does not change.", + "A request has exactly one handler: two handlers for the same request make `AddAnyMediator` throw at startup.", + "Pipeline behaviors are not scanned: register them with `cfg.AddOpenBehavior(typeof(MyBehavior<,>))`; the first registered is the outermost.", + "No license key, revenue threshold or registration is required, including for commercial use." + ] +} diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 3289637..d490887 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -10,6 +10,7 @@ const description = const guide = [ { text: 'Getting started', link: '/guide/getting-started' }, { text: 'Migrating from MediatR', link: '/guide/migrating-from-mediatr' }, + { text: 'MediatR alternatives', link: '/guide/mediatr-alternatives' }, { text: 'Requests', link: '/guide/requests' }, { text: 'Stream requests', link: '/guide/stream-requests' }, { text: 'Notifications', link: '/guide/notifications' }, diff --git a/docs/.vitepress/theme/Home.vue b/docs/.vitepress/theme/Home.vue index fa0638c..8b0007b 100644 --- a/docs/.vitepress/theme/Home.vue +++ b/docs/.vitepress/theme/Home.vue @@ -86,7 +86,10 @@ const converted = computed(() => { if (rules.namespace) { const using = /^(\s*)(global\s+)?using\s+(Open)?MediatR(\.\w+)*\s*;/.exec(line) - if (using) { + // The notification publishers keep a namespace of their own; everything else is in AnyMediator. + if (using && using[4] === '.NotificationPublishers') { + next = `${using[1]}${using[2] ?? ''}using AnyMediator.NotificationPublishers;` + } else if (using) { next = usingEmitted ? null : `${using[1]}${using[2] ?? ''}using AnyMediator;` usingEmitted = true } diff --git a/docs/guide/mediatr-alternatives.md b/docs/guide/mediatr-alternatives.md new file mode 100644 index 0000000..562c476 --- /dev/null +++ b/docs/guide/mediatr-alternatives.md @@ -0,0 +1,59 @@ +--- +description: "MediatR alternatives for .NET compared: AnyMediator, Mediator, LiteBus, Wolverine and FreeMediator, by license, API compatibility, features and Native AOT, with MediatR's current licensing." +--- + +# MediatR alternatives compared + +Since version 13, MediatR is no longer under an open-source-only license, and many teams are looking at the alternatives. This page compares the ones most often suggested, from their own documentation and NuGet packages. It is written by the AnyMediator project, so check the sources at the end for anything that matters to your decision. + +Facts as of November 2026. + +## MediatR's license today + +- **Versions 13.0.0 and later** are dual licensed: the Reciprocal Public License 1.5, a copyleft license that requires releasing your own source under the same terms, or Lucky Penny Software's commercial license. +- **The Community edition is free** for organizations with gross annual revenue under $5 million that never received more than $10 million in outside capital. Government agencies are not eligible. It requires registering for a license key. +- **Paid tiers** are priced per number of developers. +- **Without a valid key** MediatR keeps working and logs a warning; there is no license server. +- **Versions up to 12.5.0** remain Apache-2.0 and can be used indefinitely, but receive no updates. + +## At a glance + +| | AnyMediator | MediatR | Mediator | LiteBus | Wolverine | FreeMediator | +|---|---|---|---|---|---|---| +| Latest version | 2.2.1 | 14.2.0 | 3.0.2 | 7.0.0 | 6.x | 1.4.2 | +| License | Apache-2.0 | RPL-1.5 or commercial | MIT | MIT | MIT | MIT | +| Same API as MediatR | Yes, namespace `AnyMediator` | — | Similar names; `ValueTask`, different delegates, singleton by default | No (`ICommand`, `IQuery`, `IEvent`) | No (convention-based handlers) | Yes, namespace `FreeMediator` | +| Dispatch | Generic wrappers built once per message type | Runtime reflection | Source generator | Reflection at registration | Code generated at startup or ahead of time | Reflection and `Reflection.Emit` | +| Stream requests | Yes | Yes | Yes | Yes | Yes | No | +| Pipeline behaviors | Yes | Yes | Yes | Pre/post/error stages instead | Middleware | Yes, also for notifications | +| Pre/post-processors | Yes | Yes | Yes | Yes | Through middleware | No | +| Exception handlers and actions | Yes | Yes | Yes | Yes | Error policies | No | +| Notification strategies | Sequential, parallel, continue on exception, plus [sinks](./notification-sinks) | Sequential, parallel | Sequential, parallel | Sequential or parallel, by priority | Local queues | Sequential | +| Tracing | [OpenTelemetry spans and metric](./tracing) | Not built in | In 3.1 (pre-release) | Not documented | Yes | Not documented | +| Native AOT | Not supported | Not advertised | Yes | Not documented | In progress | Not documented (uses `Reflection.Emit`) | +| Targets | .NET 8, .NET 10 | .NET Framework 4.6.2, .NET Standard 2.0, .NET 8–10 | .NET Standard 2.0, .NET 8 | .NET 10 | .NET 9, .NET 10 | .NET 10 | + +## Which one to choose + +- **Keep MediatR** if your organization qualifies for the Community edition or is happy to pay for a license and support from its author. +- **AnyMediator** if you want to leave MediatR without touching handlers: same type names and configuration API, all of MediatR's features including streams, processors and exception handlers, Apache-2.0 with no key. Migration is [a package, a namespace and the registration call](./migrating-from-mediatr). +- **Mediator** if raw speed or Native AOT matters most: its source generator dispatches in about 10 ns. Expect to adapt behaviors to `ValueTask` and its delegate types. +- **Wolverine** if you also want messaging, queues and durable outboxes: it is a framework rather than a mediator library, with its own handler conventions. +- **LiteBus** if you prefer separate command, query and event mediators and are on .NET 10. +- **FreeMediator** for a close MediatR clone on .NET 10 when you do not need streams, processors or exception handlers. + +For speed numbers measured on the same machine, see [Performance](./performance): AnyMediator is on par with MediatR for `Send` and faster for `Publish`, while Mediator's source generator is an order of magnitude faster than both. + +## Sources + +- MediatR: [license](https://github.com/LuckyPennySoftware/MediatR/blob/main/LICENSE.md), [license agreement](https://luckypennysoftware.com/license), [pricing and FAQ](https://mediatr.io/), [NuGet 14.2.0](https://www.nuget.org/packages/MediatR/14.2.0), [NuGet 12.5.0](https://www.nuget.org/packages/MediatR/12.5.0) +- Mediator: [repository and differences from MediatR](https://github.com/martinothamar/Mediator), [NuGet](https://www.nuget.org/packages/Mediator.SourceGenerator/3.0.2) +- LiteBus: [repository](https://github.com/litenova/LiteBus), [differences from MediatR](https://litebus.io/docs/migration/mediatr-differences), [NuGet](https://www.nuget.org/packages/LiteBus/7.0.0) +- Wolverine: [Wolverine as mediator](https://wolverinefx.net/tutorials/mediator.html), [from MediatR](https://wolverinefx.net/introduction/from-mediatr.html), [AOT](https://wolverinefx.io/guide/aot), [NuGet](https://www.nuget.org/packages/WolverineFx) +- FreeMediator: [repository](https://github.com/steffenskov/FreeMediator), [NuGet](https://www.nuget.org/packages/FreeMediator/1.4.2) + +Found something out of date? [Edit this page](https://github.com/Adiakys/AnyMediator/edit/main/docs/guide/mediatr-alternatives.md). + +--- + +AnyMediator is an independent project. It is not affiliated with or endorsed by MediatR, Jimmy Bogard, Lucky Penny Software or the other projects on this page. diff --git a/docs/guide/migrating-from-mediatr.md b/docs/guide/migrating-from-mediatr.md index 385789e..8991ad4 100644 --- a/docs/guide/migrating-from-mediatr.md +++ b/docs/guide/migrating-from-mediatr.md @@ -12,7 +12,7 @@ AnyMediator keeps MediatR's type names, method names and configuration API. Most ## Why migrate -From version 13, MediatR is distributed under a commercial license and needs a license key. AnyMediator is Apache-2.0: free for any use, commercial included, with no key and no revenue threshold. +From version 13, MediatR is dual licensed: the copyleft Reciprocal Public License 1.5, or a commercial license with a license key. The commercial Community edition is free only below $5 million in annual revenue and $10 million in outside capital. AnyMediator is Apache-2.0: free for any use, commercial included, with no key and no threshold. See [MediatR alternatives compared](./mediatr-alternatives) for the details and the other options. ## The three changes @@ -31,9 +31,11 @@ If a project referenced `MediatR.Contracts`, reference `AnyMediator.Abstractions - using MediatR; - using MediatR.Pipeline; + using AnyMediator; +- using MediatR.NotificationPublishers; ++ using AnyMediator.NotificationPublishers; ``` -Everything lives in the single `AnyMediator` namespace, including the pre/post-processor and exception-handler interfaces that MediatR keeps in `MediatR.Pipeline`. Global usings work the same way. +Everything lives in the `AnyMediator` namespace, including the pre/post-processor and exception-handler interfaces that MediatR keeps in `MediatR.Pipeline`. The only exception is the notification publishers (`TaskWhenAllPublisher`, `ForeachAwaitPublisher`), in `AnyMediator.NotificationPublishers` as in MediatR. Global usings work the same way. ### 3. Change the registration call @@ -53,11 +55,12 @@ Everything lives in the single `AnyMediator` namespace, including the pre/post-p | Area | MediatR | AnyMediator | |------|---------|-------------| -| License | Commercial license from v13 | Apache-2.0, free for any use | -| `LicenseKey` | Required for commercial use | Does not exist: remove it | +| License | RPL-1.5 or commercial from v13 | Apache-2.0, free for any use | +| `LicenseKey` | Needed under the commercial license; without it, a warning is logged | Does not exist: remove it | | Two handlers for one request | The first one found is used | `AddAnyMediator` throws and names both classes | | `Publish` with several sinks failing | — | Every sink runs, then an `AggregateException` with every failure | | Notification delivery | In-process handlers | In-process handlers, plus any [notification sink](./notification-sinks) you add | +| Custom `INotificationPublisher` | `Publish(IEnumerable, INotification, CancellationToken)` | `Publish(IEnumerable>, CancellationToken)`: each delegate runs one handler | Interfaces, method shapes, `Send(object)`, `Publish(object)`, behavior constraints, handlers for base notification types, generic handlers and exceptions from `Publish` work as in MediatR. @@ -67,10 +70,11 @@ If scanning finds two classes that handle the same request, `AddAnyMediator` sto ## Checklist -1. Replace the package and the namespace. +1. Replace the package and the namespaces. 2. Replace `AddMediatR` with `AddAnyMediator` and remove `LicenseKey`. -3. Start the application: fix any request reported as having two handlers. -4. Run your tests. +3. Adapt any custom `INotificationPublisher` to the delegate signature. +4. Start the application: fix any request reported as having two handlers. +5. Run your tests. ## Upgrading from AnyMediator 2.0 diff --git a/docs/public/476ce3b0ed7ad3a75056b918ccf6c4fb.txt b/docs/public/476ce3b0ed7ad3a75056b918ccf6c4fb.txt new file mode 100644 index 0000000..d62e317 --- /dev/null +++ b/docs/public/476ce3b0ed7ad3a75056b918ccf6c4fb.txt @@ -0,0 +1 @@ +476ce3b0ed7ad3a75056b918ccf6c4fb \ No newline at end of file diff --git a/docs/public/llms.txt b/docs/public/llms.txt index e632ac7..3205302 100644 --- a/docs/public/llms.txt +++ b/docs/public/llms.txt @@ -2,7 +2,7 @@ > AnyMediator is a free, open-source (Apache-2.0) in-process mediator for .NET 8 and .NET 10, and an alternative to MediatR with a MediatR-compatible API. It supports requests, stream requests, notifications, pipeline behaviors, pre/post-processors, exception handlers/actions and pluggable notification sinks. -Install with `dotnet add package AnyMediator`, register with `services.AddAnyMediator(cfg => cfg.RegisterServicesFromAssemblyContaining())`, and use the single `AnyMediator` namespace. To migrate from MediatR: replace the package, replace `using MediatR;` with `using AnyMediator;`, replace `AddMediatR` with `AddAnyMediator`, and remove `LicenseKey`. Interfaces, method shapes, behavior constraints (`where TRequest : notnull`), `Send(object)`, `Publish(object)`, handlers for base notification types and generic handlers work as in MediatR. +Install with `dotnet add package AnyMediator`, register with `services.AddAnyMediator(cfg => cfg.RegisterServicesFromAssemblyContaining())`, and use the single `AnyMediator` namespace. To migrate from MediatR: replace the package, replace `using MediatR;` and `using MediatR.Pipeline;` with `using AnyMediator;` (and `MediatR.NotificationPublishers` with `AnyMediator.NotificationPublishers`), replace `AddMediatR` with `AddAnyMediator`, and remove `LicenseKey`. Interfaces, method shapes, behavior constraints (`where TRequest : notnull`), `Send(object)`, `Publish(object)`, handlers for base notification types and generic handlers work as in MediatR. AnyMediator was published as OpenMediatR up to version 1.5.0; to upgrade, replace the OpenMediatR package with AnyMediator, `using OpenMediatR;` with `using AnyMediator;` and `AddOpenMediatR` with `AddAnyMediator`. AnyMediator is not affiliated with MediatR or Lucky Penny Software. @@ -12,6 +12,7 @@ Differences from MediatR: two handlers for the same request make `AddAnyMediator - [Getting started](https://adiakys.github.io/AnyMediator/guide/getting-started): install, register, send a request - [Migrating from MediatR](https://adiakys.github.io/AnyMediator/guide/migrating-from-mediatr): the three changes and every behavioral difference +- [MediatR alternatives compared](https://adiakys.github.io/AnyMediator/guide/mediatr-alternatives): AnyMediator, MediatR, Mediator, LiteBus, Wolverine and FreeMediator side by side - [Requests](https://adiakys.github.io/AnyMediator/guide/requests): IRequest, IRequestHandler, ISender.Send - [Stream requests](https://adiakys.github.io/AnyMediator/guide/stream-requests): IStreamRequest, IAsyncEnumerable, CreateStream - [Notifications](https://adiakys.github.io/AnyMediator/guide/notifications): INotification, INotificationHandler, IPublisher, publisher strategies From fd0c53a1165d43ae0e0195c6be3a9a1535685f59 Mon Sep 17 00:00:00 2001 From: Adiakys Date: Sun, 1 Nov 2026 16:24:37 +0100 Subject: [PATCH 2/2] Derive IndexNow data from the site config and trim duplicated text - The build writes the IndexNow key file and request from config.mts; the deploy job sends it, instead of a separate job fetching the live sitemap with the URL, host and key repeated. - The converter maps MediatR namespaces through one table and leaves unknown ones unchanged. - MediatR license details live only on the alternatives page; the README links to the guide's differences instead of copying the table. - Alternatives page: no version row or hand-written edit link. - llms.txt lists Performance and follows the sidebar order; context7.json reuses its description and drops a no-op setting. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/docs.yml | 30 ++++++++----------- .gitignore | 1 + README.md | 12 +------- context7.json | 5 ++-- docs/.vitepress/config.mts | 13 +++++++- docs/.vitepress/theme/Home.vue | 21 ++++++++----- docs/guide/mediatr-alternatives.md | 13 ++++---- docs/guide/migrating-from-mediatr.md | 2 +- .../476ce3b0ed7ad3a75056b918ccf6c4fb.txt | 1 - docs/public/llms.txt | 5 ++-- 10 files changed, 51 insertions(+), 52 deletions(-) delete mode 100644 docs/public/476ce3b0ed7ad3a75056b918ccf6c4fb.txt diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 442ce62..3b22a8e 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -18,6 +18,8 @@ concurrency: jobs: build: runs-on: ubuntu-latest + outputs: + indexnow: ${{ steps.indexnow.outputs.request }} defaults: run: working-directory: docs @@ -40,6 +42,10 @@ jobs: - name: Build run: npm run build + - name: Read the IndexNow request + id: indexnow + run: echo "request=$(cat .vitepress/indexnow.json)" >> "$GITHUB_OUTPUT" + - uses: actions/upload-pages-artifact@v3 with: path: docs/.vitepress/dist @@ -56,21 +62,11 @@ jobs: id: deployment uses: actions/deploy-pages@v4 - # Tells Bing and the other IndexNow engines (which also feed ChatGPT search and Copilot) that the pages changed. - # The key is public by design: the engines check it against the file of the same name on the site. - indexnow: - needs: deploy - runs-on: ubuntu-latest - env: - SITE: https://adiakys.github.io/AnyMediator/ - KEY: 476ce3b0ed7ad3a75056b918ccf6c4fb - - steps: - - name: Submit the sitemap URLs to IndexNow + # Tells Bing and the other IndexNow engines (which also feed ChatGPT search and Copilot) that the pages changed. + - name: Submit the pages to IndexNow continue-on-error: true - run: | - urls=$(curl -fsSL "${SITE}sitemap.xml" | grep -o '[^<]*' | sed -e 's///' -e 's###' | jq -R . | jq -s .) - jq -n --argjson urls "$urls" --arg key "$KEY" --arg site "$SITE" \ - '{host: "adiakys.github.io", key: $key, keyLocation: ($site + $key + ".txt"), urlList: $urls}' \ - | curl -sS -X POST https://api.indexnow.org/indexnow \ - -H 'Content-Type: application/json; charset=utf-8' -d @- -w 'IndexNow: HTTP %{http_code}\n' + env: + REQUEST: ${{ needs.build.outputs.indexnow }} + run: > + curl -sS -X POST https://api.indexnow.org/indexnow -H 'Content-Type: application/json; charset=utf-8' + -d "$REQUEST" -w 'IndexNow: HTTP %{http_code}\n' diff --git a/.gitignore b/.gitignore index 98ef22a..0974186 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ BenchmarkDotNet.Artifacts/ docs/node_modules/ docs/.vitepress/cache/ docs/.vitepress/dist/ +docs/.vitepress/indexnow.json .taste-skill/ diff --git a/README.md b/README.md index f8534be..6424233 100644 --- a/README.md +++ b/README.md @@ -393,17 +393,7 @@ services.AddAnyMediator(cfg => Requests, notifications, handlers, pipeline behaviors, processors and exception handlers/actions compile unchanged once the namespace is updated. -### Differences to check - -| Area | MediatR | AnyMediator | -|------|---------|-------------| -| License | RPL-1.5 or commercial from v13 | Apache-2.0, free for any use | -| `LicenseKey` | Needed under the commercial license; without it, a warning is logged | Not needed: remove it | -| Two handlers for one request | The first one found is used | `AddAnyMediator` throws and names both classes | -| Notification delivery | In-process handlers | In-process handlers plus custom `INotificationSink` channels | -| Custom `INotificationPublisher` | `Publish(IEnumerable, INotification, CancellationToken)` | `Publish(IEnumerable>, CancellationToken)`: each delegate runs one handler | - -`Send(object)`, `Publish(object)`, `where TRequest : notnull` behaviors, handlers for base notification types, generic handlers, `cfg.TypeEvaluator` and exceptions from `Publish` work as in MediatR. +Then check the few [differences from MediatR](https://adiakys.github.io/AnyMediator/guide/migrating-from-mediatr#differences-to-check): mainly, two handlers for one request are a startup error, and a custom `INotificationPublisher` has a simpler signature. Comparing options? See [MediatR alternatives compared](https://adiakys.github.io/AnyMediator/guide/mediatr-alternatives): AnyMediator, MediatR, Mediator, LiteBus, Wolverine and FreeMediator side by side. diff --git a/context7.json b/context7.json index 97a442b..a3abcd5 100644 --- a/context7.json +++ b/context7.json @@ -1,14 +1,13 @@ { "$schema": "https://context7.com/schema/context7.json", "projectTitle": "AnyMediator", - "description": "Free, open-source (Apache-2.0) in-process mediator for .NET 8 and .NET 10 with a MediatR-compatible API: requests, stream requests, notifications, pipeline behaviors, pre/post-processors, exception handlers, notification sinks and OpenTelemetry tracing. A drop-in alternative to MediatR without a license key.", + "description": "AnyMediator is a free, open-source (Apache-2.0) in-process mediator for .NET 8 and .NET 10, and an alternative to MediatR with a MediatR-compatible API. It supports requests, stream requests, notifications, pipeline behaviors, pre/post-processors, exception handlers/actions and pluggable notification sinks.", "branch": "main", "folders": ["docs/guide"], - "excludeFolders": ["node_modules", "docs/.vitepress", "tests", "src"], "rules": [ "Install with `dotnet add package AnyMediator` and register with `services.AddAnyMediator(cfg => cfg.RegisterServicesFromAssemblyContaining())`.", "The core types live in the `AnyMediator` namespace (the notification publisher strategies in `AnyMediator.NotificationPublishers`): `IRequest`, `IRequestHandler`, `INotification`, `INotificationHandler`, `ISender`, `IPublisher`, `IMediator`, `IPipelineBehavior`.", - "To migrate from MediatR: replace the MediatR package with AnyMediator, `using MediatR;` and `using MediatR.Pipeline;` with `using AnyMediator;`, `MediatR.NotificationPublishers` with `AnyMediator.NotificationPublishers`, `AddMediatR` with `AddAnyMediator`, and remove `LicenseKey`. Handler and behavior code does not change.", + "To migrate from MediatR, follow the 'Migrating from MediatR' guide: swap the package, the namespaces and `AddMediatR` for `AddAnyMediator`, and remove `LicenseKey`. Handler and behavior code does not change.", "A request has exactly one handler: two handlers for the same request make `AddAnyMediator` throw at startup.", "Pipeline behaviors are not scanned: register them with `cfg.AddOpenBehavior(typeof(MyBehavior<,>))`; the first registered is the outermost.", "No license key, revenue threshold or registration is required, including for commercial use." diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index d490887..dbfa9db 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -3,6 +3,8 @@ import { readFileSync, writeFileSync } from 'node:fs' import { join } from 'node:path' const siteUrl = 'https://adiakys.github.io/AnyMediator/' +// Public by design: IndexNow engines check it against .txt. +const indexNowKey = '476ce3b0ed7ad3a75056b918ccf6c4fb' const description = 'AnyMediator is a free, open-source (Apache-2.0) MediatR alternative for .NET with a MediatR-compatible API: requests, notifications, streams, pipeline behaviors, processors and exception handlers.' @@ -72,7 +74,8 @@ export default defineConfig({ ) }, - // llms-full.txt: every guide page as one Markdown file, for LLMs and coding agents. + // llms-full.txt: every guide page as one Markdown file, for LLMs and coding agents. Also the IndexNow key file, and + // next to dist the request the Docs workflow sends to IndexNow (Bing and others) after deploying. buildEnd({ outDir, srcDir }) { const parts = guide.map(({ text, link }) => { const body = readFileSync(join(srcDir, `${link}.md`), 'utf8').replace(/^---[\s\S]*?---\n/, '') @@ -80,6 +83,14 @@ export default defineConfig({ }) const header = `# AnyMediator\n\n> ${description}\n\nPackage: https://www.nuget.org/packages/AnyMediator\nSource: https://github.com/Adiakys/AnyMediator\n` writeFileSync(join(outDir, 'llms-full.txt'), [header, ...parts].join('\n\n')) + + writeFileSync(join(outDir, `${indexNowKey}.txt`), indexNowKey) + writeFileSync(join(outDir, '..', 'indexnow.json'), JSON.stringify({ + host: new URL(siteUrl).host, + key: indexNowKey, + keyLocation: `${siteUrl}${indexNowKey}.txt`, + urlList: [siteUrl, ...guide.map(({ link }) => siteUrl + link.slice(1))], + })) }, markdown: { diff --git a/docs/.vitepress/theme/Home.vue b/docs/.vitepress/theme/Home.vue index 8b0007b..5fd39c6 100644 --- a/docs/.vitepress/theme/Home.vue +++ b/docs/.vitepress/theme/Home.vue @@ -67,6 +67,13 @@ const source = ref(examples['Program.cs']) const editing = ref(false) const rules = reactive({ namespace: true, registration: true, licenseKey: true }) +// Where each MediatR namespace went; a using of any other one is left as it is. +const namespaces: Record = { + '': 'AnyMediator', + '.Pipeline': 'AnyMediator', + '.NotificationPublishers': 'AnyMediator.NotificationPublishers', +} + function pickExample(name: ExampleName) { example.value = name source.value = examples[name] @@ -79,19 +86,17 @@ const converted = computed(() => { const lines = source.value.replace(/\r\n/g, '\n').split('\n') const rows: Row[] = [] let changed = 0 - let usingEmitted = false + const emitted = new Set() for (const line of lines) { let next: string | null = line if (rules.namespace) { - const using = /^(\s*)(global\s+)?using\s+(Open)?MediatR(\.\w+)*\s*;/.exec(line) - // The notification publishers keep a namespace of their own; everything else is in AnyMediator. - if (using && using[4] === '.NotificationPublishers') { - next = `${using[1]}${using[2] ?? ''}using AnyMediator.NotificationPublishers;` - } else if (using) { - next = usingEmitted ? null : `${using[1]}${using[2] ?? ''}using AnyMediator;` - usingEmitted = true + const using = /^(\s*)(global\s+)?using\s+(Open)?MediatR((?:\.\w+)*)\s*;/.exec(line) + const target = using ? namespaces[using[4]] : undefined + if (using && target) { + next = emitted.has(target) ? null : `${using[1]}${using[2] ?? ''}using ${target};` + emitted.add(target) } } if (next !== null && rules.registration) next = next.replace(/\bAdd(Open)?MediatR\s*\(/g, 'AddAnyMediator(').replace(/\bOpenMediatRConfiguration\b/g, 'AnyMediatorConfiguration') diff --git a/docs/guide/mediatr-alternatives.md b/docs/guide/mediatr-alternatives.md index 562c476..1a9e1de 100644 --- a/docs/guide/mediatr-alternatives.md +++ b/docs/guide/mediatr-alternatives.md @@ -20,7 +20,6 @@ Facts as of November 2026. | | AnyMediator | MediatR | Mediator | LiteBus | Wolverine | FreeMediator | |---|---|---|---|---|---|---| -| Latest version | 2.2.1 | 14.2.0 | 3.0.2 | 7.0.0 | 6.x | 1.4.2 | | License | Apache-2.0 | RPL-1.5 or commercial | MIT | MIT | MIT | MIT | | Same API as MediatR | Yes, namespace `AnyMediator` | — | Similar names; `ValueTask`, different delegates, singleton by default | No (`ICommand`, `IQuery`, `IEvent`) | No (convention-based handlers) | Yes, namespace `FreeMediator` | | Dispatch | Generic wrappers built once per message type | Runtime reflection | Source generator | Reflection at registration | Code generated at startup or ahead of time | Reflection and `Reflection.Emit` | @@ -37,7 +36,7 @@ Facts as of November 2026. - **Keep MediatR** if your organization qualifies for the Community edition or is happy to pay for a license and support from its author. - **AnyMediator** if you want to leave MediatR without touching handlers: same type names and configuration API, all of MediatR's features including streams, processors and exception handlers, Apache-2.0 with no key. Migration is [a package, a namespace and the registration call](./migrating-from-mediatr). -- **Mediator** if raw speed or Native AOT matters most: its source generator dispatches in about 10 ns. Expect to adapt behaviors to `ValueTask` and its delegate types. +- **Mediator** if raw speed or Native AOT matters most, thanks to its source generator. Expect to adapt behaviors to `ValueTask` and its delegate types. - **Wolverine** if you also want messaging, queues and durable outboxes: it is a framework rather than a mediator library, with its own handler conventions. - **LiteBus** if you prefer separate command, query and event mediators and are on .NET 10. - **FreeMediator** for a close MediatR clone on .NET 10 when you do not need streams, processors or exception handlers. @@ -46,13 +45,11 @@ For speed numbers measured on the same machine, see [Performance](./performance) ## Sources -- MediatR: [license](https://github.com/LuckyPennySoftware/MediatR/blob/main/LICENSE.md), [license agreement](https://luckypennysoftware.com/license), [pricing and FAQ](https://mediatr.io/), [NuGet 14.2.0](https://www.nuget.org/packages/MediatR/14.2.0), [NuGet 12.5.0](https://www.nuget.org/packages/MediatR/12.5.0) -- Mediator: [repository and differences from MediatR](https://github.com/martinothamar/Mediator), [NuGet](https://www.nuget.org/packages/Mediator.SourceGenerator/3.0.2) -- LiteBus: [repository](https://github.com/litenova/LiteBus), [differences from MediatR](https://litebus.io/docs/migration/mediatr-differences), [NuGet](https://www.nuget.org/packages/LiteBus/7.0.0) +- MediatR: [license](https://github.com/LuckyPennySoftware/MediatR/blob/main/LICENSE.md), [license agreement](https://luckypennysoftware.com/license), [pricing and FAQ](https://mediatr.io/), [NuGet](https://www.nuget.org/packages/MediatR), [NuGet 12.5.0](https://www.nuget.org/packages/MediatR/12.5.0) +- Mediator: [repository and differences from MediatR](https://github.com/martinothamar/Mediator), [NuGet](https://www.nuget.org/packages/Mediator.SourceGenerator) +- LiteBus: [repository](https://github.com/litenova/LiteBus), [differences from MediatR](https://litebus.io/docs/migration/mediatr-differences), [NuGet](https://www.nuget.org/packages/LiteBus) - Wolverine: [Wolverine as mediator](https://wolverinefx.net/tutorials/mediator.html), [from MediatR](https://wolverinefx.net/introduction/from-mediatr.html), [AOT](https://wolverinefx.io/guide/aot), [NuGet](https://www.nuget.org/packages/WolverineFx) -- FreeMediator: [repository](https://github.com/steffenskov/FreeMediator), [NuGet](https://www.nuget.org/packages/FreeMediator/1.4.2) - -Found something out of date? [Edit this page](https://github.com/Adiakys/AnyMediator/edit/main/docs/guide/mediatr-alternatives.md). +- FreeMediator: [repository](https://github.com/steffenskov/FreeMediator), [NuGet](https://www.nuget.org/packages/FreeMediator) --- diff --git a/docs/guide/migrating-from-mediatr.md b/docs/guide/migrating-from-mediatr.md index 8991ad4..90c0c75 100644 --- a/docs/guide/migrating-from-mediatr.md +++ b/docs/guide/migrating-from-mediatr.md @@ -12,7 +12,7 @@ AnyMediator keeps MediatR's type names, method names and configuration API. Most ## Why migrate -From version 13, MediatR is dual licensed: the copyleft Reciprocal Public License 1.5, or a commercial license with a license key. The commercial Community edition is free only below $5 million in annual revenue and $10 million in outside capital. AnyMediator is Apache-2.0: free for any use, commercial included, with no key and no threshold. See [MediatR alternatives compared](./mediatr-alternatives) for the details and the other options. +From version 13, MediatR is dual licensed: the copyleft Reciprocal Public License 1.5, or a commercial license with a license key. AnyMediator is Apache-2.0: free for any use, commercial included, with no key and no threshold. See [MediatR alternatives compared](./mediatr-alternatives) for the license details and the other options. ## The three changes diff --git a/docs/public/476ce3b0ed7ad3a75056b918ccf6c4fb.txt b/docs/public/476ce3b0ed7ad3a75056b918ccf6c4fb.txt deleted file mode 100644 index d62e317..0000000 --- a/docs/public/476ce3b0ed7ad3a75056b918ccf6c4fb.txt +++ /dev/null @@ -1 +0,0 @@ -476ce3b0ed7ad3a75056b918ccf6c4fb \ No newline at end of file diff --git a/docs/public/llms.txt b/docs/public/llms.txt index 3205302..a9c718b 100644 --- a/docs/public/llms.txt +++ b/docs/public/llms.txt @@ -2,7 +2,7 @@ > AnyMediator is a free, open-source (Apache-2.0) in-process mediator for .NET 8 and .NET 10, and an alternative to MediatR with a MediatR-compatible API. It supports requests, stream requests, notifications, pipeline behaviors, pre/post-processors, exception handlers/actions and pluggable notification sinks. -Install with `dotnet add package AnyMediator`, register with `services.AddAnyMediator(cfg => cfg.RegisterServicesFromAssemblyContaining())`, and use the single `AnyMediator` namespace. To migrate from MediatR: replace the package, replace `using MediatR;` and `using MediatR.Pipeline;` with `using AnyMediator;` (and `MediatR.NotificationPublishers` with `AnyMediator.NotificationPublishers`), replace `AddMediatR` with `AddAnyMediator`, and remove `LicenseKey`. Interfaces, method shapes, behavior constraints (`where TRequest : notnull`), `Send(object)`, `Publish(object)`, handlers for base notification types and generic handlers work as in MediatR. +Install with `dotnet add package AnyMediator`, register with `services.AddAnyMediator(cfg => cfg.RegisterServicesFromAssemblyContaining())`, and use the `AnyMediator` namespace. To migrate from MediatR: replace the package, replace `using MediatR;` and `using MediatR.Pipeline;` with `using AnyMediator;` (and `MediatR.NotificationPublishers` with `AnyMediator.NotificationPublishers`), replace `AddMediatR` with `AddAnyMediator`, and remove `LicenseKey`. Interfaces, method shapes, behavior constraints (`where TRequest : notnull`), `Send(object)`, `Publish(object)`, handlers for base notification types and generic handlers work as in MediatR. AnyMediator was published as OpenMediatR up to version 1.5.0; to upgrade, replace the OpenMediatR package with AnyMediator, `using OpenMediatR;` with `using AnyMediator;` and `AddOpenMediatR` with `AddAnyMediator`. AnyMediator is not affiliated with MediatR or Lucky Penny Software. @@ -20,8 +20,9 @@ Differences from MediatR: two handlers for the same request make `AddAnyMediator - [Pre- and post-processors](https://adiakys.github.io/AnyMediator/guide/processors): IRequestPreProcessor, IRequestPostProcessor - [Exception handling](https://adiakys.github.io/AnyMediator/guide/exception-handling): IRequestExceptionHandler, IRequestExceptionAction - [Notification sinks](https://adiakys.github.io/AnyMediator/guide/notification-sinks): INotificationSink for brokers and webhooks -- [Tracing and metrics](https://adiakys.github.io/AnyMediator/guide/tracing): OpenTelemetry spans and duration histogram via AnyMediatorDiagnostics.ActivitySourceName and MeterName - [Configuration](https://adiakys.github.io/AnyMediator/guide/configuration): every AddAnyMediator option +- [Tracing and metrics](https://adiakys.github.io/AnyMediator/guide/tracing): OpenTelemetry spans and duration histogram via AnyMediatorDiagnostics.ActivitySourceName and MeterName +- [Performance](https://adiakys.github.io/AnyMediator/guide/performance): time and memory per Send and Publish compared with MediatR and Mediator ## Optional