Alyio.AspNetCore.ApiMessages
3.2.0
dotnet add package Alyio.AspNetCore.ApiMessages --version 3.2.0
NuGet\Install-Package Alyio.AspNetCore.ApiMessages -Version 3.2.0
<PackageReference Include="Alyio.AspNetCore.ApiMessages" Version="3.2.0" />
<PackageVersion Include="Alyio.AspNetCore.ApiMessages" Version="3.2.0" />
<PackageReference Include="Alyio.AspNetCore.ApiMessages" />
paket add Alyio.AspNetCore.ApiMessages --version 3.2.0
#r "nuget: Alyio.AspNetCore.ApiMessages, 3.2.0"
#:package Alyio.AspNetCore.ApiMessages@3.2.0
#addin nuget:?package=Alyio.AspNetCore.ApiMessages&version=3.2.0
#tool nuget:?package=Alyio.AspNetCore.ApiMessages&version=3.2.0
Alyio.AspNetCore.ApiMessages
A lightweight ASP.NET Core library that seamlessly integrates with ASP.NET Core's error handling mechanisms to standardize API error handling and messages into Problem Details for HTTP APIs responses.
Table of Contents
- Why Alyio.AspNetCore.ApiMessages?
- Installation
- Quick Start
- Usage
- Migration Guide from 2.x to 3.x
- Problem Details for HTTP APIs
- Contributing
- License
- Contact
Why Alyio.AspNetCore.ApiMessages?
Developing robust and user-friendly APIs often involves handling responses consistently, whether they indicate an error or a successful operation. In ASP.NET Core, achieving this standardization across various scenarios (exceptions, validation errors, successful resource creation) can be repetitive and lead to inconsistent API contracts.
By utilizing Alyio.AspNetCore.ApiMessages, the development of reliable, maintainable, and developer-friendly APIs is facilitated with reduced effort.
Standardized Error Responses: Automatically transforms exceptions into RFC 9457 (formerly RFC 7807) Problem Details responses, ensuring consistent API error formats.
Standardized Success Responses: Provides mechanisms for consistent HTTP 201 Created responses, including HATEOAS links.
Seamless ASP.NET Core Integration: Leverages ASP.NET Core's built-in
ProblemDetailsandIProblemDetailsService, along with exception handling (IExceptionHandler,ExceptionHandlerMiddleware) and filtering (IEndpointFilter,ExceptionFilterAttribute) mechanisms.Custom Exception Definition: Enables defining custom, domain-specific API messages by implementing the
IApiMessageinterface for clear error representation.Middleware-Based Handling: Provides comprehensive middleware-based exception handling and Problem Details generation for both legacy (.NET 6.0 using
ApiMessageHandlerMiddleware) and modern (.NET 8.0+ leveragingExceptionHandlerMiddleware) ASP.NET Core applications.Attribute-Based Handling: Apply
ApiMessageAttributedirectly to controllers/action methods or register it globally as an action filter for targeted exception handling.Endpoint Filter Integration: Apply API message handling directly to individual minimal API endpoints or to groups of endpoints (for .NET 8.0+) using the
AddApiMessageendpoint filter extension.Dual-Targeting: Supports the legacy .NET 6.0, and .NET 8.0+ (including .NET 9.0 and .NET 10.0) with optimized configurations for each.
Installation
Install the NuGet package:
dotnet add package Alyio.AspNetCore.ApiMessages
Quick Start
This guide demonstrates configuring global exception handling for a .NET 8.0+ application to automatically convert exceptions into standardized ProblemDetails responses.
1. Configure Services and Middleware in Program.cs
First, register the required services by calling builder.Services.AddApiMessages(). Then, enable the global exception handler by adding app.UseExceptionHandler() to the request processing pipeline.
// Program.cs
var builder = WebApplication.CreateBuilder(args);
// Add services for controllers and API message handling.
builder.Services.AddControllers();
builder.Services.AddApiMessages();
var app = builder.Build();
// Enable the global exception handler. This must be placed early
// in the pipeline to catch exceptions from subsequent middleware.
app.UseExceptionHandler();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
2. Throw an API Message Exception
With the global handler configured, any thrown exception that implements IApiMessage will be automatically caught and formatted. No special attributes are required on the controller.
// Controllers/ProductsController.cs
using Alyio.AspNetCore.ApiMessages;
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
[HttpGet("{id}")]
public IActionResult GetProduct(int id)
{
if (id <= 0)
{
// This exception will be handled by the global exception handler.
throw new NotFoundException($"Product with ID '{id}' was not found.");
}
return Ok(new { ProductId = id, Name = "Sample Product" });
}
}
3. Observe the Standardized Error Response
A GET request to /api/products/0 will trigger the NotFoundException, and the middleware will produce the following 404 Not Found response.
Resulting HTTP Response:
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.5",
"title": "Not Found",
"status": 404,
"detail": "Product with ID '0' was not found.",
"instance": "/api/products/0",
"traceId": "00-..."
}
This configuration ensures that all IApiMessage exceptions are handled consistently across the application. For further details on defining custom exceptions, using attribute-based filters, or configuring for legacy runtimes, refer to the Usage section.
Usage
Prioritize .NET 8.0+: If applicable, target .NET 8.0+ to leverage the latest ASP.NET Core features like
IExceptionHandlerandIEndpointFilter, which offer more streamlined and performant error handling.Global Exception Handling: Always configure a global exception handler to ensure all unhandled exceptions across the application result in consistent Problem Details responses.
For .NET 8.0+, use
app.UseExceptionHandler()in conjunction withbuilder.Services.AddApiMessages().For .NET 6.0, use
app.UseExceptionHandler(ExceptionHandler.WriteUnhandledMessageAsync)andapp.UseApiMessage().
Define Custom API Messages: For domain-specific errors that require a standardized Problem Details response, create custom exceptions that implement the
IApiMessageinterface to define specifictype,title, anddetailfor business logic errors.Targeted Exception Handling:
Use
[ApiMessage]attribute on controllers or individual action methods for handlingIApiMessageexceptions thrown within the methods.For Minimal APIs or endpoint routing in .NET 8.0+, apply
.AddApiMessage()to specific endpoints.
Security: Information exposed in Problem Details responses should be reviewed. Avoid leaking sensitive exception details (e.g., stack traces) in production environments. The library's default behavior for generic exceptions (when handled by
ExceptionHandler.WriteUnhandledMessageAsyncin .NET 6.0) provides a basic message, but review what information is appropriate for the production environment.
Configuration
The library provides different configuration patterns based on the target .NET framework.
For .NET 8.0+
The primary approach for configuring API message handling involves combining services.AddApiMessages() and app.UseExceptionHandler() to enable comprehensive API message handling.
In Program.cs:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddApiMessages(); // Adds ProblemDetails, ApiMessageFilter, and ApiMessagesExceptionHandler
var app = builder.Build();
// Configure the HTTP request pipeline.
app.UseExceptionHandler(); // Uses ApiMessagesExceptionHandler for global exception handling
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
For the Legacy .NET 6.0
The primary approach for configuring API message handling in legacy .NET 6.0 applications involves utilizing middleware and the traditional exception handler.
In Program.cs:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
var app = builder.Build();
// Configure the HTTP request pipeline.
app.UseExceptionHandler(new ExceptionHandlerOptions { ExceptionHandler = ExceptionHandler.WriteUnhandledMessageAsync }); // Handles unhandled exceptions
app.UseApiMessage(); // Catches IApiMessage exceptions
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Defining and Throwing API Messages
The library provides several built-in exceptions for common HTTP error scenarios, including BadRequestException, ForbiddenException, InternalServerErrorException, NotFoundException, and UnauthorizedException. For custom API messages, implement the IApiMessage interface.
Here's how to define and throw a custom API message:
// 1. Define a custom API message
public class ProductNotFoundException : Exception, IApiMessage
{
public ProblemDetails ProblemDetails { get; }
public ProductNotFoundException(int productId)
{
ProblemDetails = new ProblemDetails
{
Title = "Product Not Found",
Status = StatusCodes.Status404NotFound,
Detail = $"Product with ID '{productId}' was not found.",
Type = "https://example.com/problems/product-not-found"
};
}
}
// 2. Throw it in a controller action
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
[HttpGet("{id}")]
public IActionResult Get(int id)
{
// Simulate product not found
if (id == 0)
{
throw new ProductNotFoundException(id);
}
return Ok($"Product {id}");
}
}
// 3. Example HTTP Response (for GET /api/products/0):
// HTTP/1.1 404 Not Found
//
// Content-Type: application/problem+json; charset=utf-8
//
// {
// "type": "https://example.com/problems/product-not-found",
// "title": "Product Not Found",
// "status": 404,
// "detail": "Product with ID '0' was not found.",
// "instance": "/api/products/0",
// "traceId": "00-..."
// }
Using Action Filters (for Controller-Based APIs)
While middleware-based handling is the common approach for global exception management, [ApiMessage] attribute offers a more granular way to handle IApiMessage exceptions. It can be applied to controllers or individual action methods to automatically handle exceptions thrown within the methods, or registered globally as an action filter.
[ApiController]
[Route("api/[controller]")]
[ApiMessage] // Apply to the whole controller
public class MyController : ControllerBase
{
[HttpGet("{id}")]
public IActionResult Get(int id)
{
if (id <= 0)
{
throw new BadRequestException("Invalid ID provided.");
}
return Ok();
}
}
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "Bad Request",
"status": 400,
"detail": "Invalid ID provided.",
"instance": "/api/mycontroller/0",
"traceId": "00-..."
}
[HttpPost]
[ApiMessage] // Apply to a specific action
public IActionResult Create([FromBody] MyModel model)
{
if (!ModelState.IsValid)
{
throw new BadRequestException(ModelState);
}
// ...
return CreatedAtAction(nameof(Get), new { id = 1 }, null);
}
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "ValidationFailed",
"status": 400,
"detail": "One or more validation errors occurred.",
"instance": "/api/mycontroller",
"traceId": "00-...",
"errors": {
"PropertyName": [
"Error message for PropertyName."
]
}
}
// Registered globally as an action filter
builder.Services.AddControllers(options => options.Filters.Add(typeof(ApiMessageAttribute)));
Using Endpoint Filters (for .NET 8.0+ Minimal APIs)
Apply API message handling directly to individual minimal API endpoints or to groups of endpoints (for .NET 8.0+) using the AddApiMessage endpoint filter extension.
app.MapGet("/items/{id}", (int id) =>
{
if (id <= 0)
{
throw new BadRequestException("Invalid ID provided.");
}
return Results.Ok($"Item {id}");
}).AddApiMessage(); // Apply the endpoint filter
Built-in API Message Examples
The library provides several built-in exceptions that implement IApiMessage for common HTTP error scenarios. Here are examples of their usage and the Problem Details responses they generate.
ForbiddenException (HTTP 403)
[ApiController]
[Route("api/examples")]
public class ForbiddenExampleController : ControllerBase
{
[HttpGet("forbidden")]
public IActionResult GetForbidden()
{
// Simulate a forbidden access scenario
throw new ForbiddenException("Access to this resource is forbidden.");
}
}
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.4",
"title": "Forbidden",
"status": 403,
"detail": "Access to this resource is forbidden.",
"instance": "/api/examples/forbidden",
"traceId": "00-..."
}
InternalServerErrorException (HTTP 500)
[ApiController]
[Route("api/examples")]
public class InternalServerErrorExampleController : ControllerBase
{
[HttpGet("internal-server-error")]
public IActionResult GetInternalServerError()
{
// Simulate an unexpected server error
throw new InternalServerErrorException("An unexpected error occurred on the server.");
}
}
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.6.1",
"title": "Internal Server Error",
"status": 500,
"detail": "An unexpected error occurred on the server.",
"instance": "/api/examples/internal-server-error",
"traceId": "00-..."
}
NotFoundException (HTTP 404)
[ApiController]
[Route("api/examples")]
public class NotFoundExampleController : ControllerBase
{
[HttpGet("not-found")]
public IActionResult GetNotFound()
{
// Simulate a resource not found scenario
throw new NotFoundException("The requested resource was not found.");
}
}
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.5",
"title": "Not Found",
"status": 404,
"detail": "The requested resource was not found.",
"instance": "/api/examples/not-found",
"traceId": "00-..."
}
UnauthorizedException (HTTP 401)
[ApiController]
[Route("api/examples")]
public class UnauthorizedExampleController : ControllerBase
{
[HttpGet("unauthorized")]
public IActionResult GetUnauthorized()
{
// Simulate an unauthorized access scenario
throw new UnauthorizedException("Authentication is required to access this resource.");
}
}
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.2",
"title": "Unauthorized",
"status": 401,
"detail": "Authentication is required to access this resource.",
"instance": "/api/examples/unauthorized",
"traceId": "00-..."
}
Handling 201 Created Responses with CreatedMessage
In RESTful API design, an HTTP 201 Created status code indicates that a request has been fulfilled and has resulted in one or more new resources being created. The Location header in the response typically points to the URI of the primary resource created.
The library provides the CreatedMessage class and associated extension methods to standardize and simplify the generation of 201 Created responses. This ensures consistency, proper HATEOAS (Hypermedia as the Engine of Application State) links, and ease of consumption by API clients.
The CreatedMessage Class
The CreatedMessage class is a data structure designed for use as the body of a 201 Created response. It includes:
Id: A string identifier for the newly created resource.Links: A collection of HATEOASLinkobjects, typically including aselflink pointing to the URI of the created resource.
Using Extension Methods for Convenience
While manual construction of a CreatedMessage is possible, the library offers convenient extension methods on ControllerBase to streamline the process. These methods automatically handle setting the HTTP 201 status code, populating the Location header, and constructing the CreatedMessage object with appropriate links.
CreatedMessageAtAction(string actionName, object routeValues, string id): Generates theLocationheader andselflink based on an action method.CreatedMessageAtRoute(string routeName, object routeValues, string id): Generates theLocationheader andselflink based on a named route.
A comprehensive example demonstrating the use of CreatedMessageAtAction is provided below:
[ApiController]
[Route("api/examples")]
public class ResourceController : ControllerBase
{
[HttpPost("resources")]
public IActionResult CreateResource()
{
// Simulate resource creation logic
var newResourceId = Guid.NewGuid().ToString();
// The CreatedMessageAtAction extension method is used to build the
// response body, including a 'self' link, conforming to the CreatedMessage structure.
var createdMessage = this.CreatedMessageAtAction(
actionName: nameof(GetResourceById),
routeValues: new { id = newResourceId },
id: newResourceId);
// The standard CreatedAtAction method creates the HTTP 201 response,
// sets the 'Location' header, and includes the createdMessage object as the response body.
return CreatedAtAction(nameof(GetResourceById), new { id = newResourceId }, createdMessage);
}
[HttpGet("resources/{id}", Name = "GetResourceById")]
public IActionResult GetResourceById(string id)
{
// Logic to retrieve the resource by ID
// For demonstration purposes, a simple message is returned.
return Ok($"Successfully retrieved resource with ID: {id}");
}
}
Example HTTP 201 Response
When the CreateResource endpoint is called, the response will appear similar to the following:
HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
Location: /api/examples/resources/a1b2c3d4-e5f6-7890-1234-567890abcdef
{
"id": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
"links": [
{
"rel": "self",
"href": "/api/examples/resources/a1b2c3d4-e5f6-7890-1234-567890abcdef",
"method": "GET"
}
]
}
Note: The Location header is automatically set by the CreatedMessageAtAction extension method. Its value matches the href in the self link within the response body, providing a consistent and discoverable API experience.
Migration Guide from 2.x to 3.x
Version 3.0 introduces significant breaking changes as part of its modernization. The library aligns with .NET 8+ standards, embraces ProblemDetails for RFC 9457 compliance, and simplifies its public API.
Summary of Breaking Changes
- Public API: The
BadRequestMessageclass and other similar classes have been replaced by new exception classes, such asBadRequestException. IApiMessageInterface: The interface is simplified to expose a singleProblemDetailsproperty.- Configuration: The setup method is changed. The legacy
UseApiMessagemiddleware is available for .NET 6 but is not the recommended approach for modern applications. - JSON Response Format: The error response format is changed from a custom schema to the standard
ProblemDetailsJSON object. This may affect clients expecting the old format. - Validation Error Structure: The structure of validation errors within the
ProblemDetailsobject is updated to align with ASP.NET Core conventions.
API Developer Migration (Server-Side)
Configuration (
Program.cs):- For .NET 8+ (Recommended):
app.UseApiMessageHandler()should be replaced withbuilder.Services.AddApiMessages()andapp.UseExceptionHandler(). - For .NET 6: The old
app.UseApiMessageHandler()should be replaced with the newapp.UseApiMessage().
- For .NET 8+ (Recommended):
Exception Classes:
- All usages of old message classes (e.g.,
new BadRequestMessage()) are to be replaced with their new exception equivalents (new BadRequestException()). - Custom exceptions implementing
IApiMessagemust be refactored to create and expose aProblemDetailsobject.
- All usages of old message classes (e.g.,
CreatedMessageUsage: TheCreatedMessageclass and its helper extensions now useSystem.Text.Jsonand nullable reference types. All usage should be reviewed for compatibility.
API Consumer Migration (Client-Side)
Error Response Handling: Clients must be updated to parse the standard
ProblemDetailsJSON object instead of the old custom error format.Validation Error Logic: The structure of validation errors has changed. Client-side code that displays validation messages must be updated to handle the new dictionary structure.
- Old
errorsfield:["FieldName: Error message"] - New
errorsfield:{"FieldName": ["Error message"]}
- Old
Problem Details for HTTP APIs
This library adheres to the Problem Details for HTTP APIs specification (RFC 9457, which obsoletes RFC 7807). This standard provides a machine-readable format for HTTP API responses to convey details of a problem. It defines a JSON (or XML) structure with fields like type, title, status, detail, and instance, allowing clients to understand and handle API errors consistently.
Contributing
Contributions are welcome! Please feel free to open issues or submit pull requests.
License
This project is licensed under the MIT License. See the LICENSE file for details.
Contact
For questions or feedback, please open an issue on the GitHub repository.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- No dependencies.
-
net6.0
- No dependencies.
-
net8.0
- No dependencies.
-
net9.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 3.2.0 | 302 | 11/12/2025 |
| 3.0.1 | 187 | 9/27/2025 |
| 3.0.1-preview.10 | 57 | 9/27/2025 |
| 2.1.0 | 2,309 | 11/10/2018 |
| 2.0.2 | 3,286 | 1/11/2018 |
| 2.0.0 | 2,300 | 8/22/2017 |
| 1.1.3 | 2,315 | 8/11/2017 |
| 1.1.2 | 2,328 | 8/4/2017 |
| 1.1.1 | 2,140 | 7/18/2017 |
| 1.1.0 | 2,034 | 6/16/2017 |
| 1.0.6 | 2,183 | 5/5/2017 |
| 1.0.5 | 2,145 | 5/4/2017 |
| 1.0.4 | 2,501 | 4/11/2017 |
| 1.0.2 | 2,934 | 12/26/2016 |
| 1.0.1 | 2,770 | 12/19/2016 |