Examples
Examples
Complete working examples demonstrating Response Formatting across MVC Controllers and Minimal APIs.
MVC Controller API
A complete REST API controller using ApiResults for consistent responses.
using AspNetConventions.Http;
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
private readonly IProductService _productService;
public ProductsController(IProductService productService)
{
_productService = productService;
}
// GET /api/products
[HttpGet]
public ActionResult<CollectionResult<Product>> GetAll(
[FromQuery] int page = 1,
[FromQuery] int pageSize = 10)
{
var (products, total) = _productService.GetPaged(page, pageSize);
return ApiResults.Paginate(products, total, page, pageSize);
}
// GET /api/products/{id}
[HttpGet("{id}")]
public ActionResult<Product> GetById(int id)
{
var product = _productService.GetById(id);
if (product is null)
return ApiResults.NotFound($"Product {id} not found.");
return ApiResults.Ok(product);
}
// POST /api/products
[HttpPost]
public ActionResult<Product> Create([FromBody] CreateProductRequest request)
{
if (!ModelState.IsValid)
return ApiResults.BadRequest(ModelState);
var product = _productService.Create(request);
return ApiResults.Created(product, "Product created successfully.");
}
// PUT /api/products/{id}
[HttpPut("{id}")]
public ActionResult<Product> Update(int id, [FromBody] UpdateProductRequest request)
{
if (!ModelState.IsValid)
return ApiResults.BadRequest(ModelState);
var product = _productService.Update(id, request);
if (product is null)
return ApiResults.NotFound($"Product {id} not found.");
return ApiResults.Ok(product, "Product updated successfully.");
}
// DELETE /api/products/{id}
[HttpDelete("{id}")]
public ActionResult Delete(int id)
{
var deleted = _productService.Delete(id);
if (!deleted)
return ApiResults.NotFound($"Product {id} not found.");
return ApiResults.NoContent("Product deleted.");
}
}
Response Examples
GET /api/products (paginated):
POST /api/products (created):
POST /api/products (validation error):
{
"status": "success",
"statusCode": 200,
"data": [
{ "id": 1, "name": "Widget A", "price": 29.99 },
{ "id": 2, "name": "Widget B", "price": 39.99 }
],
"pagination": {
"pageNumber": 1,
"pageSize": 25,
"totalPages": 4,
"totalRecords": 100,
"links": {
"firstPageUrl": "/api/products?page-number=1&page-size=25",
"lastPageUrl": "/api/products?page-number=3&page-size=25",
"nextPageUrl": "/api/products?page-number=2&page-size=25",
"previousPageUrl": null
}
},
"metadata": {
"requestType": "GET",
"timestamp": "0000-00-00T00:00:00.000Z",
"traceId": "00-abc123...",
"path": "/api/products"
}
}
{
"status": "success",
"statusCode": 201,
"message": "Product created successfully.",
"data": {
"id": 43,
"name": "New Widget",
"price": 49.99
},
"metadata": { ... }
}
{
"status": "failure",
"statusCode": 400,
"type": "VALIDATION_ERROR",
"message": "One or more validation errors occurred.",
"errors": {
"Name": ["'Name' must not be empty."],
"Price": ["'Price' must be greater than 0."]
},
"metadata": { ... }
}
Minimal API
A complete Minimal API setup with response formatting.
using AspNetConventions;
using AspNetConventions.Http;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
var api = app.UseAspNetConventions();
// GET /api/users
api.MapGet("/api/users", (IUserService userService, int page = 1, int pageSize = 10) =>
{
var (users, total) = userService.GetPaged(page, pageSize);
return ApiResults.Paginate(users, total, page, pageSize);
});
// GET /api/users/{id}
api.MapGet("/api/users/{id}", (int id, IUserService userService) =>
{
var user = userService.GetById(id);
if (user is null)
return ApiResults.NotFound($"User {id} not found.");
return ApiResults.Ok(user);
});
// POST /api/users
app.MapPost("/api/users", (CreateUserRequest request, IUserService userService) =>
{
var user = userService.Create(request);
return ApiResults.Created(user, "User created successfully.");
});
// PUT /api/users/{id}
app.MapPut("/api/users/{id}", (int id, UpdateUserRequest request, IUserService userService) =>
{
var user = userService.Update(id, request);
if (user is null)
return ApiResults.NotFound($"User {id} not found.");
return ApiResults.Ok(user, "User updated.");
});
// DELETE /api/users/{id}
app.MapDelete("/api/users/{id}", (int id, IUserService userService) =>
{
var deleted = userService.Delete(id);
if (!deleted)
return ApiResults.NotFound($"User {id} not found.");
return ApiResults.NoContent();
});
app.Run();
Error Handling Scenarios
Validation Errors
[HttpPost]
public ActionResult<User> CreateUser([FromBody] CreateUserRequest request)
{
// ModelState validation
if (!ModelState.IsValid)
return ApiResults.BadRequest(ModelState);
// Custom validation
if (_userService.EmailExists(request.Email))
return ApiResults.Conflict("A user with this email already exists.");
var user = _userService.Create(request);
return ApiResults.Created(user);
}
Response (ModelState errors):
{
"status": "failure",
"statusCode": 400,
"type": "VALIDATION_ERROR",
"message": "One or more validation errors occurred.",
"errors": {
"Email": ["'Email' is not a valid email address."],
"Password": ["'Password' must be at least 8 characters."]
},
"metadata": { ... }
}
Not Found
[HttpGet("{id}")]
public ActionResult<Order> GetOrder(int id)
{
var order = _orderService.GetById(id);
if (order is null)
return ApiResults.NotFound($"Order {id} not found.");
return ApiResults.Ok(order);
}
Response:
{
"status": "failure",
"statusCode": 404,
"type": "CLIENT_ERROR",
"message": "Order 123 not found.",
"metadata": { ... }
}
Conflict
[HttpPost]
public ActionResult<Account> CreateAccount([FromBody] CreateAccountRequest request)
{
if (_accountService.UsernameExists(request.Username))
return ApiResults.Conflict(
new { field = "username", value = request.Username },
"Username is already taken.");
var account = _accountService.Create(request);
return ApiResults.Created(account);
}
Response:
{
"status": "failure",
"statusCode": 409,
"type": "CLIENT_ERROR",
"message": "Username is already taken.",
"data": {
"field": "username",
"value": "johndoe"
},
"metadata": { ... }
}
Pagination Examples
Basic Pagination
[HttpGet]
public ActionResult<CollectionResult<Product>> GetProducts(
[FromQuery] int page = 1,
[FromQuery] int pageSize = 20)
{
var (products, totalCount) = _productService.GetPaged(page, pageSize);
return ApiResults.Paginate(products, totalCount, page, pageSize);
}
Filtered Pagination
[HttpGet]
public ActionResult<CollectionResult<Product>> SearchProducts(
[FromQuery] string? category,
[FromQuery] decimal? minPrice,
[FromQuery] decimal? maxPrice,
[FromQuery] int page = 1,
[FromQuery] int pageSize = 20)
{
var filter = new ProductFilter
{
Category = category,
MinPrice = minPrice,
MaxPrice = maxPrice
};
var (products, totalCount) = _productService.Search(filter, page, pageSize);
return ApiResults.Paginate(products, totalCount, page, pageSize);
}
Cursor-Based Pagination
For large datasets, use cursor-based pagination:
[HttpGet]
public ActionResult GetProducts([FromQuery] string? cursor, [FromQuery] int limit = 20)
{
var (products, nextCursor, hasMore) = _productService.GetWithCursor(cursor, limit);
return ApiResults.Ok(new
{
items = products,
nextCursor = hasMore ? nextCursor : null,
hasMore
});
}
Integration with Exception Handling
Combine with exception mappers for consistent error responses:
// Custom exception
public class OrderNotFoundException : Exception
{
public int OrderId { get; }
public OrderNotFoundException(int orderId)
: base($"Order {orderId} not found.")
{
OrderId = orderId;
}
}
// Exception mapper
public class OrderNotFoundExceptionMapper : ExceptionMapper<OrderNotFoundException>
{
public override ExceptionDescriptor Map(OrderNotFoundException exception)
{
return new ExceptionDescriptor(
HttpStatusCode.NotFound,
"ORDER_NOT_FOUND",
exception.Message,
new { orderId = exception.OrderId });
}
}
// Controller throws exception
[HttpGet("{id}")]
public ActionResult<Order> GetOrder(int id)
{
var order = _orderService.GetById(id)
?? throw new OrderNotFoundException(id);
return ApiResults.Ok(order);
}
Error response:
{
"status": "failure",
"statusCode": 404,
"type": "ORDER_NOT_FOUND",
"message": "Order 123 not found.",
"data": { "orderId": 123 },
"metadata": { ... }
}