Skip to main content

RA.Utilities.Api.Response

NuGet version NuGet Downloads Codecov Publish NuGet GitHub license

RA.Utilities.Api.Response provides a set of ready-made response models for ASP.NET Core APIs. Every model shares the same envelope — responseCode, responseType, responseMessage, and result — so clients can handle success and failure uniformly.

Use it when you want consistent JSON contracts across endpoints without hand-building response objects, or when you need typed error payloads such as NotFoundResult and BadRequestResult.

🎯 Purpose​

Hand-built API responses drift: one endpoint returns { "error": "..." }, another returns a bare string, and a third uses a different status field. This package replaces that with a single envelope and a family of pre-configured response types. It helps you:

  • Standardize the contract: Every response has the same four top-level properties.
  • Reduce boilerplate: SuccessResponse<T> and the error responses set the status code, semantic type, and default message for you.
  • Keep errors typed: NotFoundResult, ConflictResult, and BadRequestResult carry structured domain context instead of a plain string.
  • Integrate with the ecosystem: Works with RA.Utilities.Api middleware, exception mapping, and the constants in RA.Utilities.Core.Constants.

✨ Key Features​

  • One envelope — Response<T> defines ResponseCode, ResponseType, ResponseMessage, and Result
  • 12 pre-configured responses — success, 400, 401, 402, 403, 404, 409, 422, 429, 500, 503, and 504
  • Typed error payloads — BadRequestResult, NotFoundResult, ConflictResult, and ErrorResult
  • Convenience constructors — single-argument constructors apply the standard code and message
  • Custom overloads — override the response code and message when a domain needs it
  • Immutable after construction — all Response<T> properties use init accessors
  • Zero configuration — no DI registration, no options, no setup
Upgrading from v10.0.0 or earlier?

v10.1.0 changes the namespace from RA.Utilities.Api.Results to RA.Utilities.Api.Response, redesigns the constructor overloads, and — through RA.Utilities.Core.Constants v10.0.4 — writes responseType as a number by default. v10.2.0 adds PaymentRequiredResponse (HTTP 402) and raises the RA.Utilities.Core.Constants dependency to 10.1.1. See the Migration Guides.


🛠️ Getting Started​

Prerequisites​

  • .NET 10.0 or later

Installation​

.NET CLI

dotnet add package RA.Utilities.Api.Response

Package Manager Console

Install-Package RA.Utilities.Api.Response

PackageReference (csproj)

<PackageReference Include="RA.Utilities.Api.Response" Version="10.2.0" />

Hello World​

using Microsoft.AspNetCore.Mvc;
using RA.Utilities.Api.Response;

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
[HttpGet("{id}")]
public IActionResult GetProduct(int id)
{
var product = _productService.GetById(id);

if (product is null)
{
// 404 with a structured NotFoundResult payload
return NotFound(new NotFoundResponse(new NotFoundResult("Product", id)));
}

// 200 with the product as the result payload
return Ok(new SuccessResponse<Product>(product));
}
}

No DI registration or configuration is required. Add the using directive and return the model from your action.


🧠 Core Concepts​

TypeHTTPResponseTypeResult payload
Response<T>anyanyT
SuccessResponse<T>200Success (JSON 0)T
BadRequestResponse400BadRequest (JSON 12)BadRequestResult[]
UnauthorizedResponse401Unauthorized (JSON 10)ErrorResult
PaymentRequiredResponse402PaymentRequired (JSON 18)ErrorResult
ForbiddenResponse403Forbidden (JSON 14)ErrorResult
NotFoundResponse404NotFound (JSON 8)NotFoundResult
ConflictResponse409Conflict (JSON 9)ConflictResult
UnprocessableResponse422Unprocessable (JSON 13)ErrorResult
TooManyRequestsResponse429TooManyRequests (JSON 15)ErrorResult
ErrorResponse500Error (JSON 11)ErrorResult
ServiceUnavailableResponse503ServiceUnavailable (JSON 16)ErrorResult
GatewayTimeoutResponse504GatewayTimeout (JSON 17)ErrorResult

How they work together​

Every response class derives from Response<T> and fills the envelope in its constructor. The controller returns the model (usually through Ok, BadRequest, NotFound, Conflict, or StatusCode), and ASP.NET Core serializes it.

new NotFoundResponse(new NotFoundResult("Product", 99))
├─ ResponseCode = 404
├─ ResponseType = ResponseType.NotFound → JSON: 8
├─ ResponseMessage = "The requested resource was not found."
└─ Result = NotFoundResult
├─ Entity = "Product"
├─ Value = 99
├─ ErrorCode = "NotFound"
└─ ErrorMessage = "" (set explicitly to override)

Terminology​

  • Envelope: The four properties inherited from Response<T> that wrap every payload.
  • Result payload: The Result property — a success object, a BadRequestResult[], or an ErrorResult subtype.
  • Convenience constructor: A single-argument constructor that applies the standard HTTP code, semantic type, and default message.
  • Custom overload: A constructor that takes an explicit responseCode and responseMessage for domain-specific overrides.
  • Numeric responseType: ResponseType is an enum. With default System.Text.Json options it is written as a number; register JsonStringEnumConverter to write names such as "NotFound".

