MediatR, MVC, and Arc
If you’ve built ASP.NET Core apps with controllers, DTOs, and MediatR, you already know most of the concepts in Arc. This page maps those familiar pieces onto Arc’s command/query model.
The one-paragraph version
Section titled “The one-paragraph version”In MVC, a controller action takes a request model, validates it, calls a handler, and returns a response. With MediatR, that often becomes an IRequest and an IRequestHandler. In Arc, the command is a record with a Handle() method on it; Arc maps that command to an HTTP endpoint and generates a typed client for your frontend.
How the pieces map
Section titled “How the pieces map”| You know (MediatR / MVC) | In Arc |
|---|---|
IRequest<T> + IRequestHandler<T> | A [Command] record with Handle() defined on the record |
| Controller action + routing attributes | Automatic — Arc maps the command/query to HTTP for you |
| Request/response DTOs | The command record itself; the result is what Handle() returns |
FluentValidation / ModelState | A CommandValidator<T> discovered by convention |
| MediatR pipeline behaviors | The command pipeline and filters |
INotification / handlers | A follow-up command, domain service, or optional Chronicle reactor when you are event-sourcing |
| A query action returning a DTO from EF | A query method on a [ReadModel], served directly over HTTP |
Application-specific fetch/HttpClient on the frontend | A generated TypeScript proxy |
What stays the same
Section titled “What stays the same”- You still think in commands and queries — the CQRS split you already use with MediatR is first-class here.
- You still write small, focused handlers and validators.
- You still use dependency injection; constructor-inject collaborators into
Handle()and into validators.
What changes
Section titled “What changes”- Commands are endpoints. The command record carries the request shape and the
Handle()method; Arc maps it to HTTP. - The handler sits with the intent.
Handle()lives on the command record, so the intent and its implementation sit together in one vertical slice instead of acrossCommands/andHandlers/folders. - The frontend model is generated. The proxy is generated from your C# types, so command/query shape changes are caught by TypeScript.
- The read side is explicit. Instead of returning arbitrary DTOs from controllers, you name the read model and expose query methods on it. Those methods are generated into the frontend just like commands.
A side-by-side
Section titled “A side-by-side”A “register customer” feature in MediatR + MVC is often a request record, a handler, a validator, a controller action, and a frontend client call. In Arc, the same feature is modeled as a slice:
[Command]public record RegisterCustomer(CustomerId Id, CompanyName Name){ public Task Handle(IMongoCollection<Customer> customers) => customers.InsertOneAsync(new Customer(Id, Name));}
[ReadModel]public record Customer([property: Key] CustomerId Id, CompanyName Name){ public static ISubject<IEnumerable<Customer>> AllCustomers(IMongoCollection<Customer> customers) => customers.Observe();}Arc exposes both members over HTTP and generates the typed client. Your React component calls the generated command and query proxies; no controller, DTO mapper, or hand-written fetch layer sits between them.
Where to go next
Section titled “Where to go next”- Build your first command in the getting started guide.
- See the full command and query options under Backend.
- Need history, replay, or reactors later? The Chronicle integration shows how Arc adds event sourcing as an optional write-side choice.