# dotnetcore/CAP: An Outbox Pattern Event Bus for .NET Microservices

> CAP is a .NET library that pairs a local message table with a message broker so published events survive a crash between the database commit and the queue write. It is for teams already running SQL Server, MySQL, PostgreSQL or MongoDB alongside RabbitMQ or Kafka.

**dotnetcore/CAP** — Distributed transaction solution in micro-service base on eventually consistency, also an eventbus with Outbox pattern

- Repository: https://github.com/dotnetcore/CAP
- Website: http://cap.dotnetcore.xyz
- Stars: 7,115 · Forks: 1,336
- Language: C#
- License: MIT
- Published: 2026-09-22 · Updated: 2026-09-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/dotnetcore-cap

## The gap CAP fills between a database commit and a broker publish

A service writes an order row, commits, then calls the broker to announce it. If the process dies in between, the order exists and nobody is told. Wrap both in a distributed transaction and you inherit the coordination cost. CAP takes the third route: the outgoing message is written to a local message table inside the same database as the business data, in the same transaction. The README describes this as leveraging a local message table, integrated with your current database, to solve exceptions that can occur during distributed system communications, and points at the Outbox Pattern as described in the eShop on .NET ebook. The audience is .NET teams building SOA or microservice systems where a lost event is a real business problem, not a logging inconvenience. If your events are metrics or cache invalidation hints, the machinery is heavier than the problem.

## How the Outbox table and the dispatcher actually move a message

