Troubleshooting
Practical problem/solution guide for Pragmatic.Endpoints. Each section covers a common issue, the likely causes, and the fix.
Endpoint Not Registering (404 on All Routes)
Section titled “Endpoint Not Registering (404 on All Routes)”Your endpoint class compiles, but every request returns 404.
Checklist
Section titled “Checklist”-
Is the class
partial? The SG cannot generate the handler withoutpartial. You will see diagnostic PRAG0500 if this is missing. -
Does it inherit from a valid base class? The class must inherit from
Endpoint<T>,VoidEndpoint,DomainAction<T>,VoidDomainAction, orMutation<T>. Without this, the SG skips it entirely (PRAG0501). -
Does it have the
[Endpoint]attribute? The SG only processes classes decorated with[Endpoint(HttpVerb.Get, "/route")]. A missing attribute means no code generation. -
Did you call
AddPragmaticEndpoints()in Program.cs?builder.Services.AddPragmaticEndpoints(); -
Did you call
MapPragmaticEndpoints()after building the app?var app = builder.Build();app.MapPragmaticEndpoints(); -
Is the SG analyzer referenced correctly? In your
.csproj, the Pragmatic.SourceGenerator must be referenced withOutputItemType="Analyzer":<ProjectReference Include="..\Pragmatic.SourceGenerator\Pragmatic.SourceGenerator.csproj"OutputItemType="Analyzer"ReferenceOutputAssembly="false" />
If Using Groups
Section titled “If Using Groups”-
Does the
[EndpointGroup]static class exist and is it accessible from the endpoint’s assembly? -
Is the
Grouptype reference correct? It must point to a static class decorated with[EndpointGroup]:[Endpoint(HttpVerb.Get, "/", Group = typeof(OrdersGroup))] -
If the group class is in a different namespace, verify it is imported. Diagnostic PRAG0507 fires when the referenced group cannot be found.
Route Gives 404 But Endpoint Exists
Section titled “Route Gives 404 But Endpoint Exists”The endpoint registers (no build errors, no diagnostics), but the specific URL returns 404.
Possible Causes
Section titled “Possible Causes”Route prefix stacking. The final route is composed from three layers:
| Layer | Source | Example |
|---|---|---|
| Global prefix | PragmaticEndpointsOptions.RoutePrefix | /api |
| Group prefix | [EndpointGroup("/v1/orders")] | /v1/orders |
| Endpoint route | [Endpoint(HttpVerb.Get, "/{id}")] | /{id} |
| Final | /api/v1/orders/{id} |
If you are requesting /v1/orders/123 but the global prefix is /api, the actual route is /api/v1/orders/123.
Missing leading slash. Routes should start with /. While the SG normalizes this, inconsistent slashes can cause confusion when debugging.
Route parameter constraint mismatch. If your route uses {id:guid} but you pass an integer, ASP.NET Core will not match the route. Verify the constraint matches the actual parameter type:
// Route says guid, but caller sends an integer -> 404[Endpoint(HttpVerb.Get, "/orders/{id:guid}")]HTTP verb mismatch. Sending a POST to a GET endpoint returns 404 (or 405 Method Not Allowed). Verify the verb in the attribute matches the request.
Pre-Processor Not Running
Section titled “Pre-Processor Not Running”The [PreProcessor<T>] attribute is present, but the processor logic never executes.
Checklist
Section titled “Checklist”-
Check the
Orderproperty. Lower values run first. If your processor depends on another processor’s side effects, verify the ordering:[PreProcessor<AuthCheckProcessor>(Order = 0)][PreProcessor<ValidateGuestProcessor>(Order = 1)] -
Check DI registration. The processor must be registered as scoped in the DI container:
builder.Services.AddScoped<ValidateGuestProcessor>();Alternatively, decorate the processor with
[Service(Lifetime = ServiceLifetime.Scoped)]for auto-registration. -
Verify the interface. The processor must implement
IEndpointPreProcessororIEndpointPreProcessor<TEndpoint>. If neither is implemented, diagnostic PRAG0508 is emitted. -
Check for short-circuiting. A preceding pre-processor may be returning
PreProcessorResult.Fail(...), which stops the pipeline before your processor runs.
Rate Limiting Not Working
Section titled “Rate Limiting Not Working”The [RateLimit] attribute is on the endpoint, but requests are never throttled.
Checklist
Section titled “Checklist”-
Did you add the rate limiting middleware?
app.UseRateLimiter(); // Required -- must be before MapPragmaticEndpoints()app.MapPragmaticEndpoints();Without
UseRateLimiter(), the rate limiting metadata on endpoints has no effect. -
Is the policy name matching? Inline
[RateLimit(Requests = 5, Window = "1m")]generates a policy named__pragmatic_ratelimit_{TypeName}. If you use[RateLimit(Policy = "standard")], verify the policy is registered:services.AddPragmaticEndpoints(options =>{options.ConfigureRateLimiter("standard", limiter =>{limiter.PermitLimit = 100;limiter.Window = TimeSpan.FromMinutes(1);});}); -
For distributed rate limiting: Is
ICacheStackregistered viaPragmatic.Caching? In-memory rate limiters are per-instance, so multiple app instances each maintain separate counters. -
Invalid configuration. Diagnostic PRAG0506 fires when
[RateLimit]has neitherPolicynor bothRequestsandWindowspecified.
File Upload Returning 413 or 415
Section titled “File Upload Returning 413 or 415”413 Payload Too Large
Section titled “413 Payload Too Large”The uploaded file exceeds the size limit. Check in this order:
-
[MaxFileSize]on the property. The SG validates the file size beforeHandleAsync. -
MaxUploadFileSizeinPragmaticEndpointsOptions. Applied toIFormFileproperties without an explicit[MaxFileSize]. -
Kestrel limit. ASP.NET Core’s default request body size is ~28.6 MB. For larger uploads:
builder.WebHost.ConfigureKestrel(options =>{options.Limits.MaxRequestBodySize = 100 * 1024 * 1024; // 100 MB});
415 Unsupported Media Type
Section titled “415 Unsupported Media Type”The file’s MIME type is not in the allowed list. Check:
[AllowedContentTypes]on the property. Only the listed types are accepted.DefaultAllowedContentTypesinPragmaticEndpointsOptions. Applied when no explicit attribute is present.- Client-reported content type. The
Content-Typeheader is set by the client. Verify the client sends the correct type. Note:[AllowedContentTypes]checks the header, not the file bytes.
Validation Not Running
Section titled “Validation Not Running”The endpoint or action has validation attributes ([Required], [Email], [MinLength]), but invalid input is not rejected.
Checklist
Section titled “Checklist”-
Is
Pragmatic.Validationreferenced? The validation SG generatesISyncValidator<T>implementations only when the package is present in the project. -
Are validation attributes on the correct properties? Attributes must be on the endpoint’s public properties, not on nested DTO types.
-
For DomainAction endpoints: Validation runs automatically before
Execute()when the SG detects a validator. If validation is not triggering, check that the generated validator exists in the SG output. -
For plain Endpoint
: Ensure the generated handler calls the validator. The SG integrates validation when it detectsISyncValidatororIAsyncValidatorfor the endpoint type.
Authorization Returns 401 on All Requests
Section titled “Authorization Returns 401 on All Requests”Every request fails with 401 Unauthorized, including endpoints that should be public.
Checklist
Section titled “Checklist”-
Is
RequireAuthorizationByDefaultenabled? When set totrue(globally or on a group), all endpoints require authentication. Exempt specific endpoints with[AllowAnonymous]. -
Is the auth middleware configured?
app.UseAuthentication();app.UseAuthorization();app.MapPragmaticEndpoints();Both must be in the pipeline and in the correct order (authentication before authorization).
-
Is the authentication scheme configured? ASP.NET Core needs at least one authentication scheme registered:
builder.Services.AddAuthentication("Bearer").AddJwtBearer(options => { /* ... */ }); -
Group-level authorization. Check if the endpoint’s group has
RequireAuthorization = trueinConfigureGroup:options.ConfigureGroup("Orders", group =>{group.RequireAuthorization = true; // All endpoints in this group require auth});
Generated Code Not Compiling
Section titled “Generated Code Not Compiling”The build fails with PRAG05xx diagnostics from the source generator.
Diagnostics Reference
Section titled “Diagnostics Reference”| ID | Severity | Cause | Fix |
|---|---|---|---|
| PRAG0500 | Error | Class is not partial | Add partial keyword to the class declaration |
| PRAG0501 | Error | Missing base class | Inherit from Endpoint<T>, VoidEndpoint, DomainAction<T>, VoidDomainAction, or Mutation<T> |
| PRAG0502 | Error | Route not specified | Add a route pattern to the [Endpoint] attribute: [Endpoint(HttpVerb.Post, "/orders")] |
| PRAG0503 | Error | More than 6 error types | Reduce the generic error type parameters to 6 or fewer |
| PRAG0504 | Warning | Route parameter has no matching property | Add a public property matching the route parameter name, or fix the spelling |
| PRAG0505 | Error | Duplicate endpoint name | Rename one of the conflicting endpoint classes |
| PRAG0506 | Error | Invalid rate limit config | Specify either Policy or both Requests and Window on [RateLimit] |
| PRAG0507 | Error | Group type not found | Verify the [EndpointGroup] class exists and is accessible |
| PRAG0508 | Error | Processor missing interface | Implement IEndpointPreProcessor or IEndpointPostProcessor |
| PRAG0512 | Info | Implicit body binding | Add [FromBody], [FromQuery], or [FromRoute] for clarity |
| PRAG0515 | Error | Autocomplete entity missing key | Add an Id property or [Key] attribute to the entity |
| PRAG0550 | Error | [Autocomplete] on non-string | Move [Autocomplete] to a string property |
| PRAG0551 | Warning | Versioned methods need Asp.Versioning.Http | Add the Asp.Versioning.Http NuGet package |
Check the Error List window in Visual Studio or the build output for diagnostic details and the affected source location.
Body DTO Not Binding
Section titled “Body DTO Not Binding”The request body is sent as JSON, but properties on the endpoint are null or default.
Checklist
Section titled “Checklist”-
Properties must be public with
{ get; init; }or{ get; set; }. Private or internal properties are not bound from the request body. -
Use the
requiredkeyword for mandatory properties. This ensures the model binder rejects requests with missing fields:public required string Name { get; init; } -
JSON property names are camelCase by default. A property named
CustomerIdexpects"customerId"in the JSON body. Verify the casing matches. -
Check the
Content-Typeheader. The request must includeContent-Type: application/json. Without it, ASP.NET Core will not attempt JSON deserialization. -
Binding source conflicts. If a property has
[FromRoute],[FromQuery], or[FromHeader], it is not bound from the body. Properties without explicit binding attributes are auto-detected as body properties. -
Form vs. JSON conflict. When
[FromForm]properties exist on the endpoint, no body DTO is generated. Form data and JSON body are mutually exclusive.
Endpoint Returns 500 Unexpectedly
Section titled “Endpoint Returns 500 Unexpectedly”The endpoint should return a business error but returns 500 Internal Server Error.
Common Causes
Section titled “Common Causes”-
Unhandled exception in
HandleAsyncorExecute. The Result pattern only works when you return error objects, not when exceptions are thrown. Wrap risky operations:// Instead of letting exceptions propagate:var invoice = await _invoices.GetByIdAsync(Id, ct);if (invoice is null)return new NotFoundError("Invoice", Id); -
DI resolution failure. A dependency field on the endpoint is
null!at runtime because the service is not registered. Check that all private fields have corresponding DI registrations. -
Missing middleware. Add
app.UseExceptionHandler()to convert unhandled exceptions into ProblemDetails responses instead of raw 500s.
OpenAPI Schema Missing or Incomplete
Section titled “OpenAPI Schema Missing or Incomplete”Swagger UI shows the endpoint but without request/response schemas.
Checklist
Section titled “Checklist”- Add
[EndpointSummary]and[EndpointDescription]for human-readable documentation. - Add
[Tags("Orders")]to group endpoints in the Swagger UI. - Use
[HttpStatus(201)]to override the default 200 success status code. - Add XML doc comments on public properties for property-level documentation in the generated body DTO.
- Multiple error types declared in the generic parameters (
DomainAction<T, NotFoundError, ValidationError>) automatically produce response schema entries for each error’s status code.
Can I use both [FromForm] and body properties on the same endpoint?
Section titled “Can I use both [FromForm] and body properties on the same endpoint?”No. When [FromForm] properties are present, the SG does not generate a [FromBody] DTO. Form data and JSON body binding are mutually exclusive.
How do I change the success HTTP status code?
Section titled “How do I change the success HTTP status code?”Use [HttpStatus(201)] on the endpoint class. The default is 200 for Endpoint<T> and 204 for VoidEndpoint.
Why does my route parameter property stay at default value?
Section titled “Why does my route parameter property stay at default value?”Verify three things: (a) the property name matches the route parameter name (case-insensitive), (b) the property has [FromRoute], and (c) the type is compatible with the route constraint (e.g., Guid for {id:guid}).
Can I override group-level settings on a single endpoint?
Section titled “Can I override group-level settings on a single endpoint?”Yes. Endpoint-level attributes take priority over group-level configuration. For example, an [AllowAnonymous] attribute on an endpoint overrides the group’s RequireAuthorization = true.
How do I debug what routes were registered?
Section titled “How do I debug what routes were registered?”Inspect the generated MapPragmaticEndpoints() method in the SG output. In Visual Studio, expand Dependencies > Analyzers > Pragmatic.SourceGenerator in Solution Explorer to see all generated files. Look for the endpoint registration file.
Getting Help
Section titled “Getting Help”- GitHub Issues: github.com/nicola-pragmatic/Pragmatic.Design/issues
- Showcase Examples: See the
Showcaseproject for working endpoint implementations across all base class types. - Binding Reference: See binding-reference.md for all binding scenarios.
- Error Handling: See error-handling.md for HTTP status mapping and custom errors.
- Endpoint Groups: See endpoint-groups.md for route prefix composition and group configuration.