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. NotFoundError → 404) |
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.