DKNet.Templates

The shape your endpoints should take

Composite-first. One endpoint class per aggregate: declare the group’s authorization scopes on the class, map the generated CRUD composite at the top, then hand-write below it only the routes that carry real orchestration. Generated and hand-written routes live in the same group — this is not a choice between two styles of endpoint.

ProductV1Endpoint (Minimal.Api/ApiEndpoints/AutomatedSample/ProductV1Endpoint.cs) is that shape, trimmed here to its structure:

[EndpointGroupScope(ProductScopes.Read, EndpointHttpMethods.Get)]
[EndpointGroupScope(ProductScopes.Write, EndpointHttpMethods.Post, EndpointHttpMethods.Put,
    EndpointHttpMethods.Delete)]
internal sealed class ProductV1Endpoint : IEndpointConfig
{
    public void Map(RouteGroupBuilder group)
    {
        group.MapProductCrud(o =>
        {
            o.Exclude("Discontinue");                                        // hand-written below

            // a PUT like Update, so the per-method declaration cannot separate them — holding
            // products.write must not be enough to assign a supplier reference
            if (requireAuthorization)
                o.Configure("AssignSupplierReference", rb => rb.RequireAuthorization(ProductScopes.Supplier));
        });

        // writes two aggregates in one transaction — outside what the generator can express
        group.MapPut("{id:guid}/discontinue", /* ... */).Produces<ProductDto>();

        // a response shape the generator has none for; the group's GET declaration already covers it
        group.MapGet("summary", /* ... */).Produces<ProductPriceSummaryDto>();
    }
}

Seven of the nine routes come from MapProductCrud. [EndpointGroupScope] names the scope per HTTP method once and covers generated and hand-mapped routes alike; a per-route Configure/ RequireAuthorization is needed only where two routes share an HTTP method but not a scope. Scopes are applied only when FeatureManagement:RequireAuthorization is on, so a stock Development run enforces none of them — the attribute checks that flag itself, which is why the per-route overrides are the only calls that need an explicit guard.

When an operation needs a hand-written route

The criterion is stated once, in Manual vs. Automated — At a glance, and this page does not carry a second version of it. Read it before you hand-write a handler: the list is shorter than most readers expect, and two of the cases that look like they belong on it do not.

Both samples ship in full: Products (generator-driven Product) and Purchase Orders (hand-written PurchaseOrder). The end-to-end steps for a feature of your own are in the DDD implementation guide.