Skip to main content

Troubleshooting & FAQ

Common Issues​

After upgrading, I get "The type or namespace name 'SuccessResponse' does not exist"​

Cause: In v10.0.6, the static helper class SuccessResponse was renamed to SuccessResult to avoid collision with the generic response model SuccessResponse<T>.

Fix: Update your using directives and method calls:

// ❌ v10.0.5 and earlier
using RA.Utilities.Api.Mapper;
return SuccessResponse.Ok(product);

// ✅ v10.0.6
using RA.Utilities.Api.Mapper;
return SuccessResult.Ok(product);

I removed the RA.Utilities.Api.Results NuGet package and now my code won't compile​

Cause: The response models were consolidated into RA.Utilities.Api in v10.0.6 and later revived as the standalone RA.Utilities.Api.Response package. RA.Utilities.Api depends on it, so the types arrive transitively — but a project that relied on the old RA.Utilities.Api.Results package id may have dropped the reference entirely.

Fix: Add the revived package, or ensure RA.Utilities.Api (v10.2.2+) is referenced:

# Add the standalone response models package
dotnet add package RA.Utilities.Api.Response

# Alternatively, the models arrive transitively via the Api package
dotnet add package RA.Utilities.Api --version 10.2.2

No package changes are needed, but the namespace changed in RA.Utilities.Api.Response v10.1.0 and arrives transitively with RA.Utilities.Api 10.2.2+: replace using RA.Utilities.Api.Results; with using RA.Utilities.Api.Response;.

After upgrading, MiddlewareExtensions no longer compiles​

Cause: In v10.2.2 the monolithic MiddlewareExtensions class was split back into two classes, one per target type: ApplicationBuilderExtensions (the Use* methods) and ServiceCollectionExtensions (the Add* methods).

Fix: The extension methods are resolved by their target type (IServiceCollection, IApplicationBuilder), so call sites need no changes — but any code that references the class name directly must be updated:

// ❌ No longer exists (v10.2.1 and earlier)
var t = typeof(MiddlewareExtensions);

// ✅ v10.2.2
var t = typeof(ServiceCollectionExtensions); // Add* methods
var u = typeof(ApplicationBuilderExtensions); // Use* methods

Typical places that reference the type name: typeof(...) assertions in unit tests, reflection-based DI checks, and InternalsVisibleTo-style helpers. Plain using RA.Utilities.Api.Extensions; directives keep working.

UseRequestContextLoggingMiddleware() no longer compiles​

Cause: The method was renamed to UseContextLoggingMiddleware() in v10.2.1. The dependency-injection method (AddRequestContextLoggingMiddleware()) kept its name.

Fix: Rename the pipeline call:

// ❌ v10.2.0 and earlier
app.UseRequestContextLoggingMiddleware();

// ✅ v10.2.1+
app.UseContextLoggingMiddleware();

Validate<T>() is in EndpointBuilderExtensions — where did it go?​

Cause: Validate<T>() was moved from EndpointBuilderExtensions into ValidationExtensions. The class was renamed to better reflect its purpose (validation, not endpoint building).

Fix: No code changes required — the method is still an extension on RouteHandlerBuilder in the same namespace (RA.Utilities.Api.Extensions). The class name change is transparent to consuming code. Just ensure your project is up to date.

GlobalExceptionHandler is not catching my exceptions​

Cause: Either the handler wasn't registered, the middleware is ordered incorrectly, or an earlier middleware swallowed the exception.

Fix: Ensure both registration calls are present and the exception handler is placed early in the pipeline:

var builder = WebApplication.CreateBuilder(args);

// ✅ Must be called to register the handler with DI
builder.Services.AddRaExceptionHandling();

var app = builder.Build();

// ✅ Must be called early — before other middleware that might swallow exceptions
app.UseRaExceptionHandling();

// Other middleware and endpoint mappings go here...
app.MapEndpoints();

app.Run();

MapEndpoints() is not discovering my endpoint classes​

Cause: The compile-time source generator isn't finding your IEndpoint/IEndpointGroup implementations. This usually happens when:

  • The class doesn't implement the interface directly — types that merely inherit the static members from a base class are not registered (only the base class is)
  • The class or one of its containing types is private or file-local, so the generated code cannot reference it (EPMG004)
  • The class is generic (EPMG003)
  • The static members are implemented explicitly instead of as public static members (EPMG005)
  • The endpoint class lives in a referenced class library — the generator only discovers types declared in the project's own assembly

Fix: Make every IEndpointGroup/IEndpoint implementation a non-generic, internal/public type in the same assembly, implementing the interface directly, with GroupName and MapGroup/MapEndpoint declared as public static members. A rebuild regenerates the registration — check the EPMG* diagnostics for the exact offender.

My build fails with EPMG001 or EPMG002​

Cause: The generator validates group names at compile time whenever they are compile-time constants:

  • EPMG001 — two IEndpointGroup implementations declare the same GroupName
  • EPMG002 — an IEndpoint references a GroupName that no group declares

