FluentResponse.ApiWrapper
1.0.0
dotnet add package FluentResponse.ApiWrapper --version 1.0.0
NuGet\Install-Package FluentResponse.ApiWrapper -Version 1.0.0
<PackageReference Include="FluentResponse.ApiWrapper" Version="1.0.0" />
<PackageVersion Include="FluentResponse.ApiWrapper" Version="1.0.0" />
<PackageReference Include="FluentResponse.ApiWrapper" />
paket add FluentResponse.ApiWrapper --version 1.0.0
#r "nuget: FluentResponse.ApiWrapper, 1.0.0"
#:package FluentResponse.ApiWrapper@1.0.0
#addin nuget:?package=FluentResponse.ApiWrapper&version=1.0.0
#tool nuget:?package=FluentResponse.ApiWrapper&version=1.0.0
FluentResponse
Enterprise API Response Wrapper Library for .NET
FluentResponse is a production-ready API response wrapper for ASP.NET Core that standardizes responses across all your projects while allowing full customization of the structure, metadata, error handling, pagination, and serialization behavior. Built with Clean Architecture and SOLID principles.
Table of Contents
- Features
- Quick Start
- Response Types
- Fluent Builder API
- Response Customization
- Global Exception Middleware
- Custom Exceptions
- Pagination
- FluentValidation Integration
- Scalar API Documentation
- Serialization
- Example Projects
- Benchmarks
- Architecture
- Project Structure
- Extensibility
- Best Practices
- NuGet Package
- Dependencies
- License
Features
| Feature | Description |
|---|---|
| Standard Response Format | { success, statusCode, message, data, errors, timestamp, traceId } |
| Full Customization | Rename, add, or remove any property per project |
| Pagination | Built-in paged responses with metadata |
| Global Exception Handling | Middleware that catches and formats all exceptions |
| FluentValidation | Auto-converts validation errors to response format |
| Localization | Multi-language messages via IStringLocalizer |
| Correlation / Trace ID | Automatic request tracking with distributed tracing |
| Fluent Builder API | Chainable API for building responses |
| Custom Exceptions | NotFoundException, BusinessException, UnauthorizedException, ConflictException, ForbiddenException |
| Custom Serialization | Full control over JSON via custom JsonConverter |
| Minimal API & MVC | First-class support for both paradigms |
| Response Metadata | Add version, execution time, server info |
| Scalar / OpenAPI | Automatic API documentation via Scalar UI |
| Benchmark Suite | BenchmarkDotNet project for performance measurement |
| Extensibility | Custom models, serializers, middleware, exception handlers |
Quick Start
Installation
dotnet add package FluentResponse
Basic Setup
using FluentResponse.Extensions;
var builder = WebApplication.CreateBuilder(args);
// Register with default options
builder.Services.AddFluentResponse();
// Or fully customize:
builder.Services.AddFluentResponse(options =>
{
options.SuccessPropertyName = "isSuccess";
options.MessagePropertyName = "msg";
options.IncludeTimestamp = true;
options.IncludeTraceId = true;
options.EnableExecutionTimeTracking = true;
});
var app = builder.Build();
// Exception handling middleware
app.UseFluentResponseExceptionHandler();
app.UseFluentResponseCorrelationId();
Launch Settings
Both sample projects include pre-configured Properties/launchSettings.json:
{
"profiles": {
"http": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "scalar",
"applicationUrl": "http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
Minimal API Example
using FR = FluentResponse.FluentResponse;
app.MapGet("/users/{id}", (int id) =>
{
var user = GetUser(id);
if (user is null)
return Results.NotFound(FR.NotFound<User>($"User {id} not found"));
return Results.Ok(FR.Success(user, "User found"));
})
.WithName("GetUserById")
.WithOpenApi();
MVC Controller Example
using FR = FluentResponse.FluentResponse;
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
[HttpGet]
public IActionResult GetAll()
{
return Ok(FR.Success(Users, "Users retrieved"));
}
[HttpPost]
public IActionResult Create(CreateUserRequest request)
{
var user = new User(Users.Count + 1, request.Name, request.Email);
Users.Add(user);
return CreatedAtAction(nameof(GetById), new { id = user.Id },
FR.Created(user, "User created"));
}
}
Response Types
Success Response
// Default — status 200, message "Success"
FR.Success(data);
// Custom message
FR.Success(data, "Users loaded successfully");
// Without data (non-generic)
FR.Success("Operation completed");
Failure Response
// Simple failure — status 400
FR.Fail("User not found");
// With multiple errors
FR.Fail("Validation failed",
["Email is required", "Password too short"]);
// Typed failure
FR.Fail<UserDto>("Not found");
Status-Specific Helpers
FR.Success(data); // 200
FR.Created(data, "Created"); // 201
FR.NotFound<User>("Not found"); // 404
FR.Unauthorized<object>("Invalid token"); // 401
FR.Forbidden<object>("Access denied"); // 403
FR.Conflict<object>("Conflict", errors); // 409
FR.ValidationError<object>(errors); // 422
FR.Error<object>("Server error"); // 500
Paged Response
var users = _service.GetAll();
var result = FR.PagedSuccess(users, pageNumber: 1, pageSize: 10, totalRecords: 100);
{
"success": true,
"data": [...],
"pagination": {
"pageNumber": 1,
"pageSize": 10,
"totalRecords": 100,
"totalPages": 10,
"hasPreviousPage": false,
"hasNextPage": true
}
}
Response Metadata
FR.Success(data)
.WithMetadata("apiVersion", "v2")
.WithMetadata("server", Environment.MachineName);
{
"success": true,
"data": {},
"metadata": {
"apiVersion": "v2",
"server": "api-01"
}
}
Trace & Correlation IDs
app.MapGet("/trace", (HttpContext context) =>
{
var traceId = context.GetTraceId();
var correlationId = context.GetCorrelationId();
return Results.Ok(FR.Success(new { traceId, correlationId }, "Tracing info"));
});
Fluent Builder API
using FRBuilder = FluentResponse.FluentResponseBuilder;
// Typed builder
FRBuilder
.Success<UserDto>()
.WithData(user)
.WithMessage("User created")
.WithStatusCode(201)
.WithError("Warning: email not verified")
.Build();
// Paged builder
FRBuilder
.Paged<User>()
.WithData(users)
.WithMessage("Users loaded")
.WithPagination(1, 10, 100)
.Build();
Response Customization
This is the most powerful feature. Each consuming project can fully customize the JSON output without modifying the library source.
Custom Property Names
builder.Services.AddFluentResponse(options =>
{
options.SuccessPropertyName = "isSuccess";
options.MessagePropertyName = "msg";
options.StatusCodePropertyName = "code";
options.DataPropertyName = "payload";
options.ErrorsPropertyName = "issues";
options.TimestampPropertyName = "time";
options.TraceIdPropertyName = "requestId";
});
{ "isSuccess": true, "code": 200, "msg": "OK", "payload": {}, "issues": [] }
Field Inclusion Control
options.IncludeTimestamp = false; // Remove timestamp
options.IncludeTraceId = false; // Remove trace ID
options.IncludeMetadata = false; // Remove metadata block
options.IgnoreNullValues = true; // Omit null fields from output
Timestamp & Trace Configuration
options.TimestampFormat = "yyyy-MM-ddTHH:mm:ssZ";
options.TimestampProvider = () => DateTimeOffset.UtcNow;
options.EnableExecutionTimeTracking = true;
Different Projects, Different Shapes
| Project A | Project B | Project C |
|---|---|---|
{ "success": true, "message": "OK" } |
{ "isSuccess": true, "msg": "Completed" } |
{ "status": "success", "payload": {} } |
Each project configures its own AddFluentResponse(options => ...).
Global Exception Middleware
app.UseFluentResponseExceptionHandler(); // Exception handling only
app.UseFluentResponseCorrelationId(); // Correlation/trace ID
app.UseFluentResponseExecutionTime(); // Execution time tracking
app.UseFluentResponseLocalization(); // Culture detection
app.UseFluentResponseAll(); // All of the above
Exception → HTTP Status Mapping
| Exception | Status Code |
|---|---|
NotFoundException |
404 |
BusinessException |
400 |
UnauthorizedException |
401 |
ForbiddenException |
403 |
ConflictException |
409 |
ArgumentException |
400 |
KeyNotFoundException |
404 |
UnauthorizedAccessException |
401 |
InvalidOperationException |
409 |
| Unhandled exceptions | 500 (sensitive details hidden when HideSensitiveErrors is true) |
Custom Exceptions
throw new NotFoundException("User", userId);
throw new BusinessException("Insufficient funds", ["Minimum: $100"]);
throw new UnauthorizedException("Authentication required");
throw new ForbiddenException("Admin access required");
throw new ConflictException("Duplicate entry", ["Email already in use"]);
These are automatically caught by the middleware and return a properly formatted FluentResponse.
Pagination
Default Pagination Response
{
"success": true,
"data": [...],
"pagination": {
"pageNumber": 1,
"pageSize": 10,
"totalRecords": 100,
"totalPages": 10,
"hasPreviousPage": false,
"hasNextPage": true
}
}
Custom Pagination Naming
builder.Services.AddFluentResponse((responseOpts, paginationOpts) =>
{
paginationOpts.PageNumberPropertyName = "page";
paginationOpts.PageSizePropertyName = "perPage";
paginationOpts.DefaultPageSize = 20;
paginationOpts.MaxPageSize = 200;
});
Helper Methods
PaginationHelper.NormalizePageNumber(page); // Clamp to >= 1
PaginationHelper.NormalizePageSize(size, 10, 100); // Clamp between 1 and max
PaginationHelper.CalculatePagination(1, 10, 100); // Returns (page, size, total, pages)
FluentValidation Integration
using FluentResponse.Validators;
var result = await validator.ValidateAsync(dto);
if (!result.IsValid)
{
// Convert to FluentResponse ApiResponse (non-generic)
return ValidationResultConverter.ToApiResponse(result);
// Or typed version
return ValidationResultConverter.ToApiResponse<UserDto>(result);
}
Output:
{
"success": false,
"statusCode": 422,
"message": "Validation failed",
"errors": ["Name is required", "Email is invalid"]
}
Scalar API Documentation
Both sample projects are pre-configured with Scalar — a modern, beautiful API documentation UI (alternative to Swagger).
Setup
using Scalar.AspNetCore;
builder.Services.AddOpenApi();
var app = builder.Build();
app.MapOpenApi();
app.MapScalarApiReference(options =>
{
options
.WithTitle("My API")
.WithTheme(ScalarTheme.Purple)
.WithDefaultHttpClient(ScalarTarget.CSharp, ScalarClient.HttpClient);
});
The launchSettings.json opens the browser to /scalar automatically on dotnet run.
Serialization
FluentResponse uses a custom JsonConverterFactory for System.Text.Json that:
- Reads your configured
FluentResponseOptionsat runtime - Maps internal property names to configured names (e.g.,
Success→isSuccess) - Conditionally includes/excludes fields based on
IncludeTimestamp,IncludeTraceId, etc. - Writes directly to
Utf8JsonWriterfor maximum performance - Is automatically registered via
IConfigureOptions<JsonOptions>when you callAddFluentResponse()
No additional setup required — just configure options and the converter handles the rest.
Example Projects
The repository includes two comprehensive example projects. Both are pre-configured with launch settings and Scalar API docs.
Minimal API Example (samples/FluentResponse.Sample.MinimalApi)
20 endpoints demonstrating every feature:
GET /users List all users (success response)
GET /users/{id} Get user by ID (not-found handling)
POST /users Create user (201 created response)
DELETE /users/{id} Delete user (204 no content)
GET /users/paged Paged user list
GET /error Failure response with errors
GET /builder Fluent builder pattern
GET /metadata Response with metadata
GET /trace Trace & correlation IDs
GET /status/401 Unauthorized helper
GET /status/403 Forbidden helper
GET /status/409 Conflict helper
GET /status/422 Validation error helper
GET /status/500 Server error helper
GET /throw/notfound Exception → 404 middleware
GET /throw/business Exception → 400 middleware
GET /throw/unauthorized Exception → 401 middleware
GET /throw/conflict Exception → 409 middleware
GET /throw/forbidden Exception → 403 middleware
MVC Controller Example (samples/FluentResponse.Sample.Mvc)
Traditional [ApiController] with 13 endpoints via UsersController:
GET /api/users List all users
GET /api/users/{id} Get by ID
POST /api/users Create user
PUT /api/users/{id} Update user
DELETE /api/users/{id} Delete user
GET /api/users/paged Paged results
GET /api/users/metadata With metadata
GET /api/users/builder Builder pattern
GET /api/users/error Failure response
GET /api/users/validation-error 422 validation response
GET /api/users/throw/not-found Exception → 404
GET /api/users/throw/conflict Exception → 409
GET /api/users/throw/business Exception → 400
Run an Example
cd samples/FluentResponse.Sample.MinimalApi
dotnet run
# Opens browser at http://localhost:5000/scalar
Benchmarks
The repository includes a BenchmarkDotNet project (benchmarks/FluentResponse.Benchmarks) that measures:
| Benchmark | Description |
|---|---|
| Serialization throughput | FluentResponse custom converter vs standard System.Text.Json |
| Deserialization | Both with and without custom converter |
| Builder performance | Static method vs fluent builder API |
| Memory allocation | Heap allocations per operation via [MemoryDiagnoser] |
Run Benchmarks
cd benchmarks\FluentResponse.Benchmarks
dotnet run -c Release
Results (approximate)
| Scenario | Std Json | FluentResponse | Difference |
|---|---|---|---|
| Success Response | baseline | ~comparable | < 5% overhead |
| List Response (100 items) | baseline | ~comparable | Linear scaling |
| Paged Response | baseline | ~comparable | Pagination metadata included |
| Error Response | baseline | ~comparable | No measurable overhead |
The custom JsonConverter writes directly to Utf8JsonWriter without intermediate object allocations, keeping performance on par with standard serialization while enabling full response customization.
Architecture
FluentResponse.sln
├── src/FluentResponse/ # Core library (NuGet package)
│ ├── Builders/ # Fluent API builders
│ ├── Configurations/ # Configuration classes
│ ├── Constants/ # Status codes, default messages
│ ├── Exceptions/ # Custom exception types
│ ├── Extensions/ # DI, middleware, response extensions
│ ├── Helpers/ # TraceId, DateTime, Pagination
│ ├── Interfaces/ # All public interfaces
│ ├── Middleware/ # Exception, Correlation, ExecutionTime, Localization
│ ├── Models/ # ApiResponse, PagedResponse, Metadata
│ ├── Options/ # FluentResponseOptions, PaginationOptions
│ ├── Pagination/ # PaginationMetadata
│ ├── Serialization/ # Custom JsonConverter (System.Text.Json)
│ ├── Services/ # Factory, ExceptionHandler
│ └── Validators/ # FluentValidation integration
├── samples/
│ ├── FluentResponse.Sample.MinimalApi/ # Minimal API demo (20 endpoints)
│ └── FluentResponse.Sample.Mvc/ # MVC controller demo (13 endpoints)
├── benchmarks/
│ └── FluentResponse.Benchmarks/ # BenchmarkDotNet suite
└── tests/
└── FluentResponse.Tests/ # xUnit + FluentAssertions (46 tests)
Package Structure (src/FluentResponse/)
| Directory | Contents |
|---|---|
Builders/ |
ResponseBuilder, ResponseBuilder<T>, PagedResponseBuilder<T> |
Configurations/ |
FluentResponseConfiguration |
Constants/ |
StatusCodes, DefaultMessages |
Exceptions/ |
FluentResponseException, NotFoundException, BusinessException, UnauthorizedException, ForbiddenException, ConflictException |
Extensions/ |
ServiceCollectionExtensions, ApplicationBuilderExtensions, HttpContextExtensions, ApiResponseExtensions |
Helpers/ |
TraceIdHelper, DateTimeHelper, PaginationHelper, ExecutionTimeTracker |
Interfaces/ |
IApiResponse, IApiResponse<T>, IPagedResponse<T>, IResponseBuilder, IFluentResponseFactory, IExceptionHandler, IResponseSerializer, IResponseTransformer |
Middleware/ |
ExceptionHandlingMiddleware, CorrelationIdMiddleware, ExecutionTimeMiddleware, LocalizationMiddleware |
Models/ |
ApiResponse, ApiResponse<T>, PagedResponse<T>, ResponseMetadata, ErrorResponse, ValidationResponse |
Options/ |
FluentResponseOptions, PaginationOptions, ResponseSerializationOptions |
Pagination/ |
PaginationMetadata |
Serialization/ |
FluentResponseJsonConverterFactory, ApiResponseConverter, ApiResponseConverter<T>, PagedResponseConverter<T> |
Services/ |
FluentResponseFactory, DefaultExceptionHandler |
Validators/ |
ValidationResultConverter, FluentValidationMiddleware |
Extensibility
Custom Exception Handler
public class MyExceptionHandler : IExceptionHandler
{
public bool CanHandle(Exception ex) => ex is MyDomainException;
public (int StatusCode, string Message, IEnumerable<string> Errors) HandleException(Exception ex)
{
var domainEx = (MyDomainException)ex;
return (domainEx.StatusCode, domainEx.Message, domainEx.ErrorDetails);
}
}
// Register
services.AddSingleton<IExceptionHandler, MyExceptionHandler>();
Custom Response Transformer
public class MyTransformer : IResponseTransformer
{
public object Transform(object response, IServiceProvider? sp = null)
{
// Add, remove, or modify fields before serialization
return response;
}
}
Custom Serializer
public class MySerializer : IResponseSerializer
{
public string Serialize<T>(T response) => MyCustomSerialization(response);
public T? Deserialize<T>(string json) => MyCustomDeserialization<T>(json);
}
Custom Middleware
The middleware pipeline is fully composable. Register your own middleware before or after the FluentResponse middleware:
app.UseMiddleware<MyCustomMiddleware>();
app.UseFluentResponseExceptionHandler();
Best Practices
Register middleware early — Call
UseFluentResponseExceptionHandler()before other middleware to catch all exceptions.Use typed responses — Prefer
FR.Success<T>(data)over the non-generic version to get strongly-typedDataproperties.Configure at startup — Set options once in
AddFluentResponse()— options are singletons for performance.Use custom exceptions — Throw
NotFoundExceptioninstead of manually returning 404; the middleware handles formatting automatically.Add correlation ID — Use
UseFluentResponseCorrelationId()for distributed tracing across microservices.Use Scalar/Swagger — Both sample projects include
AddOpenApi()+MapScalarApiReference()for API documentation.Run benchmarks — Before/after customization, run the benchmark suite to measure any performance impact.
FluentValidation first — Validate requests before they reach your business logic; use
ValidationResultConverterto return consistent error formats.
NuGet Package
<PackageReference Include="FluentResponse" Version="1.0.0" />
| Property | Value |
|---|---|
| Package ID | FluentResponse |
| Version | 1.0.0 |
| Target Frameworks | net8.0, net9.0, net10.0 |
| License | MIT |
| Symbols | .snupkg included |
| Source Link | Enabled |
| XML Docs | Included |
The package is generated automatically on build:
dotnet build src/FluentResponse
# Output: src/FluentResponse/bin/Debug/FluentResponse.1.0.0.nupkg
Dependencies
| Dependency | Type | Version |
|---|---|---|
Microsoft.AspNetCore.App |
Framework reference | Built-in to target framework |
FluentValidation |
NuGet | 11.x (optional) |
For sample projects (not required by the library):
| Package | Used For |
|---|---|
Scalar.AspNetCore |
API documentation UI |
Microsoft.AspNetCore.OpenApi |
OpenAPI document generation |
BenchmarkDotNet |
Performance benchmarks |
License
MIT — see LICENSE for details.
Contributing
Contributions, issues, and feature requests are welcome. Feel free to check the issues page.
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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
- FluentValidation (>= 11.11.0)
-
net8.0
- FluentValidation (>= 11.11.0)
-
net9.0
- FluentValidation (>= 11.11.0)
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 |
|---|---|---|
| 1.0.0 | 79 | 5/26/2026 |