Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ concurrency:
jobs:
build:
runs-on: ubuntu-latest
outputs:
indexnow: ${{ steps.indexnow.outputs.request }}
defaults:
run:
working-directory: docs
Expand All @@ -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
Expand All @@ -55,3 +61,12 @@ 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.
- name: Submit the pages to IndexNow
continue-on-error: true
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'
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ BenchmarkDotNet.Artifacts/
docs/node_modules/
docs/.vitepress/cache/
docs/.vitepress/dist/
docs/.vitepress/indexnow.json
.taste-skill/
13 changes: 3 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -393,16 +393,9 @@ services.AddAnyMediator(cfg =>

Requests, notifications, handlers, pipeline behaviors, processors and exception handlers/actions compile unchanged once the namespace is updated.

### Differences to check
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.

| Area | MediatR | AnyMediator |
|------|---------|-------------|
| License | Commercial license from v13 | Apache-2.0, free for any use |
| `LicenseKey` | Required for commercial use | 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 |

`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

Expand Down
15 changes: 15 additions & 0 deletions context7.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"$schema": "https://context7.com/schema/context7.json",
"projectTitle": "AnyMediator",
"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"],
"rules": [
"Install with `dotnet add package AnyMediator` and register with `services.AddAnyMediator(cfg => cfg.RegisterServicesFromAssemblyContaining<Program>())`.",
"The core types live in the `AnyMediator` namespace (the notification publisher strategies in `AnyMediator.NotificationPublishers`): `IRequest<T>`, `IRequestHandler<TRequest, TResponse>`, `INotification`, `INotificationHandler<T>`, `ISender`, `IPublisher`, `IMediator`, `IPipelineBehavior<TRequest, TResponse>`.",
"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."
]
}
14 changes: 13 additions & 1 deletion docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ 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 <siteUrl><key>.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.'

// Order matters: it is the sidebar order and the order of llms-full.txt.
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' },
Expand Down Expand Up @@ -71,14 +74,23 @@ 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/, '')
return `<!-- ${siteUrl}${link.slice(1)} -->\n${body.trim()}`
})
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: {
Expand Down
18 changes: 13 additions & 5 deletions docs/.vitepress/theme/Home.vue
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ const source = ref<string>(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<string, string> = {
'': 'AnyMediator',
'.Pipeline': 'AnyMediator',
'.NotificationPublishers': 'AnyMediator.NotificationPublishers',
}

function pickExample(name: ExampleName) {
example.value = name
source.value = examples[name]
Expand All @@ -79,16 +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<string>()

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)
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')
Expand Down
56 changes: 56 additions & 0 deletions docs/guide/mediatr-alternatives.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
---
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 |
|---|---|---|---|---|---|---|
| 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, 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.

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](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)

---

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.
18 changes: 11 additions & 7 deletions docs/guide/migrating-from-mediatr.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. 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

Expand All @@ -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

Expand All @@ -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<NotificationHandlerExecutor>, INotification, CancellationToken)` | `Publish(IEnumerable<Func<Task>>, 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.

Expand All @@ -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

Expand Down
Loading
Loading