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
privateorfile-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— twoIEndpointGroupimplementations declare the sameGroupNameEPMG002— anIEndpointreferences aGroupNamethat 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 usingGlobalExceptionHandlermiddleware. The exception propagates up and the handler catches it, logs it, and returns a structured response. -
returnwithResult<T>: Best when using the functionalResultpattern fromRA.Utilities.Core.Results. Use.Match()to map success/failure toIResult:
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 200BadRequestResponse— HTTP 400UnauthorizedResponse— HTTP 401PaymentRequiredResponse— HTTP 402ForbiddenResponse— HTTP 403NotFoundResponse— HTTP 404ConflictResponse— HTTP 409UnprocessableResponse— HTTP 422TooManyRequestsResponse— HTTP 429ErrorResponse— HTTP 500ServiceUnavailableResponse— HTTP 503GatewayTimeoutResponse— 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
ErrorCodevalues for client-side lookup tables if you need multi-language support. - Minimal API focused: The
IEndpointregistration pattern andValidate<T>()filter are designed for Minimal APIs. Controller-based apps can still use the exception handler andSuccessResulthelpers. - Validation requires a bound argument:
ValidationEndpointFilter<TModel>validates the first non-nullTModelargument 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/IEndpointtypes declared in the project's own assembly. Endpoint types in referenced class libraries are not mapped by the executable'sMapEndpoints()call; each library would need the package and its own registration entry point. DefaultHeadersMiddlewaredefaults to requiringx-request-idonly: Additional required headers must be configured viaDefaultHeadersOptions.RequiredHeaders. The default auto-generatesx-request-idif missing.DefaultHeadersMiddlewareerror body casing: its 400 payload uses PascalCase and numericResponseTypevalues, unlike the camelCase responses produced by Minimal API/controller JSON options.- Mapper payloads are inconsistent: only
BadRequestException,ConflictException, andNotFoundExceptionproduce a structuredResult; the other mapped exception types returnresult: nullwith the details on the envelope.NotFoundException's nestederrorCode/errorMessageare currently reversed/empty. LoggingMiddlewarebuffers full request/response bodies: Large payloads will be fully read into memory. UseMaxBodyLogLengthto truncate large bodies andPathsToIgnoreto skip specific endpoints.TooManyRequestsExceptionlog level:GlobalExceptionHandlertreatsBadRequestException,UnauthorizedException,PaymentRequiredException,ForbiddenException,NotFoundException,ConflictException,UnprocessableException, andTooManyRequestsExceptionas client errors and logs them atWarning.ServiceUnavailableException,GatewayTimeoutException, and otherRaBaseExceptiontypes are logged atError.