diff --git a/INSTRUCT.md b/INSTRUCT.md new file mode 100644 index 0000000..9002b7c --- /dev/null +++ b/INSTRUCT.md @@ -0,0 +1,50 @@ +# GeneratedEndpoints Minimal API Implementation Guide + +## 1. Package integration +- Install the `GeneratedEndpoints` NuGet package in the ASP.NET Core project that hosts Minimal APIs, then add `using Microsoft.AspNetCore.Generated.Routing;` to `Program.cs`. Call `builder.Services.AddEndpointHandlers();` before building the app and `app.MapEndpointHandlers();` after building so the generated extension methods can register services and map the discovered handlers. The generator outputs both extension methods automatically, so no manual implementation is necessary.【F:README.md†L15-L39】 +- `AddEndpointHandlers` is emitted inside `Microsoft.AspNetCore.Generated.Routing.EndpointServicesExtensions` and registers every non-static endpoint class as a `Scoped` service via `TryAddScoped()`, ensuring constructor-injected dependencies are available even when multiple endpoints share a class instance.【F:src/GeneratedEndpoints/AddEndpointHandlersGenerator.cs†L20-L66】 +- `MapEndpointHandlers` is emitted inside `Microsoft.AspNetCore.Generated.Routing.EndpointRouteBuilderExtensions`. It returns the `IEndpointRouteBuilder`, creates any `[MapGroup]` route groups, and maps each handler method with the metadata assembled by the generator so the call can be chained during application startup.【F:src/GeneratedEndpoints/UseEndpointHandlersGenerator.cs†L29-L95】 + +## 2. How handlers are discovered +- The generator analyzes every class method that is decorated with one of the provided `[Map*]` attributes (GET/POST/PUT/PATCH/DELETE/HEAD/OPTIONS/TRACE/CONNECT/QUERY/FALLBACK). These attributes live in `Microsoft.AspNetCore.Generated.Attributes` and are injected into the compilation during generator initialization, so the consuming project only needs to add the `using` directive. `[MapFallback]` routes get special handling so they call `MapFallback` (optionally with a pattern) instead of a verb-specific API.【F:README.md†L41-L88】【F:src/GeneratedEndpoints/MinimalApiGenerator.cs†L25-L137】【F:src/GeneratedEndpoints/UseEndpointHandlersGenerator.cs†L154-L182】 +- Only members of `class` types participate; interfaces, records, and structs are ignored because the generator explicitly requires the containing type to be a class before producing a handler descriptor.【F:src/GeneratedEndpoints/Common/RequestHandlerClassHelper.cs†L8-L31】 +- Handler methods can be `static` or instance. Static methods are mapped directly, while instance methods are wrapped in a generated lambda that resolves the handler via `[FromServices]` and forwards the request parameters, preserving your method signatures exactly.【F:src/GeneratedEndpoints/UseEndpointHandlersGenerator.cs†L183-L214】 +- Endpoint names default to the method name with any `Async` suffix removed, but the `Name` named argument on the `[Map*]` attributes overrides it. When two endpoints end up with the same name, the generator rewrites those names to `Full.Type.Name.Method` to avoid registration collisions.【F:src/GeneratedEndpoints/MinimalApiGenerator.cs†L130-L191】【F:src/GeneratedEndpoints/Common/RequestHandler.cs†L5-L40】 + +## 3. Handler class patterns +- Use static classes when the handler does not need constructor injection. Use instantiable classes (including primary constructors) when you want to inject services once per request; `AddEndpointHandlers` will register the class as scoped so Minimal APIs can resolve it.【F:README.md†L90-L112】【F:src/GeneratedEndpoints/AddEndpointHandlersGenerator.cs†L51-L57】 +- Keep handler methods in the feature or slice that owns the behavior. The generator groups handlers by containing class, which enables class-wide attributes and configuration to cascade to every method.【F:src/GeneratedEndpoints/Common/EndpointConfigurationFactory.cs†L12-L180】 +- Optionally define a static `Configure(TBuilder builder)` method on the handler class (with `TBuilder` constrained to `IEndpointConventionBuilder`). A second `IServiceProvider` parameter is allowed. When present, the generator wraps every mapped endpoint in a call to `Configure`, letting you apply custom conventions or DI-driven setup across the class.【F:README.md†L114-L139】【F:src/GeneratedEndpoints/Common/RequestHandlerClassHelper.cs†L33-L95】【F:src/GeneratedEndpoints/UseEndpointHandlersGenerator.cs†L139-L245】 + +## 4. Parameters and dependency injection +- All method parameters are preserved in the generated delegate. The generator inspects parameter attributes to decide how to annotate them, automatically applying `[FromRoute]`, `[FromQuery]`, `[FromHeader]`, `[FromBody]`, `[FromForm]`, `[FromServices]`, `[FromKeyedServices]`, or `[AsParameters]` (including custom binding names and keyed service values) when you decorate the parameter accordingly.【F:src/GeneratedEndpoints/Common/MethodSymbolExtensions.cs†L13-L105】 +- Instance handlers receive `([FromServices] HandlerClass handler, ...) => handler.Method(...)` delegates, so constructor-injected members remain available without manually wiring DI in each endpoint.【F:src/GeneratedEndpoints/UseEndpointHandlersGenerator.cs†L183-L214】 +- Use standard Minimal API types for return values (`IResult`, typed results, `Results`, DTOs, etc.). The generator simply passes through your method’s return type to ASP.NET Core’s routing infrastructure. + +## 5. Attribute-driven endpoint metadata +- Apply class-level attributes to broadcast settings to all endpoints in the class, and decorate methods to override or augment them. The generator merges the two configurations, giving method-level directives precedence for authorization, request timeout, CORS, and rate limiting while combining tags, filters, and metadata arrays.【F:src/GeneratedEndpoints/Common/EndpointConfigurationFactory.cs†L12-L180】【F:src/GeneratedEndpoints/UseEndpointHandlersGenerator.cs†L216-L246】【F:src/GeneratedEndpoints/UseEndpointHandlersGenerator.cs†L477-L637】 +- Supported metadata attributes (apply to class and/or method as indicated in the README reference table) include: + - Visibility & documentation: `[DisplayName]`, `[Summary]`, `[Description]`, `[Tags]`, `[ExcludeFromDescription]` control `.WithDisplayName`, `.WithSummary`, `.WithDescription`, `.WithTags`, and `.ExcludeFromDescription()` calls on the generated endpoint builder.【F:README.md†L141-L180】【F:src/GeneratedEndpoints/UseEndpointHandlersGenerator.cs†L249-L311】 + - Contracts: `[Accepts]`, `[ProducesResponse]`, `[ProducesProblem]`, `[ProducesValidationProblem]` expand to `.Accepts()` and `.Produces*()` builder calls, including optional status codes, content types, and `IsOptional` flags.【F:README.md†L145-L173】【F:src/GeneratedEndpoints/UseEndpointHandlersGenerator.cs†L312-L351】 + - Security & networking: `[RequireAuthorization]`, `[AllowAnonymous]`, `[RequireCors]`, `[RequireHost]`, `[RequireRateLimiting]`, `[RequestTimeout]`, `[DisableRequestTimeout]`, `[DisableAntiforgery]`, `[DisableValidation]`, `[ShortCircuit]`, `[RequireCors]`, `[RequireRateLimiting]`, `[RequireHost]`, and `[RequireRateLimiting]` translate into the corresponding builder methods. Authorization directives obey the precedence rules in `ResolveAuthorization`, so method-level attributes override class-level ones. Request-timeout and CORS/rate-limiting policies behave similarly through their resolver helpers.【F:README.md†L147-L178】【F:src/GeneratedEndpoints/Common/EndpointConfigurationFactory.cs†L81-L138】【F:src/GeneratedEndpoints/UseEndpointHandlersGenerator.cs†L364-L475】【F:src/GeneratedEndpoints/UseEndpointHandlersGenerator.cs†L497-L637】 + - Pipeline customization: `[EndpointFilter]` / `[EndpointFilter]` register filters exactly once per type, `[DisableAntiforgery]`, `[DisableValidation]`, `[ShortCircuit]`, and `[Order]` become `.AddEndpointFilter()`, `.DisableAntiforgery()`, `.DisableValidation()`, `.ShortCircuit()`, and `.WithOrder()` respectively.【F:README.md†L149-L180】【F:src/GeneratedEndpoints/Common/EndpointConfigurationFactory.cs†L75-L123】【F:src/GeneratedEndpoints/UseEndpointHandlersGenerator.cs†L287-L475】 +- Use `[MapGroup("/pattern", Name = "GroupName")]` on the class to generate a route group (`builder.MapGroup(pattern)`), optional group names (`.WithGroupName("...")`), and a reusable builder identifier shared by every handler inside that class.【F:README.md†L159-L166】【F:src/GeneratedEndpoints/Common/EndpointConfigurationFactory.cs†L67-L71】【F:src/GeneratedEndpoints/UseEndpointHandlersGenerator.cs†L56-L75】【F:src/GeneratedEndpoints/UseEndpointHandlersGenerator.cs†L278-L285】 + +## 6. Configure method usage +- Implement `public static void Configure(TBuilder builder)` (or `public static void Configure(TBuilder builder, IServiceProvider sp)`) where `TBuilder` is constrained to `IEndpointConventionBuilder`. The generator wraps every endpoint mapping in `HandlerClass.Configure(builder => ...)` so you can apply fluent calls not covered by attributes—e.g., `.AddEndpointFilterFactory(...)` or `.WithMetadata(new CustomMetadata())`. If you request `IServiceProvider`, the generator automatically passes `builder.ServiceProvider`. This hook executes once per handler after all attribute-driven configuration has been appended.【F:README.md†L114-L139】【F:src/GeneratedEndpoints/Common/RequestHandlerClassHelper.cs†L33-L95】【F:src/GeneratedEndpoints/UseEndpointHandlersGenerator.cs†L139-L245】 + +## 7. Route mapping behavior +- Each handler ultimately calls the same Minimal API surface area you would use manually. Verb attributes become `builder.MapGet`, `builder.MapPost`, etc., while non-standard verbs fall back to `builder.MapMethods(pattern, new[] { "VERB" })`. `[MapFallback]` uses `builder.MapFallback(pattern?, handler)`. Every mapping returns the builder instance so the generated code can continue chaining metadata and filters.【F:src/GeneratedEndpoints/UseEndpointHandlersGenerator.cs†L154-L214】 +- When `[MapGroup]` is applied, the generator creates a single variable (e.g., `_MyFeature_Group`) that holds the grouped `RouteGroupBuilder`. All endpoints in the class call `.Map*` on that variable so you can attach shared conventions to the group in `Configure` or via class-level attributes.【F:src/GeneratedEndpoints/UseEndpointHandlersGenerator.cs†L56-L75】 +- After mapping, `.WithName`, `.WithDisplayName`, `.WithSummary`, `.WithDescription`, `.WithGroupName`, `.WithOrder`, `.WithTags`, `.Accepts`, `.Produces*`, `.RequireAuthorization`, `.RequireCors`, `.RequireHost`, `.RequireRateLimiting`, `.DisableAntiforgery`, `.AllowAnonymous`, `.ShortCircuit`, `.DisableValidation`, `.WithRequestTimeout`, `.DisableRequestTimeout`, `.AddEndpointFilter`, etc., are appended in the exact order emitted inside `AppendEndpointConfiguration`, ensuring consistent, deterministic metadata regardless of how many attributes are applied.【F:src/GeneratedEndpoints/UseEndpointHandlersGenerator.cs†L249-L475】 + +## 8. Implementation recipe for AI agents +1. **Create or update the hosting project:** make sure it references the `GeneratedEndpoints` package and imports `Microsoft.AspNetCore.Generated.Routing` so startup can call the generated extension methods. No manual source inclusion is required.【F:README.md†L15-L39】 +2. **Define a feature class:** add a `static` or instantiable `class` inside the relevant namespace (e.g., `Features.Users`). If it needs DI, add constructor parameters; otherwise keep it static.【F:README.md†L90-L112】【F:src/GeneratedEndpoints/Common/RequestHandlerClassHelper.cs†L12-L31】 +3. **Author handler methods:** decorate each method with the appropriate `[Map*]` attribute from `Microsoft.AspNetCore.Generated.Attributes`. Keep method signatures natural—parameters map directly, and return types follow normal Minimal API rules. Supply explicit `Name` arguments only when you need deterministic endpoint names or to avoid `Async` suffix removal.【F:README.md†L41-L88】【F:src/GeneratedEndpoints/MinimalApiGenerator.cs†L130-L137】 +4. **Apply metadata attributes:** add `[Summary]`, `[Description]`, `[Tags]`, `[Accepts]`, `[Produces*]`, `[RequireAuthorization]`, `[AllowAnonymous]`, `[RequireCors]`, `[RequireHost]`, `[RequireRateLimiting]`, `[RequestTimeout]`, `[DisableRequestTimeout]`, `[DisableAntiforgery]`, `[DisableValidation]`, `[ShortCircuit]`, `[Order]`, `[EndpointFilter]`, etc., to the class or method as needed. Rely on the generator to merge and emit the matching fluent calls.【F:README.md†L141-L178】【F:src/GeneratedEndpoints/Common/EndpointConfigurationFactory.cs†L12-L180】【F:src/GeneratedEndpoints/UseEndpointHandlersGenerator.cs†L249-L475】 +5. **Bind parameters explicitly when required:** decorate method parameters with `[FromRoute(Name = "...")]`, `[FromQuery]`, `[FromHeader]`, `[FromBody]`, `[FromForm]`, `[FromServices]`, `[FromKeyedServices("key")]`, or `[AsParameters]` so the generator stamps the correct binding attributes into the lambda. Otherwise, ASP.NET Core’s default binding rules apply automatically.【F:src/GeneratedEndpoints/Common/MethodSymbolExtensions.cs†L35-L105】 +6. **Optional group & configure:** add `[MapGroup("/users", Name = "Users")]` for a feature-specific route group, and add a `Configure` method if you need additional fluent customization. Both apply once per class, affecting every endpoint within it.【F:src/GeneratedEndpoints/Common/EndpointConfigurationFactory.cs†L67-L79】【F:src/GeneratedEndpoints/UseEndpointHandlersGenerator.cs†L56-L75】【F:src/GeneratedEndpoints/Common/RequestHandlerClassHelper.cs†L33-L95】 +7. **Build and run:** the generator emits DI registration and mapping code at compile time. Startup only needs to call the two extension methods; the rest of the boilerplate is source-generated and remains up to date as you add, rename, or remove handlers.【F:README.md†L21-L39】【F:src/GeneratedEndpoints/MinimalApiGenerator.cs†L143-L153】 + +Following these rules keeps endpoint definitions close to their features while letting the generator handle registration, metadata, routing, and dependency injection in a deterministic, attribute-driven way.