📖 How-To Guides​

Return a successful response​

using Microsoft.AspNetCore.Mvc;
using RA.Utilities.Api.Response;

[HttpGet("{id}")]
public IActionResult GetProduct(int id)
{
var product = _productService.GetById(id);
if (product is null)
{
return NotFound(new NotFoundResponse(new NotFoundResult("Product", id)));
}

return Ok(new SuccessResponse<Product>(product));
}

Return a success with no payload​

SuccessResponse<T> has a parameterless constructor that leaves Result at default:

return Ok(new SuccessResponse<Product>());

For a 204 No Content, prefer ASP.NET Core's NoContent() — this package has no dedicated no-content model.

Return a 400 with validation errors​

var errors = new[]
{
new BadRequestResult
{
PropertyName = "Email",
ErrorMessage = "'Email' must not be empty.",
AttemptedValue = request.Email,
ErrorCode = "NotEmptyValidator"
},
new BadRequestResult
{
PropertyName = "Age",
ErrorMessage = "'Age' must be 18 or older.",
AttemptedValue = request.Age,
ErrorCode = "AGE_TOO_LOW"
}
};

return BadRequest(new BadRequestResponse(errors));

BadRequestResult is the element type; BadRequestResponse is the envelope whose Result is BadRequestResult[].

Return a 404 with entity details​

return NotFound(new NotFoundResponse(new NotFoundResult("Product", id)));

NotFoundResult defaults its ErrorCode to "NotFound" and its ErrorMessage to an empty string. Pass the optional message argument when you want the nested payload to carry text:

var result = new NotFoundResult(
"Product",
id,
errorCode: "PRODUCT_NOT_FOUND",
message: $"Product with id {id} was not found.");

return NotFound(new NotFoundResponse(result));

Return a 409 conflict​

var conflict = new ConflictResult("User", request.Email)
{
ErrorMessage = $"A user with email '{request.Email}' already exists."
};

return Conflict(new ConflictResponse(conflict));

ConflictResult defaults ErrorCode to null (omitted from JSON) and ErrorMessage to an empty string.

Return a 402 when payment is required​

return StatusCode(402, new PaymentRequiredResponse());

The parameterless constructor sets ResponseCode = 402, ResponseType = ResponseType.PaymentRequired, ResponseMessage = "Payment is required to access this resource.", and leaves Result null. Pass a code and message when the reason is domain-specific:

return StatusCode(402, new PaymentRequiredResponse(
402,
"This report is available on paid plans."));

Return a 500 without leaking details​

return StatusCode(500, new ErrorResponse());

The parameterless constructor sets ResponseCode = 500, ResponseType = ResponseType.Error, ResponseMessage = "Something happened on our end.", and leaves Result null.

Override the code and message​

Every error response has an all-argument overload. BadRequestResponse and ConflictResponse require both values; the others accept a nullable code and an optional message:

// BadRequestResponse requires responseCode AND responseMessage
return BadRequest(new BadRequestResponse(
validationError,
responseCode: 422,
responseMessage: "The request could not be processed."));

// NotFoundResponse accepts a nullable code and optional message
return NotFound(new NotFoundResponse(
new NotFoundResult("Product", id),
responseCode: null,
responseMessage: "Product not found."));

Empty-message default: On the all-argument overloads the responseMessage parameter defaults to "", not to the standard message. If you omit it, ResponseMessage is empty. Use the convenience constructor when you want the standard text.

Serialize ResponseType as a string​

The models contain a ResponseType enum. By default, System.Text.Json writes its numeric value (8, 12, ...). Register JsonStringEnumConverter if your clients expect names:

using System.Text.Json.Serialization;

builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.Converters.Add(new JsonStringEnumConverter());
});

Create a custom response type​

Derive from Response<T> when you need a status code the package does not wrap. The init accessors can be set from the constructor:

using RA.Utilities.Api.Response;
using RA.Utilities.Core.Constants;

public sealed class TeapotResponse : Response<ErrorResult>
{
public TeapotResponse()
{
ResponseCode = 418;
ResponseType = ResponseType.Error;
ResponseMessage = "I'm a teapot.";
Result = new ErrorResult
{
ErrorCode = "TEAPOT",
ErrorMessage = "The server refuses to brew coffee."
};
}
}

⚙️ Configuration​

This package has no configuration options. There is no DI registration, no appsettings.json entries, and no options classes.

The only optional setup is JSON serialization: register JsonStringEnumConverter if your API contract expects responseType names instead of numbers. See Serialize ResponseType as a string.


✨ Available Response Types​


🙌 Contributing & Support​

Found a bug or have a suggestion?​

Contributing​

Contributions are welcome! Please follow the standard workflow:

  1. Fork the RA.Utilities repository
  2. Create a branch from main with a descriptive name (e.g., feature/add-response-type)
  3. Make your changes — add XML doc comments for new public APIs and keep the envelope shape unchanged
  4. Submit a pull request to the main branch with a clear description
ResourceURL
NuGetRA.Utilities.Api.Response
SourceGitHub — RedonAlla/RA.Utilities
LicenseMIT
Build StatusGitHub Actions