FluentResponse.ApiWrapper 1.0.0

dotnet add package FluentResponse.ApiWrapper --version 1.0.0
                    
NuGet\Install-Package FluentResponse.ApiWrapper -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="FluentResponse.ApiWrapper" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="FluentResponse.ApiWrapper" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="FluentResponse.ApiWrapper" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add FluentResponse.ApiWrapper --version 1.0.0
                    
#r "nuget: FluentResponse.ApiWrapper, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package FluentResponse.ApiWrapper@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=FluentResponse.ApiWrapper&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=FluentResponse.ApiWrapper&version=1.0.0
                    
Install as a Cake Tool

FluentResponse

Enterprise API Response Wrapper Library for .NET

NuGet License .NET Benchmarks

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

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 FluentResponseOptions at runtime
  • Maps internal property names to configured names (e.g., SuccessisSuccess)
  • Conditionally includes/excludes fields based on IncludeTimestamp, IncludeTraceId, etc.
  • Writes directly to Utf8JsonWriter for maximum performance
  • Is automatically registered via IConfigureOptions<JsonOptions> when you call AddFluentResponse()

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

  1. Register middleware early — Call UseFluentResponseExceptionHandler() before other middleware to catch all exceptions.

  2. Use typed responses — Prefer FR.Success<T>(data) over the non-generic version to get strongly-typed Data properties.

  3. Configure at startup — Set options once in AddFluentResponse() — options are singletons for performance.

  4. Use custom exceptions — Throw NotFoundException instead of manually returning 404; the middleware handles formatting automatically.

  5. Add correlation ID — Use UseFluentResponseCorrelationId() for distributed tracing across microservices.

  6. Use Scalar/Swagger — Both sample projects include AddOpenApi() + MapScalarApiReference() for API documentation.

  7. Run benchmarks — Before/after customization, run the benchmark suite to measure any performance impact.

  8. FluentValidation first — Validate requests before they reach your business logic; use ValidationResultConverter to 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.

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request
Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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