Two halves. On publish, CAP writes a row into the event log table that the storage package creates in your database, and that write joins whatever transaction you are already running. On the other side, a background dispatcher reads undispatched rows, hands them to the configured transport, and marks them sent. That ordering is what makes the guarantee meaningful: the row cannot exist without the business data, and the broker call happens after the commit, so a crash before dispatch leaves a row to be retried rather than a lost event. Subscribers are equally undemanding. The README states CAP offers a simplified approach to event publishing and subscribing without requiring you to inherit or implement any specific interfaces, and subscriptions can be attribute-based, use wildcards (* and #), or match partial topics. Consumer groups give you competing consumers or fan-out, and processing can be configured parallel for throughput or serial when order matters. A backpressure mechanism is documented to manage processing speed and prevent memory overload under load, which matters because the dispatcher and the consumers share a process with your application.

## Installing dotnetcore/CAP and publishing your first event

Everything comes from NuGet. The README gives PowerShell install commands for the core package plus one transport and one storage provider; the main package is DotNetCore.CAP, and you need at least one of each of the others. A RabbitMQ plus SQL Server combination would be installed like this.

```bash
PM> Install-Package DotNetCore.CAP
PM> Install-Package DotNetCore.CAP.RabbitMQ
PM> Install-Package DotNetCore.CAP.SqlServer
```

The README lists the other choices in the same form: DotNetCore.CAP.Kafka, DotNetCore.CAP.AzureServiceBus, DotNetCore.CAP.AmazonSQS, DotNetCore.CAP.NATS, DotNetCore.CAP.RedisStreams and DotNetCore.CAP.Pulsar for transports, and DotNetCore.CAP.MySql, DotNetCore.CAP.PostgreSql and DotNetCore.CAP.MongoDB for storage. After the install, the event log table is created in the database you selected.

## Registering CAP and choosing a transport in Program.cs

Registration happens through AddCap in ConfigureServices or Program.cs. You pick exactly one storage provider and one transport. The README shows UseEntityFramework with an AppDbContext so CAP auto-discovers the connection string, or explicit UseSqlServer, UseMySql, UsePostgreSql and UseMongoDB calls taking a connection string. MongoDB is documented as requiring a 4.0+ cluster. The transport is the second call in the same block.

```csharp
services.AddCap(x =>
{
    x.UseEntityFramework<AppDbContext>();
    x.UseRabbitMQ("HostName");
});
```

Swapping the transport means swapping that second line for x.UseKafka("ConnectionString"), x.UseAzureServiceBus("ConnectionString"), x.UseNATS("ConnectionString"), x.UsePulsar("ConnectionString") or x.UseRedisStreams("ConnectionString").

## Publishing inside a transaction you already own

Inject ICapPublisher and publish. The README's ADO.NET example opens a transaction with BeginTransaction(_capBus, autoCommit: true), runs business logic, and calls _capBus.Publish("xxx.services.show.time", DateTime.Now) inside it. The autoCommit flag is what ties the message row to the same commit as your business write. What you should see after running this is a row in the CAP event log table in your database, and, once the dispatcher picks it up, a message on the configured broker topic. Delayed messages are also supported as of version 7.0, published with a delay without relying on message queue features, so the timing is held on the CAP side rather than delegated to the broker.

```csharp
using (var connection = new MySqlConnection(ConnectionString))
{
    using (var transaction = connection.BeginTransaction(_capBus, autoCommit: true))
    {
        _capBus.Publish("xxx.services.show.time", DateTime.Now);
    }
}
```

## Where CAP stops being the right tool

The guarantee is bounded by the database. If the event log table lives in the same database as your business data, that database becomes a hard dependency of every publish path; a service that cannot reach it cannot publish, and the Outbox pattern buys you nothing if the two are ever split across stores that can fail independently. The dispatcher is a background worker inside your process, so it competes for the same resources as your request handling, and the backpressure mechanism exists precisely because that competition is real. Storage support is also uneven in kind, not just in name: MongoDB is called out as needing a 4.0+ cluster, and the README does not document rollback behaviour for a message that was dispatched but whose consumer failed, beyond automatic retries for failed messages. If you need strict ordering across all messages in a topic, or exactly-once consumer semantics, this is eventual consistency, and the README says so in its own description.

## CAP against a plain broker client or a saga framework

The obvious alternative is using the broker's own client directly, MassTransit or Confluent.Kafka, and accepting that a publish can be lost between commit and send. That is a legitimate choice when an occasional dropped event is tolerable or when a reconciliation job already repairs state. The difference in approach is where the durability lives: a plain client trusts the broker to be reachable at the moment of the call, while CAP writes the intent to your database first and lets a dispatcher catch up later. The other alternative is a saga or process-manager framework that coordinates compensating actions across services. That solves a different problem. CAP does not orchestrate a multi-step business process; it makes each individual publish durable and each subscription retryable, and leaves compensation to your code. Teams sometimes reach for a saga library when what they actually needed was an Outbox.

## Maintenance, versioning and the MIT licence

The repository is not archived, and the last push was on 2026-08-01, which is also the date of the v10.0.2 release. The release cadence visible in the list is roughly one minor line per several months, with v10.0.0 in November 2025, v10.0.1 in January 2026 and v10.0.2 in August 2026. Upgrading between those patch releases is a NuGet version bump, but the 7.0 delayed-message feature and the 10.x line are the kind of boundaries where you should read the release notes before moving, since the API surface has grown across major versions. The licence is MIT, which permits commercial use and modification; as with any dependency, the terms that matter to you are between you and your legal counsel, and nothing here is legal advice. Practically, the cost of adoption is the extra table in your database and a background dispatcher per publishing service, both of which you carry for as long as you use CAP.

## Conclusion

Adopt CAP when your service already owns a relational or MongoDB database and you need published events to survive a process crash between commit and broker write; the Outbox table lives in that same database, so the guarantee is only as good as the connection string you point it at. Skip it if your events are fire-and-forget telemetry, or if you cannot accept an extra table and a background dispatcher inside every publishing service. Before committing, verify which storage package matches your database (DotNetCore.CAP.MySql, DotNetCore.CAP.PostgreSql, DotNetCore.CAP.SqlServer or DotNetCore.CAP.MongoDB, the last needing a 4.0+ cluster), confirm your broker package exists in the transport list, and read the transaction example in the README to see whether autoCommit fits your existing unit-of-work code.

## FAQ

### What is dotnetcore/CAP used for?

It is a .NET library for distributed transactions and event bus integration in SOA or microservice systems, using a local message table to make sure event messages are never lost. It can also be used as a standalone EventBus for publishing and subscribing without implementing specific interfaces.

### How do I install dotnetcore/CAP?

Install the DotNetCore.CAP package from NuGet, then add one transport package such as DotNetCore.CAP.RabbitMQ or DotNetCore.CAP.Kafka and one storage package such as DotNetCore.CAP.SqlServer or DotNetCore.CAP.MySql. The README gives the exact Install-Package commands for each.

### Which message queues and databases does dotnetcore/CAP support?

The README lists RabbitMQ, Kafka, Azure Service Bus, Amazon SQS, NATS, Redis Streams and Pulsar as transports, and SQL Server, MySQL, PostgreSQL and MongoDB as storage. MongoDB storage requires a 4.0+ cluster.

### Does dotnetcore/CAP have a dashboard?

Yes. The README describes a built-in real-time web dashboard for monitoring messages, viewing status and manually retrying failed ones. The repository also contains dashboard sample projects, including samples/Sample.Dashboard.Auth and samples/Sample.Dashboard.Jwt.

### What is the difference between dotnetcore/CAP and using Kafka or RabbitMQ directly?

CAP writes the outgoing message to a local message table in your database in the same transaction as your business data, then a background dispatcher sends it to the broker. A raw broker client publishes directly, so a crash between commit and send can lose the event.

## Sources

- [dotnetcore/CAP on GitHub](https://github.com/dotnetcore/CAP)
- [License: MIT](https://github.com/dotnetcore/CAP/blob/master/LICENSE)
- [Project website](http://cap.dotnetcore.xyz)
- [README](https://github.com/dotnetcore/CAP/blob/master/README.md)
- [Releases](https://github.com/dotnetcore/CAP/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/dotnetcore-cap
