Skip to content

Features

9.2.1. OpenAPI Auto-Generation

Every generated endpoint gets full OpenAPI metadata at compile time — nothing to configure:

Source Generated metadata
Method name (CreateOrder) .WithName("Class_CreateOrder") + .WithSummary("Create order")
Class name (OrderController) .WithTags("Order") + MapGroup("/api/orders")
Success return type (Order) .Produces<Order>(200) or .Produces<Order>(201) for POST
OneOf error types .Produces(statusCode) per error (e.g. NotFoundError404)
int id parameter /{id} route segment
Request body parameter JSON body binding

9.2.2. Authorization

Class-level auth applies to all convention-mapped methods. Use [SmartAllowAnonymous] or [AutoMapEndpoint(AllowAnonymous = true)] to exempt individual methods:

[AutoGenerateEndpoints(RoutePrefix = "/api/orders", RequiresAuth = true,
    Policies = new[] { "CanReadOrders" })]
public class OrderController
{
    // Inherits RequiresAuth = true + "CanReadOrders" policy
    public Task<Result<Order>> GetOrder(int id) => ...;

    // [SmartAllowAnonymous] — override class-level auth for public reads
    [SmartAllowAnonymous]
    public Task<Result<List<OrderSummary>>> GetOrderSummaries() => ...;

    // [AutoMapEndpoint] — per-method roles override
    [AutoMapEndpoint("/api/orders", HttpMethod = "POST",
        Roles = new[] { "Admin", "OrderManager" })]
    public Task<Result<Order>> CreateOrder(CreateOrderRequest request) => ...;
}

9.2.3. Endpoint Filters — [SmartFilter]

Apply IEndpointFilter implementations to individual methods. Stack multiple filters — applied in declaration order:

[AutoGenerateEndpoints(RoutePrefix = "/api/products")]
public class ProductController
{
    // Single filter
    [SmartFilter(typeof(LoggingFilter))]
    public Task<Result<Product>> GetProduct(int id) => ...;

    // Stacked filters — LoggingFilter runs first, then ValidationFilter
    [SmartFilter(typeof(LoggingFilter))]
    [SmartFilter(typeof(ValidationFilter<CreateProductRequest>))]
    public Task<Result<Product>> CreateProduct(CreateProductRequest request) => ...;
}

9.2.4. Output Caching & Rate Limiting

Set response cache duration and rate limiting at class level (as defaults) and override per method:

[AutoGenerateEndpoints(
    RoutePrefix = "/api/catalog",
    CacheSeconds = 300,          // 5-minute GET cache for all methods
    RateLimitPolicy = "standard")]  // standard rate limit for all methods
public class CatalogController
{
    // Inherits 5-min cache + standard rate limit
    public Task<Result<List<Product>>> GetProducts() => ...;

    // Override: shorter cache, stricter rate limit
    [AutoMapEndpoint("/api/catalog/{id}", CacheSeconds = 60, RateLimitPolicy = "strict")]
    public Task<Result<Product>> GetProduct(int id) => ...;

    // Opt out: disable cache and rate limiting for this method
    [AutoMapEndpoint("/api/catalog", HttpMethod = "POST",
        CacheSeconds = -1, RateLimitPolicy = "none")]
    public Task<Result<Product>> CreateProduct(CreateProductRequest request) => ...;
}

9.2.5. Attribute Precedence Cheat Sheet

When class-level and method-level attributes overlap, the rule is always method wins over class. The table below shows the full resolution order for each configurable feature.

Feature Highest priority → Lowest priority Opt-out value
Auth: allow anonymous [SmartAllowAnonymous][AutoMapEndpoint(AllowAnonymous = true)](not set)
Auth: require auth [AutoMapEndpoint(RequiresAuth/Policies/Roles)] → class RequiresAuth/Policies/Roles [SmartAllowAnonymous]
Output cache [AutoMapEndpoint(CacheSeconds = N)] → class CacheSeconds → no cache -1 (method-level disables even if class sets it)
Rate limiting [AutoMapEndpoint(RateLimitPolicy = "X")] → class RateLimitPolicy → no limiting "none" (method-level disables even if class sets it)
Endpoint filters [SmartFilter] on the method only — no class-level inheritance remove the attribute
OpenAPI summary XML <summary> doc → [AutoMapEndpoint(Summary)] → PascalCase inference
Route / HTTP method [AutoMapEndpoint(Route, HttpMethod)] → naming convention (Get*, Create*, …)

Key rules to remember: - [SmartAllowAnonymous] is atomic — it wins over everything, no other auth check applies to that method. - -1 and "none" are explicit opt-outs, not zero values. CacheSeconds = 0 means "use class default"; CacheSeconds = -1 means "disable cache for this method." - Filters never inherit from the class — every method that needs a filter must declare it explicitly.

9.2.6. Auto-Validation

Decorate a request type with [Validate] (from REslava.Result.SourceGenerators) and SmartEndpoints injects the validation call automatically — no extra code in the controller method needed:

[Validate]
public record CreateProductRequest(
    [Required] string Name,
    [Range(0.01, double.MaxValue)] decimal Price);

[AutoGenerateEndpoints(RoutePrefix = "/api/catalog")]
public class CatalogController
{
    public Task<Result<Product>> CreateProduct(CreateProductRequest request) => ...;
}

Generated lambda (v1.26.0+):

catalogGroup.MapPost("", async (CreateProductRequest request, CatalogController service) =>
{
    var validation = request.Validate();
    if (!validation.IsSuccess) return validation.ToIResult();  // 422 on failure
    var result = await service.CreateProduct(request);
    return result.ToIResult();
});

Before v1.26.0 this required a manual .Validate().ToIResult() in the endpoint body. Now placing [Validate] on the request type is the only signal required. Only POST/PUT body parameters are auto-validated; GET query parameters are not affected.