diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 6ab4565..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 @@ -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' 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 78ef404..6424233 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: @@ -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 diff --git a/context7.json b/context7.json new file mode 100644 index 0000000..a3abcd5 --- /dev/null +++ b/context7.json @@ -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())`.", + "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, 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 3289637..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.' @@ -10,6 +12,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' }, @@ -71,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/, '') @@ -79,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 fa0638c..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,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() 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') diff --git a/docs/guide/mediatr-alternatives.md b/docs/guide/mediatr-alternatives.md new file mode 100644 index 0000000..1a9e1de --- /dev/null +++ b/docs/guide/mediatr-alternatives.md @@ -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. diff --git a/docs/guide/migrating-from-mediatr.md b/docs/guide/migrating-from-mediatr.md index 385789e..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 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 @@ -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/llms.txt b/docs/public/llms.txt index e632ac7..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;` 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 `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 @@ -19,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