Troubleshooting & FAQ
Common Issues
RA.Utilities.Api.Results no longer exists after upgrading
Cause: v10.1.0 renamed the namespace to RA.Utilities.Api.Response to match the package id. The old RA.Utilities.Api.Results package is deprecated and is not involved.
Fix: Replace the using directive and any fully qualified names:
// Before
using RA.Utilities.Api.Results;
// After
using RA.Utilities.Api.Response;
If you consume RA.Utilities.Api or RA.Utilities.OpenApi, update them together — their public APIs expose these types.
responseType is a number instead of a string
Cause: ResponseType is a plain enum in RA.Utilities.Core.Constants v10.0.4+. System.Text.Json writes enum values as numbers by default (ResponseType.NotFound → 8).
Fix: Register JsonStringEnumConverter on the serializer used by your endpoints:
using System.Text.Json.Serialization;
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.Converters.Add(new JsonStringEnumConverter());
});
For MVC controllers, use AddControllers().AddJsonOptions(...) instead. See ResponseType.
ResponseMessage is empty when I pass a custom code
Cause: The all-argument overloads declare string responseMessage = "". The ?? fallback only applies to null, so an omitted message stays empty:
// ResponseMessage == "" — the standard message is NOT applied
new NotFoundResponse(model, 404)
Fix: Pass the standard message explicitly, or use the convenience constructor:
// Pass the message
var withMessage = new NotFoundResponse(model, 404, BaseResponseMessages.NotFound);
// Or keep the standard code and message
var standard = new NotFoundResponse(model);
NotFoundResult.ErrorMessage or ConflictResult.ErrorMessage is empty
Cause: NotFoundResult and ConflictResult default their nested error message to "". The convenience response constructor only sets the envelope message.
Fix: Set the nested value explicitly:
var notFound = new NotFoundResult("Product", id)
{
ErrorMessage = $"Product with id {id} was not found."
};
return NotFound(new NotFoundResponse(notFound));
For ConflictResult, pass errorMessage or set the property the same way.
new SuccessResponse<string>("value") puts the value in the message
Cause: SuccessResponse<T> has a dedicated SuccessResponse(string responseMessage) constructor. When T is string, a single string argument selects that overload instead of the generic T overload.
Fix: Pass the second argument (null for the default code) so the generic overload is selected:
// ⚠️ ResponseMessage = "value", Result = null
var wrong = new SuccessResponse<string>("value");
// ✅ Result = "value"
var right = new SuccessResponse<string>("value", null);
The same applies to T = int: new SuccessResponse<int>(42) sets ResponseCode = 42, not Result = 42. Use new SuccessResponse<int>(42, null).
What is the difference between BadRequestResponse and BadRequestResult?
BadRequestResponse is the envelope (Response<BadRequestResult[]>). BadRequestResult is one validation error. Return the response, and put one or more results inside it:
var error = new BadRequestResult { PropertyName = "Email", ErrorMessage = "Required." };
return BadRequest(new BadRequestResponse(error));
Can I change ResponseCode or ResponseType after construction?
Answer: No. All Response<T> properties are init-only, so they can be set in a constructor or an object initializer but not mutated afterward:
var response = new Response<string>
{
// ✅ init accessors work in an object initializer
ResponseCode = 200,
ResponseType = ResponseType.Success,
Result = "payload"
};
// ❌ CS8852 — init-only property
response.ResponseCode = 201;
To vary the code, use a constructor overload or derive from Response<T>.
Why is result null for new ErrorResponse()?
Answer: The parameterless constructor deliberately returns no payload so that internal exception details are not leaked. Result is null and serializes as "result": null. Use new ErrorResponse(new ErrorResult { ... }) when you want a structured payload.
Why doesn't new ForbiddenResponse() compile?
Answer: ForbiddenResponse has no parameterless constructor; its convenience overload is ForbiddenResponse(ErrorResult? result) without a default value. Pass null when you need no payload:
return StatusCode(403, new ForbiddenResponse(null));
UnauthorizedResponse, PaymentRequiredResponse, TooManyRequestsResponse, ServiceUnavailableResponse, and GatewayTimeoutResponse do allow parameterless calls because their result parameter defaults to null.
Why is the HTTP response still 200 when I return an ErrorResponse?
Cause: The response model sets ResponseCode in the body; it does not set the HTTP status code. Returning a model from Ok(...) keeps HTTP 200.
Fix: Return it through the matching ASP.NET Core helper or StatusCode:
// ❌ HTTP 200, body says 500
return Ok(new ErrorResponse());
// ✅ HTTP 500
return StatusCode(500, new ErrorResponse());
Is there a NoContentResponse?
Answer: No. Use ASP.NET Core's NoContent() for HTTP 204. SuccessResponse<T>() is a 200 response with a null result, which is a different contract.
Can I return a status code the package doesn't model?
Answer: Yes. Derive from Response<T> and set the properties in the constructor:
public sealed class TeapotResponse : Response<ErrorResult>
{
public TeapotResponse()
{
ResponseCode = 418;
ResponseType = ResponseType.Error;
ResponseMessage = "I'm a teapot.";
Result = new ErrorResult { ErrorCode = "TEAPOT", ErrorMessage = "Short and stout." };
}
}
Known Limitations
- The model does not set the HTTP status:
ResponseCodeis part of the JSON body. Return the model throughOk,BadRequest,NotFound,Conflict,UnprocessableEntity, orStatusCodeso the transport status matches. - Numeric
responseTypeby default: RegisterJsonStringEnumConverterif clients need names. - Empty messages on explicit overloads:
responseMessagedefaults to""on the all-argument constructors and on nested result constructors. Pass the standard message when you need it. - No constructor validation:
NotFoundResultandConflictResultacceptnullforEntity/Valueat runtime even though the properties are non-nullable. Validate before constructing if the values come from untrusted input. - Closed
ResponseTypeset: You cannot add enum members. UseResponseType.Errorfor uncategorized outcomes and carry the specifics inResponseCode/ErrorCode. - No localization: Messages are English-only constants. Treat them as defaults and localize in your API layer if needed.
- No
NoContentorAcceptedmodel: Use ASP.NET Core'sNoContent()andAccepted()helpers; the package focuses on success and error envelopes.
Performance Tips
- Models are cheap: Each response is a small object graph with no reflection or lazy initialization. Allocation cost is dominated by the payload you attach.
- Reuse the converter options: Register
JsonStringEnumConverteronce at startup instead of buildingJsonSerializerOptionsper request. - Avoid building
ErrorResultfor the generic 500 path:new ErrorResponse()is enough when you do not want to expose details. - Do not serialize the model twice: Return it from the action and let ASP.NET Core serialize it; avoid pre-serializing to a string and wrapping it again.
- Static empty responses are safe to reuse: The models are immutable after construction, so a shared
new ErrorResponse()is safe across requests if you do not need per-request payloads.