Alyio.AspNetCore.ApiMessages
3.0.1
dotnet add package Alyio.AspNetCore.ApiMessages --version 3.0.1
NuGet\Install-Package Alyio.AspNetCore.ApiMessages -Version 3.0.1
<PackageReference Include="Alyio.AspNetCore.ApiMessages" Version="3.0.1" />
<PackageVersion Include="Alyio.AspNetCore.ApiMessages" Version="3.0.1" />
<PackageReference Include="Alyio.AspNetCore.ApiMessages" />
paket add Alyio.AspNetCore.ApiMessages --version 3.0.1
#r "nuget: Alyio.AspNetCore.ApiMessages, 3.0.1"
#:package Alyio.AspNetCore.ApiMessages@3.0.1
#addin nuget:?package=Alyio.AspNetCore.ApiMessages&version=3.0.1
#tool nuget:?package=Alyio.AspNetCore.ApiMessages&version=3.0.1
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
ProblemDetails
andIProblemDetailsService
, along with exception handling (IExceptionHandler
,ExceptionHandlerMiddleware
) and filtering (IEndpointFilter
,ExceptionFilterAttribute
) mechanisms.Custom Exception Definition: Enables defining custom, domain-specific API messages by implementing the
IApiMessage
interface 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
ApiMessageAttribute
directly 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
AddApiMessage
endpoint 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
IExceptionHandler
andIEndpointFilter
, 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
IApiMessage
interface to define specifictype
,title
, anddetail
for business logic errors.Targeted Exception Handling:
Use
[ApiMessage]
attribute on controllers or individual action methods for handlingIApiMessage
exceptions 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.WriteUnhandledMessageAsync
in .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(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 HATEOASLink
objects, typically including aself
link 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 theLocation
header andself
link based on an action method.CreatedMessageAtRoute(string routeName, object routeValues, string id)
: Generates theLocation
header andself
link 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
BadRequestMessage
class and other similar classes have been replaced by new exception classes, such asBadRequestException
. IApiMessage
Interface: The interface is simplified to expose a singleProblemDetails
property.- Configuration: The setup method is changed. The legacy
UseApiMessage
middleware 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
ProblemDetails
JSON object. This may affect clients expecting the old format. - Validation Error Structure: The structure of validation errors within the
ProblemDetails
object 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
IApiMessage
must be refactored to create and expose aProblemDetails
object.
- All usages of old message classes (e.g.,
CreatedMessage
Usage: TheCreatedMessage
class and its helper extensions now useSystem.Text.Json
and 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
ProblemDetails
JSON 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
errors
field:["FieldName: Error message"]
- New
errors
field:{"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 was computed. 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. |
-
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.0.1 | 32 | 9/27/2025 |
3.0.1-preview.10 | 33 | 9/27/2025 |
2.1.0 | 2,264 | 11/10/2018 |
2.0.2 | 3,174 | 1/11/2018 |
2.0.0 | 2,228 | 8/22/2017 |
1.1.3 | 2,202 | 8/11/2017 |
1.1.2 | 2,217 | 8/4/2017 |
1.1.1 | 2,053 | 7/18/2017 |
1.1.0 | 1,950 | 6/16/2017 |
1.0.6 | 2,089 | 5/5/2017 |
1.0.5 | 2,045 | 5/4/2017 |
1.0.4 | 2,365 | 4/11/2017 |
1.0.2 | 2,789 | 12/26/2016 |
1.0.1 | 2,637 | 12/19/2016 |