Fix: Rename one of the duplicate group names, or correct the endpoint's GroupName so it matches an existing group. Names computed at runtime (e.g. typeof(X).Name) cannot be validated at compile time — a mismatch among those throws an InvalidOperationException at startup naming the endpoint type and the missing group key.

Validate<T>() doesn't reject invalid requests​

Cause: No IValidator<T> is registered in DI. ValidationEndpointFilter<T> resolves validators from DI; when none are registered, it passes the request through without validating.

Fix: Register your validators with the DI container:

// Register all validators in the assembly
builder.Services.AddValidatorsFromAssembly(typeof(Program).Assembly);

My error response always returns 500 even though I'm throwing a specific exception​

Cause: The GlobalExceptionHandler only knows about exceptions that inherit from RaBaseException. If you're throwing a standard .NET exception, it will return a generic 500.

Fix: Use the semantic exception types from RA.Utilities.Core.Exceptions:

// ❌ Returns generic 500
throw new Exception("Product not found");

// ✅ Returns structured 404
throw new NotFoundException(nameof(Product), productId);

DefaultHeadersMiddleware only checks x-request-id — how do I require other headers?​

Cause: The middleware uses DefaultHeadersOptions to determine which headers are required. By default, only x-request-id is configured (with auto-generation enabled).

Fix: Add additional required headers via the options:

builder.Services.Configure<DefaultHeadersOptions>(options =>
{
// Add a header that must be provided by the caller (400 if missing)
options.RequiredHeaders.Add(new RequiredHeaderDefinition
{
Name = "x-api-key",
ErrorMessage = "API key is required for this endpoint.",
});

// Add a header that auto-generates if missing
options.RequiredHeaders.Add(new RequiredHeaderDefinition
{
Name = "x-correlation-id",
AutoGenerate = true,
EchoInResponse = true,
});
});

To disable the default x-request-id check entirely, clear the collection and add your own:

builder.Services.Configure<DefaultHeadersOptions>(options =>
{
options.RequiredHeaders.Clear();
// Add only the headers you need...
});

DefaultHeadersMiddleware returns 400 for ALL my endpoints — how do I exclude paths?​

Cause: The PathsToIgnore set is empty by default, so all paths are checked.

Fix: Add path prefixes to exclude from header enforcement:

builder.Services.Configure<DefaultHeadersOptions>(options =>
{
options.PathsToIgnore.Add("/health");
options.PathsToIgnore.Add("/metrics");
options.PathsToIgnore.Add("/swagger");
});

Comparisons are case-insensitive and match by prefix — /health also matches /health/detailed.

LoggingMiddleware is not logging anything​

Cause: Either the middleware isn't registered in both DI and the pipeline, or the log level is too high.

Fix: Ensure both Add and Use calls are present:

// Register services
builder.Services.AddLoggingMiddleware();

var app = builder.Build();

// Add to pipeline (early, to capture all requests)
app.UseLoggingMiddleware();

Also check your configured log level — the request log is written at Information and the response log is written at Warning; both are skipped entirely when Information is not enabled. When the request exceeds WarningThresholdMilliseconds, an additional Warning entry is written:

builder.Services.AddLoggingMiddleware(options =>
{
options.WarningThresholdMilliseconds = 500; // Extra Warning entry for requests slower than 500 ms
});

I get a NU1903 vulnerability warning for Microsoft.OpenApi​

Cause: Older RA.Utilities.OpenApi releases depended on Microsoft.OpenApi 2.0.0, which has a known vulnerability.

Fix: Upgrade RA.Utilities.OpenApi to a version that pins Microsoft.OpenApi 2.12.2 or later. Current releases already do this; upgrade rather than suppressing the warning.

Why is the nested NotFoundResult payload wrong?​

Cause: ErrorResultMapper.ToResponse(NotFoundException) passes exception.Message as the third positional argument of NotFoundResult. That parameter is errorCode, not the message. As a result, the nested errorCode contains the message text and errorMessage is empty.

Current behavior:

{
"responseCode": 404,
"responseType": 8,
"responseMessage": "Product with value '99' was not found.",
"result": {
"entity": "Product",
"value": 99,
"errorCode": "Product with value '99' was not found.",
"errorMessage": ""
}
}

The envelope is correct. A source fix is pending; until then, do not rely on the nested errorCode for NotFoundException responses.

Why is result null for some error responses?​

Cause: ErrorResultMapper maps Unauthorized, PaymentRequired, Forbidden, Unprocessable, TooManyRequests, ServiceUnavailable, GatewayTimeout, and generic RaBaseException through the (int responseCode, string responseMessage) constructors. Those constructors set Result = null, so the error code and message exist only on the envelope.

Only BadRequestException, ConflictException, and NotFoundException produce a structured Result payload. See ErrorResultMapper.

Why are DefaultHeadersMiddleware error fields PascalCase?​

Cause: DefaultHeadersMiddleware writes its 400 body with JsonSerializer.Serialize(...) and default options, not through MVC/Minimal API JSON options. The body therefore uses PascalCase property names (ResponseCode, Result, ...) and the numeric ResponseType.

Fix: Parse this endpoint's error body with case-insensitive property matching, or normalize the casing in a custom middleware. This is a known inconsistency with the camelCase output of normal endpoints.


