A feature is a folder, not a new layer. Copy the nearest existing slice. Application/Features/Messages/GetMessageStatus/ is a small read. Application/Features/Branding/CreateBrandProfile/ is a small write. Template and provider behavior already live under src/Mursil/Modules/.

Order of work

  1. Decide the rule. If the rule is an invariant (a version cannot publish twice, a domain name is required), put it on the domain type. If it is coordination (load, validate, save), put it in the handler.
  2. Add the contract in Mursil.Contracts when the HTTP body or response gains a field. Keep the DTO free of behavior.
  3. Add the handler as a MediatR IRequestHandler. Place a FluentValidation validator beside it when the command has input rules. Return a Result. Do not throw for a validation failure the caller can correct.
  4. Persist through a repository or specification. Do not put an EF Core query in the handler.
  5. Translate HTTP in the endpoint. The endpoint reads headers through MursilRequestContextResolver, sends the command, and maps Result to a status code. It does not decide business outcomes.
  6. Add an admin page only when an operator must see the feature. Pages live in Mursil.Admin and call IMursilOperationsClient. Authorize the page with an existing policy (Viewer, TemplateEditor, Operators, TenantOwner).
  7. Test the behavior. Domain rules belong in src/Tests/Mursil/Core/Mursil.Domain.UnitTests. Handler behavior belongs in src/Tests/Mursil/Application/Mursil.Application.UnitTests. Run those projects with dotnet run --project.

Endpoint shape

Mursil.Server groups every product route under /api/v1/mursil:

  var mursilApi = app.MapGroup("/api/v1/mursil");
mursilApi.MapMursilEndpoints();
mursilApi.MapMursilTemplateEndpoints();
mursilApi.MapMursilProviderEndpoints();
  

A new route joins the registration that already owns that area. Require an existing policy. Read the tenant with TryReadTenant. That method ignores any caller-supplied tenant id while Mursil:MultiTenancy is false and uses the deployment tenant. Read the application and client with TryReadApplicationScope on routes that act for one application.

Database changes

Add an EF Core migration through Mursil.Migrator. Prefer an expand-and-contract change when a column is already in use. Do not edit a migration that has already been applied.

What this repository does not take

Do not add a CleanArch Pro project reference. Do not vendor framework source. Do not add a new architectural layer. Do not put business rules in the Razor page or the minimal API method. Do not create a Mursil admin or UI package. The portal remains Mursil.Admin.