Skip to content

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.

  1. Is the class partial? The SG cannot generate the handler without partial. You will see diagnostic PRAG0500 if this is missing.

  2. Does it inherit from a valid base class? The class must inherit from Endpoint<T>, VoidEndpoint, DomainAction<T>, VoidDomainAction, or Mutation<T>. Without this, the SG skips it entirely (PRAG0501).

  3. Does it have the [Endpoint] attribute? The SG only processes classes decorated with [Endpoint(HttpVerb.Get, "/route")]. A missing attribute means no code generation.

  4. Did you call AddPragmaticEndpoints() in Program.cs?

    builder.Services.AddPragmaticEndpoints();
  5. Did you call MapPragmaticEndpoints() after building the app?

    var app = builder.Build();
    app.MapPragmaticEndpoints();
  6. Is the SG analyzer referenced correctly? In your .csproj, the Pragmatic.SourceGenerator must be referenced with OutputItemType="Analyzer":

    <ProjectReference Include="..\Pragmatic.SourceGenerator\Pragmatic.SourceGenerator.csproj"
    OutputItemType="Analyzer"
    ReferenceOutputAssembly="false" />
  • Does the [EndpointGroup] static class exist and is it accessible from the endpoint’s assembly?

  • Is the Group type 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.


The endpoint registers (no build errors, no diagnostics), but the specific URL returns 404.

Route prefix stacking. The final route is composed from three layers:

LayerSourceExample
Global prefixPragmaticEndpointsOptions.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.


The [PreProcessor<T>] attribute is present, but the processor logic never executes.

  1. Check the Order property. 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)]
  2. 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.

  3. Verify the interface. The processor must implement IEndpointPreProcessor or IEndpointPreProcessor<TEndpoint>. If neither is implemented, diagnostic PRAG0508 is emitted.

  4. Check for short-circuiting. A preceding pre-processor may be returning PreProcessorResult.Fail(...), which stops the pipeline before your processor runs.


The [RateLimit] attribute is on the endpoint, but requests are never throttled.

  1. 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.

  2. 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);
    });
    });
  3. For distributed rate limiting: Is ICacheStack registered via Pragmatic.Caching? In-memory rate limiters are per-instance, so multiple app instances each maintain separate counters.

  4. Invalid configuration. Diagnostic PRAG0506 fires when [RateLimit] has neither Policy nor both Requests and Window specified.


The uploaded file exceeds the size limit. Check in this order:

  1. [MaxFileSize] on the property. The SG validates the file size before HandleAsync.

  2. MaxUploadFileSize in PragmaticEndpointsOptions. Applied to IFormFile properties without an explicit [MaxFileSize].

  3. 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
    });

The file’s MIME type is not in the allowed list. Check:

  1. [AllowedContentTypes] on the property. Only the listed types are accepted.
  2. DefaultAllowedContentTypes in PragmaticEndpointsOptions. Applied when no explicit attribute is present.
  3. Client-reported content type. The Content-Type header is set by the client. Verify the client sends the correct type. Note: [AllowedContentTypes] checks the header, not the file bytes.

The endpoint or action has validation attributes ([Required], [Email], [MinLength]), but invalid input is not rejected.

  1. Is Pragmatic.Validation referenced? The validation SG generates ISyncValidator<T> implementations only when the package is present in the project.

  2. Are validation attributes on the correct properties? Attributes must be on the endpoint’s public properties, not on nested DTO types.

  3. 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.

  4. For plain Endpoint: Ensure the generated handler calls the validator. The SG integrates validation when it detects ISyncValidator or IAsyncValidator for the endpoint type.


Every request fails with 401 Unauthorized, including endpoints that should be public.

  1. Is RequireAuthorizationByDefault enabled? When set to true (globally or on a group), all endpoints require authentication. Exempt specific endpoints with [AllowAnonymous].

  2. 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).

  3. Is the authentication scheme configured? ASP.NET Core needs at least one authentication scheme registered:

    builder.Services.AddAuthentication("Bearer")
    .AddJwtBearer(options => { /* ... */ });
  4. Group-level authorization. Check if the endpoint’s group has RequireAuthorization = true in ConfigureGroup:

    options.ConfigureGroup("Orders", group =>
    {
    group.RequireAuthorization = true; // All endpoints in this group require auth
    });

The build fails with PRAG05xx diagnostics from the source generator.

IDSeverityCauseFix
PRAG0500ErrorClass is not partialAdd partial keyword to the class declaration
PRAG0501ErrorMissing base classInherit from Endpoint<T>, VoidEndpoint, DomainAction<T>, VoidDomainAction, or Mutation<T>
PRAG0502ErrorRoute not specifiedAdd a route pattern to the [Endpoint] attribute: [Endpoint(HttpVerb.Post, "/orders")]
PRAG0503ErrorMore than 6 error typesReduce the generic error type parameters to 6 or fewer
PRAG0504WarningRoute parameter has no matching propertyAdd a public property matching the route parameter name, or fix the spelling
PRAG0505ErrorDuplicate endpoint nameRename one of the conflicting endpoint classes
PRAG0506ErrorInvalid rate limit configSpecify either Policy or both Requests and Window on [RateLimit]
PRAG0507ErrorGroup type not foundVerify the [EndpointGroup] class exists and is accessible
PRAG0508ErrorProcessor missing interfaceImplement IEndpointPreProcessor or IEndpointPostProcessor
PRAG0512InfoImplicit body bindingAdd [FromBody], [FromQuery], or [FromRoute] for clarity
PRAG0515ErrorAutocomplete entity missing keyAdd an Id property or [Key] attribute to the entity
PRAG0550Error[Autocomplete] on non-stringMove [Autocomplete] to a string property
PRAG0551WarningVersioned methods need Asp.Versioning.HttpAdd 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.


The request body is sent as JSON, but properties on the endpoint are null or default.

  1. Properties must be public with { get; init; } or { get; set; }. Private or internal properties are not bound from the request body.

  2. Use the required keyword for mandatory properties. This ensures the model binder rejects requests with missing fields:

    public required string Name { get; init; }
  3. JSON property names are camelCase by default. A property named CustomerId expects "customerId" in the JSON body. Verify the casing matches.

  4. Check the Content-Type header. The request must include Content-Type: application/json. Without it, ASP.NET Core will not attempt JSON deserialization.

  5. 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.

  6. Form vs. JSON conflict. When [FromForm] properties exist on the endpoint, no body DTO is generated. Form data and JSON body are mutually exclusive.


The endpoint should return a business error but returns 500 Internal Server Error.

  1. Unhandled exception in HandleAsync or Execute. 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);
  2. 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.

  3. Missing middleware. Add app.UseExceptionHandler() to convert unhandled exceptions into ProblemDetails responses instead of raw 500s.


Swagger UI shows the endpoint but without request/response schemas.

  1. Add [EndpointSummary] and [EndpointDescription] for human-readable documentation.
  2. Add [Tags("Orders")] to group endpoints in the Swagger UI.
  3. Use [HttpStatus(201)] to override the default 200 success status code.
  4. Add XML doc comments on public properties for property-level documentation in the generated body DTO.
  5. 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.