FAQ​

What's the difference between throw and return with exceptions?​

Both approaches work depending on your error handling style:

  • throw: Best when using GlobalExceptionHandler middleware. The exception propagates up and the handler catches it, logs it, and returns a structured response.

  • return with Result<T>: Best when using the functional Result pattern from RA.Utilities.Core.Results. Use .Match() to map success/failure to IResult:

return result.Match<IResult>(
success: product => SuccessResult.Ok(product),
failure: ErrorResultResponse.Result
);

Where can I find the response model types?​

All response model types are provided by the RA.Utilities.Api.Response package (referenced transitively by RA.Utilities.Api):

  • SuccessResponse<T> — HTTP 200
  • BadRequestResponse — HTTP 400
  • UnauthorizedResponse — HTTP 401
  • PaymentRequiredResponse — HTTP 402
  • ForbiddenResponse — HTTP 403
  • NotFoundResponse — HTTP 404
  • ConflictResponse — HTTP 409
  • UnprocessableResponse — HTTP 422
  • TooManyRequestsResponse — HTTP 429
  • ErrorResponse — HTTP 500
  • ServiceUnavailableResponse — HTTP 503
  • GatewayTimeoutResponse — HTTP 504

How do I add custom exception-to-response mappings?​

Extend the ErrorResultResponse.Result method or the GlobalExceptionHandler. For custom exceptions that inherit from RaBaseException, the existing switch expression maps them to ErrorResponse (500) by default. You can add a new case for your custom exception type.

Can I use RA.Utilities.Api without the GlobalExceptionHandler?​

Yes. Every feature is opt-in. You can use IEndpointGroup/IEndpoint + MapEndpoints for endpoint organization, SuccessResult for consistent responses, and Validate<T>() for request validation — all independently of the exception handler.

Does the exception handler work with controller-based APIs?​

Yes. GlobalExceptionHandler implements IExceptionHandler (introduced in .NET 8) which works with both Minimal APIs and controller-based APIs. Call AddRaExceptionHandling() and UseRaExceptionHandling() as shown in the usage guide.

How do I add a DefaultHeadersMiddleware to the pipeline?​

Use the AddDefaultHeadersMiddleware / UseDefaultHeadersMiddleware extension methods in RA.Utilities.Api.Extensions:

using RA.Utilities.Api.Extensions;

builder.Services.AddDefaultHeadersMiddleware(options =>
{
options.RequiredHeaders.Add(new RequiredHeaderDefinition
{
Name = "x-api-key",
ErrorMessage = "API key is required.",
});
});

var app = builder.Build();
app.UseDefaultHeadersMiddleware();

How do I configure which request bodies the HTTP logging middleware captures?​

Use HttpLoggingOptions:

builder.Services.AddLoggingMiddleware(options =>
{
options.MaxBodyLogLength = 8192; // Truncate bodies larger than this
options.PathsToIgnore.Add("/health"); // Skip health check endpoints
options.ExcludedHeaders.Add("Authorization"); // Redact sensitive headers
options.WarningThresholdMilliseconds = 500; // Extra Warning entry for requests slower than 500 ms
});

Known Limitations​

  • No localization support: Error messages are English-only. Use ErrorCode values for client-side lookup tables if you need multi-language support.
  • Minimal API focused: The IEndpoint registration pattern and Validate<T>() filter are designed for Minimal APIs. Controller-based apps can still use the exception handler and SuccessResult helpers.
  • Validation requires a bound argument: ValidationEndpointFilter<TModel> validates the first non-null TModel argument of the endpoint delegate. If the model isn't a bound parameter (for example, you deserialize the body manually), the filter passes through without validating. It never reads the request body itself, so it doesn't interfere with body-reading middlewares.
  • Discovery is per assembly: The source generator only discovers IEndpointGroup/IEndpoint types declared in the project's own assembly. Endpoint types in referenced class libraries are not mapped by the executable's MapEndpoints() call; each library would need the package and its own registration entry point.
  • DefaultHeadersMiddleware defaults to requiring x-request-id only: Additional required headers must be configured via DefaultHeadersOptions.RequiredHeaders. The default auto-generates x-request-id if missing.
  • DefaultHeadersMiddleware error body casing: its 400 payload uses PascalCase and numeric ResponseType values, unlike the camelCase responses produced by Minimal API/controller JSON options.
  • Mapper payloads are inconsistent: only BadRequestException, ConflictException, and NotFoundException produce a structured Result; the other mapped exception types return result: null with the details on the envelope. NotFoundException's nested errorCode/errorMessage are currently reversed/empty.
  • LoggingMiddleware buffers full request/response bodies: Large payloads will be fully read into memory. Use MaxBodyLogLength to truncate large bodies and PathsToIgnore to skip specific endpoints.
  • TooManyRequestsException log level: GlobalExceptionHandler treats BadRequestException, UnauthorizedException, PaymentRequiredException, ForbiddenException, NotFoundException, ConflictException, UnprocessableException, and TooManyRequestsException as client errors and logs them at Warning. ServiceUnavailableException, GatewayTimeoutException, and other RaBaseException types are logged at Error.