FS.AspNetCore.ResponseWrapper
9.2.0
dotnet add package FS.AspNetCore.ResponseWrapper --version 9.2.0
NuGet\Install-Package FS.AspNetCore.ResponseWrapper -Version 9.2.0
<PackageReference Include="FS.AspNetCore.ResponseWrapper" Version="9.2.0" />
<PackageVersion Include="FS.AspNetCore.ResponseWrapper" Version="9.2.0" />
<PackageReference Include="FS.AspNetCore.ResponseWrapper" />
paket add FS.AspNetCore.ResponseWrapper --version 9.2.0
#r "nuget: FS.AspNetCore.ResponseWrapper, 9.2.0"
#:package FS.AspNetCore.ResponseWrapper@9.2.0
#addin nuget:?package=FS.AspNetCore.ResponseWrapper&version=9.2.0
#tool nuget:?package=FS.AspNetCore.ResponseWrapper&version=9.2.0
FS.AspNetCore.ResponseWrapper
Automatic API response wrapping with metadata injection for ASP.NET Core applications.
FS.AspNetCore.ResponseWrapper provides a consistent, standardized response format for your ASP.NET Core APIs with zero boilerplate code. Transform your raw controller responses into rich, metadata-enhanced API responses that include execution timing, pagination details, correlation IDs, status codes, and comprehensive error handling.
🎯 Why ResponseWrapper?
Building robust APIs means handling consistent response formats, error management, timing information, status codes, and pagination metadata. Without a standardized approach, you end up with:
- Inconsistent Response Formats: Different endpoints returning data in different structures
- Manual Error Handling: Writing repetitive error response logic in every controller
- Missing Metadata: No execution timing, correlation IDs, or request tracking
- Complex Pagination: Mixing business data with pagination information
- Status Code Confusion: Mixing HTTP status codes with application-specific workflow states
- Debugging Difficulties: Limited insight into request processing and performance
ResponseWrapper solves all these challenges by automatically wrapping your API responses with a consistent structure, comprehensive metadata, and intelligent error handling.
✨ Key Features
🔄 Automatic Response Wrapping
Transform any controller response into a standardized format without changing your existing code.
⏱️ Performance Monitoring
Built-in execution time tracking and database query statistics for performance optimization.
🔍 Request Tracing
Automatic correlation ID generation and tracking for distributed systems debugging.
📊 Application Status Codes
Intelligent status code extraction and promotion from response data, enabling complex workflow management and rich client-side conditional logic.
📄 Smart Pagination
Automatic detection and clean separation of pagination metadata from business data using duck typing.
🚨 Comprehensive Error Handling
Global exception handling with customizable error messages and consistent error response format.
🎛️ Flexible Configuration
Extensive configuration options for customizing behavior, excluding specific endpoints, and controlling metadata generation.
🦆 Duck Typing Support
Works with ANY pagination implementation - no need to change existing pagination interfaces.
📦 Installation
Install the package via NuGet Package Manager:
dotnet add package FS.AspNetCore.ResponseWrapper
Or via Package Manager Console:
Install-Package FS.AspNetCore.ResponseWrapper
Or add directly to your .csproj
file:
<PackageReference Include="FS.AspNetCore.ResponseWrapper" Version="9.1.0" />
🚀 Quick Start
Getting started with ResponseWrapper is incredibly simple. Add it to your ASP.NET Core application in just two steps:
Step 1: Register ResponseWrapper Services
In your Program.cs
file, add ResponseWrapper to your service collection:
using FS.AspNetCore.ResponseWrapper;
var builder = WebApplication.CreateBuilder(args);
// Add controllers
builder.Services.AddControllers();
// Add ResponseWrapper with default configuration
builder.Services.AddResponseWrapper();
var app = builder.Build();
// Configure the HTTP request pipeline
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Step 2: Add Global Exception Handling (Optional but Recommended)
For comprehensive error handling, add the middleware:
var app = builder.Build();
// Add ResponseWrapper middleware for global exception handling
app.UseMiddleware<GlobalExceptionHandlingMiddleware>();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
That's it! Your API responses are now automatically wrapped. Let's see what this means in practice.
📊 Before and After
Before: Raw Controller Response
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
[HttpGet]
public async Task<List<User>> GetUsers()
{
return await _userService.GetUsersAsync();
}
}
Raw Response:
[
{"id": 1, "name": "John Doe", "email": "john@example.com"},
{"id": 2, "name": "Jane Smith", "email": "jane@example.com"}
]
After: ResponseWrapper Enhanced
Same Controller Code - No changes needed!
Enhanced Response:
{
"success": true,
"data": [
{"id": 1, "name": "John Doe", "email": "john@example.com"},
{"id": 2, "name": "Jane Smith", "email": "jane@example.com"}
],
"message": null,
"statusCode": null,
"errors": [],
"metadata": {
"requestId": "550e8400-e29b-41d4-a716-446655440000",
"timestamp": "2024-01-15T10:30:45.123Z",
"executionTimeMs": 42,
"version": "1.0",
"correlationId": "abc123",
"path": "/api/users",
"method": "GET",
"additional": {
"requestSizeBytes": 0,
"clientIP": "192.168.1.1"
}
}
}
🎛️ Configuration Options
ResponseWrapper provides extensive configuration options to customize behavior according to your needs.
Basic Configuration
builder.Services.AddResponseWrapper(options =>
{
// Enable/disable execution time tracking
options.EnableExecutionTimeTracking = true;
// Enable/disable pagination metadata extraction
options.EnablePaginationMetadata = true;
// Enable/disable correlation ID tracking
options.EnableCorrelationId = true;
// Enable/disable database query statistics (requires EF interceptors)
options.EnableQueryStatistics = false;
// Control which responses to wrap
options.WrapSuccessResponses = true;
options.WrapErrorResponses = true;
// Exclude specific paths from wrapping
options.ExcludedPaths = new[] { "/health", "/metrics", "/swagger" };
// Exclude specific result types from wrapping
options.ExcludedTypes = new[] { typeof(FileResult), typeof(RedirectResult) };
});
Advanced Configuration with Custom Error Messages
builder.Services.AddResponseWrapper(
options =>
{
options.EnableExecutionTimeTracking = true;
options.EnableQueryStatistics = true;
options.ExcludedPaths = new[] { "/health", "/metrics" };
},
errorMessages =>
{
errorMessages.ValidationErrorMessage = "Please check your input and try again";
errorMessages.NotFoundErrorMessage = "The requested item could not be found";
errorMessages.UnauthorizedAccessMessage = "Please log in to access this resource";
errorMessages.ForbiddenAccessMessage = "You don't have permission to access this resource";
errorMessages.BusinessRuleViolationMessage = "This operation violates business rules";
errorMessages.ApplicationErrorMessage = "We're experiencing technical difficulties";
errorMessages.UnexpectedErrorMessage = "An unexpected error occurred. Our team has been notified";
});
Expert Configuration with Custom Dependencies
builder.Services.AddResponseWrapper<CustomApiLogger>(
dateTimeProvider: () => DateTime.UtcNow, // Custom time provider for testing
configureOptions: options =>
{
options.EnableExecutionTimeTracking = true;
options.EnableQueryStatistics = true;
},
configureErrorMessages: errorMessages =>
{
errorMessages.ValidationErrorMessage = "Custom validation message";
errorMessages.NotFoundErrorMessage = "Custom not found message";
});
🚨 Error Handling
ResponseWrapper provides comprehensive error handling that transforms exceptions into consistent API responses.
Built-in Exception Types
ResponseWrapper includes several exception types for common scenarios:
// For validation errors
throw new ValidationException(validationFailures);
// For missing resources
throw new NotFoundException("User", userId);
// For business rule violations
throw new BusinessException("Insufficient inventory for this order");
// For authorization failures
throw new ForbiddenAccessException("Access denied to this resource");
Exception Response Examples
Validation Error Response:
{
"success": false,
"data": null,
"message": "Please check your input and try again",
"statusCode": "VALIDATION_ERROR",
"errors": [
"Email is required",
"Password must be at least 8 characters"
],
"metadata": {
"requestId": "550e8400-e29b-41d4-a716-446655440000",
"timestamp": "2024-01-15T10:30:45.123Z",
"executionTimeMs": 15,
"path": "/api/users",
"method": "POST"
}
}
Not Found Error Response:
{
"success": false,
"data": null,
"message": "The requested item could not be found",
"statusCode": "NOT_FOUND",
"errors": ["User (123) was not found."],
"metadata": {
"requestId": "550e8400-e29b-41d4-a716-446655440001",
"timestamp": "2024-01-15T10:32:15.456Z",
"executionTimeMs": 8,
"path": "/api/users/123",
"method": "GET"
}
}
Custom Error Messages
Customize error messages for different environments or languages:
// English messages
errorMessages.ValidationErrorMessage = "Please check your input and try again";
errorMessages.NotFoundErrorMessage = "The requested item could not be found";
// Turkish messages
errorMessages.ValidationErrorMessage = "Lütfen girdiğiniz bilgileri kontrol edin";
errorMessages.NotFoundErrorMessage = "Aradığınız öğe bulunamadı";
// Developer-friendly messages for development environment
if (environment.IsDevelopment())
{
errorMessages.ValidationErrorMessage = "Validation failed - check detailed errors";
errorMessages.ApplicationErrorMessage = "Application error - check logs for stack trace";
}
📊 Application Status Codes
One of ResponseWrapper's powerful features is its intelligent application status code handling, which enables complex workflow management beyond simple success/failure indicators.
The Problem with HTTP Status Codes Alone
HTTP status codes are great for transport-level communication, but modern applications often need richer status information:
{
"success": true,
"data": {"userId": 123, "email": "user@example.com"},
"statusCode": "EMAIL_VERIFICATION_REQUIRED",
"message": "Account created successfully. Please verify your email."
}
This enables sophisticated client-side logic based on application state rather than just HTTP semantics.
Automatic Status Code Extraction
ResponseWrapper automatically extracts status codes from your response data when they implement the IHasStatusCode
interface:
// Your response DTO
public class UserRegistrationResult : IHasStatusCode
{
public int UserId { get; set; }
public string Email { get; set; }
public string StatusCode { get; set; } = "EMAIL_VERIFICATION_REQUIRED";
public string Message { get; set; } = "Please verify your email to complete registration";
}
// Your controller
[HttpPost("register")]
public async Task<UserRegistrationResult> RegisterUser(RegisterRequest request)
{
var result = await _userService.RegisterAsync(request);
return result; // StatusCode is automatically promoted to ApiResponse level
}
Resulting Response:
{
"success": true,
"data": {
"userId": 123,
"email": "user@example.com"
},
"statusCode": "EMAIL_VERIFICATION_REQUIRED",
"message": "Please verify your email to complete registration",
"metadata": { ... }
}
Notice how the status code and message are promoted to the top-level ApiResponse while the data remains clean and focused on business information.
Complex Workflow Examples
// Authentication workflow
public class LoginResult : IHasStatusCode
{
public string Token { get; set; }
public UserProfile User { get; set; }
public string StatusCode { get; set; }
public string Message { get; set; }
}
// Different status codes for different scenarios
switch (authResult.Status)
{
case AuthStatus.Success:
return new LoginResult
{
Token = token,
User = user,
StatusCode = "LOGIN_SUCCESS",
Message = "Welcome back!"
};
case AuthStatus.RequiresTwoFactor:
return new LoginResult
{
StatusCode = "TWO_FACTOR_REQUIRED",
Message = "Please enter your authentication code"
};
case AuthStatus.PasswordExpired:
return new LoginResult
{
StatusCode = "PASSWORD_EXPIRED",
Message = "Your password has expired. Please update it."
};
}
Client-Side Usage
The status codes enable sophisticated client-side logic:
const response = await api.post('/auth/login', credentials);
if (response.success) {
switch (response.statusCode) {
case 'LOGIN_SUCCESS':
router.push('/dashboard');
break;
case 'TWO_FACTOR_REQUIRED':
showTwoFactorDialog();
break;
case 'PASSWORD_EXPIRED':
router.push('/change-password');
break;
case 'EMAIL_VERIFICATION_REQUIRED':
showEmailVerificationPrompt();
break;
}
} else {
handleErrors(response.errors);
}
📄 Pagination Support
One of ResponseWrapper's most powerful features is its intelligent pagination handling using duck typing.
The Problem with Traditional Pagination
Most pagination libraries mix business data with pagination metadata:
{
"items": [...],
"page": 1,
"pageSize": 10,
"totalPages": 5,
"totalItems": 47
}
This creates inconsistent API responses and makes client development more complex.
ResponseWrapper's Solution: Clean Separation
ResponseWrapper automatically detects pagination objects and separates business data from pagination metadata:
Clean Response with Separated Metadata:
{
"success": true,
"data": {
"items": [
{"id": 1, "name": "Product 1"},
{"id": 2, "name": "Product 2"}
]
},
"metadata": {
"pagination": {
"page": 1,
"pageSize": 10,
"totalPages": 5,
"totalItems": 47,
"hasNextPage": true,
"hasPreviousPage": false
},
"requestId": "...",
"executionTimeMs": 25
}
}
Duck Typing: Works with ANY Pagination Library
ResponseWrapper uses duck typing, which means it works with ANY pagination implementation that has the required properties. You don't need to change your existing code!
Works with your existing pagination classes:
// Your existing pagination class - no changes needed!
public class MyCustomPagedResult<T>
{
public List<T> Items { get; set; }
public int Page { get; set; }
public int PageSize { get; set; }
public int TotalPages { get; set; }
public int TotalItems { get; set; }
public bool HasNextPage { get; set; }
public bool HasPreviousPage { get; set; }
}
// Your controller - no changes needed!
[HttpGet]
public async Task<MyCustomPagedResult<Product>> GetProducts()
{
return await _productService.GetPagedProductsAsync();
}
Also works with third-party libraries:
// Works with EntityFramework extensions
public async Task<PagedList<User>> GetUsers()
{
return await context.Users.ToPagedListAsync(page, pageSize);
}
// Works with any library that follows the pagination pattern
public async Task<PaginatedResult<Order>> GetOrders()
{
return await _orderService.GetPaginatedOrdersAsync();
}
Supported Pagination Patterns
ResponseWrapper automatically detects any object with these properties:
Items
(List<T>) - The business dataPage
(int) - Current page numberPageSize
(int) - Items per pageTotalPages
(int) - Total number of pagesTotalItems
(int) - Total number of itemsHasNextPage
(bool) - Whether next page existsHasPreviousPage
(bool) - Whether previous page exists
🎯 Real-World Usage Examples
E-Commerce API Example
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
private readonly IProductService _productService;
public ProductsController(IProductService productService)
{
_productService = productService;
}
// Simple product listing - automatically wrapped
[HttpGet]
public async Task<List<Product>> GetProducts()
{
return await _productService.GetActiveProductsAsync();
}
// Paginated products - pagination metadata automatically extracted
[HttpGet("paged")]
public async Task<PagedResult<Product>> GetPagedProducts(int page = 1, int pageSize = 10)
{
return await _productService.GetPagedProductsAsync(page, pageSize);
}
// Product creation with status codes - automatically wrapped with 201 status
[HttpPost]
public async Task<ProductCreationResult> CreateProduct(CreateProductRequest request)
{
// Validation happens automatically via ValidationException
if (!ModelState.IsValid)
{
var failures = ModelState.Values
.SelectMany(v => v.Errors)
.Select(e => new ValidationFailure("", e.ErrorMessage));
throw new ValidationException(failures);
}
return await _productService.CreateProductAsync(request);
}
// Product by ID - automatic 404 handling
[HttpGet("{id}")]
public async Task<Product> GetProduct(int id)
{
var product = await _productService.GetProductByIdAsync(id);
// This automatically becomes a 404 with proper error structure
if (product == null)
throw new NotFoundException("Product", id);
return product;
}
// File download - automatically excluded from wrapping
[HttpGet("{id}/image")]
public async Task<IActionResult> GetProductImage(int id)
{
var imageData = await _productService.GetProductImageAsync(id);
return new CustomExportResult(imageData, "product.jpg", "image/jpeg");
}
// Exclude specific endpoint from wrapping
[HttpGet("raw")]
[SkipApiResponseWrapper("Legacy endpoint for backward compatibility")]
public async Task<List<Product>> GetProductsRaw()
{
return await _productService.GetActiveProductsAsync();
}
}
User Management with Complex Workflows
// Response DTO with status codes
public class UserActivationResult : IHasStatusCode
{
public User User { get; set; }
public string StatusCode { get; set; }
public string Message { get; set; }
}
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
private readonly IUserService _userService;
public UsersController(IUserService userService)
{
_userService = userService;
}
[HttpPost("{id}/activate")]
public async Task<UserActivationResult> ActivateUser(int id)
{
var user = await _userService.GetUserByIdAsync(id);
if (user == null)
throw new NotFoundException("User", id);
// Business rule validation
if (user.IsActive)
{
return new UserActivationResult
{
User = user,
StatusCode = "ALREADY_ACTIVE",
Message = "User is already active"
};
}
if (user.IsSuspended)
{
return new UserActivationResult
{
StatusCode = "ACCOUNT_SUSPENDED",
Message = "Cannot activate suspended user. Please contact support."
};
}
var activatedUser = await _userService.ActivateUserAsync(id);
return new UserActivationResult
{
User = activatedUser,
StatusCode = "ACTIVATION_SUCCESS",
Message = "User activated successfully"
};
}
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteUser(int id)
{
// Authorization check
if (!User.IsInRole("Admin"))
throw new ForbiddenAccessException("Only administrators can delete users");
await _userService.DeleteUserAsync(id);
// Empty successful response
return Ok();
}
}
🔧 Advanced Scenarios
Environment-Specific Configuration
public void ConfigureServices(IServiceCollection services)
{
services.AddResponseWrapper(
options =>
{
options.EnableExecutionTimeTracking = true;
options.EnablePaginationMetadata = true;
// Enable detailed query stats only in development
options.EnableQueryStatistics = Environment.IsDevelopment();
// Different excluded paths per environment
if (Environment.IsProduction())
{
options.ExcludedPaths = new[] { "/health" };
}
else
{
options.ExcludedPaths = new[] { "/health", "/swagger", "/debug" };
}
},
errorMessages =>
{
if (Environment.IsDevelopment())
{
// Detailed messages for development
errorMessages.ValidationErrorMessage = "Validation failed - check detailed error list";
errorMessages.ApplicationErrorMessage = "Application error - check logs for full stack trace";
}
else
{
// User-friendly messages for production
errorMessages.ValidationErrorMessage = "Please check your information and try again";
errorMessages.ApplicationErrorMessage = "We're experiencing technical difficulties";
}
});
}
Integration with Entity Framework for Query Statistics
// Create a custom interceptor for query statistics
public class QueryStatisticsInterceptor : DbConnectionInterceptor
{
public override ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync(
DbCommand command,
CommandEventData eventData,
InterceptionResult<DbDataReader> result,
CancellationToken cancellationToken = default)
{
// Track query execution
var httpContext = GetHttpContext();
if (httpContext != null)
{
var queryStats = GetOrCreateQueryStats(httpContext);
queryStats["QueriesCount"] = (int)queryStats.GetValueOrDefault("QueriesCount", 0) + 1;
var executedQueries = (List<string>)queryStats.GetValueOrDefault("ExecutedQueries", new List<string>());
executedQueries.Add(command.CommandText);
queryStats["ExecutedQueries"] = executedQueries.ToArray();
}
return base.ReaderExecutingAsync(command, eventData, result, cancellationToken);
}
}
// Register the interceptor
builder.Services.AddDbContext<AppDbContext>(options =>
{
options.UseSqlServer(connectionString)
.AddInterceptors(new QueryStatisticsInterceptor());
});
Custom Logger Implementation
public class CustomApiLogger : ILogger<ApiResponseWrapperFilter>
{
private readonly ILogger<ApiResponseWrapperFilter> _innerLogger;
private readonly IMetrics _metrics;
public CustomApiLogger(ILogger<ApiResponseWrapperFilter> innerLogger, IMetrics metrics)
{
_innerLogger = innerLogger;
_metrics = metrics;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
// Custom logging logic
_innerLogger.Log(logLevel, eventId, state, exception, formatter);
// Send metrics
if (logLevel >= LogLevel.Warning)
{
_metrics.Counter("api_errors").Increment();
}
}
// Implement other ILogger methods...
}
// Register with ResponseWrapper
services.AddResponseWrapper<CustomApiLogger>(
() => DateTime.UtcNow,
options => options.EnableExecutionTimeTracking = true);
📋 Best Practices
1. Configure for Your Environment
// Development: Enable everything for debugging
if (env.IsDevelopment())
{
services.AddResponseWrapper(options =>
{
options.EnableExecutionTimeTracking = true;
options.EnableQueryStatistics = true;
options.EnablePaginationMetadata = true;
options.EnableCorrelationId = true;
});
}
// Production: Optimize for performance
if (env.IsProduction())
{
services.AddResponseWrapper(options =>
{
options.EnableExecutionTimeTracking = true;
options.EnableQueryStatistics = false; // Disable if not needed
options.EnablePaginationMetadata = true;
options.EnableCorrelationId = true;
options.ExcludedPaths = new[] { "/health", "/metrics" };
});
}
2. Use Specific Exception Types
// Good: Specific exception types
if (user == null)
throw new NotFoundException("User", id);
if (!user.IsActive)
throw new BusinessException("User account is not active");
if (!User.IsInRole("Admin"))
throw new ForbiddenAccessException("Administrator access required");
// Avoid: Generic exceptions
// throw new Exception("Something went wrong");
3. Leverage Application Status Codes
// Good: Rich status information
public class PaymentResult : IHasStatusCode
{
public string TransactionId { get; set; }
public decimal Amount { get; set; }
public string StatusCode { get; set; }
public string Message { get; set; }
}
// Different status codes for different outcomes
switch (paymentResponse.Status)
{
case PaymentStatus.Success:
return new PaymentResult { StatusCode = "PAYMENT_SUCCESS", ... };
case PaymentStatus.InsufficientFunds:
return new PaymentResult { StatusCode = "INSUFFICIENT_FUNDS", ... };
case PaymentStatus.CardExpired:
return new PaymentResult { StatusCode = "CARD_EXPIRED", ... };
}
4. Leverage Custom Error Messages
// Customize messages for better UX
errorMessages.ValidationErrorMessage = "Please review the highlighted fields and try again";
errorMessages.NotFoundErrorMessage = "We couldn't find what you're looking for";
errorMessages.UnauthorizedAccessMessage = "Please sign in to continue";
5. Exclude Appropriate Endpoints
options.ExcludedPaths = new[]
{
"/health", // Health checks
"/metrics", // Metrics endpoints
"/swagger", // API documentation
"/api/files", // File download endpoints
"/webhooks" // Webhook endpoints
};
6. Monitor Performance Impact
// Enable detailed monitoring in development
options.EnableQueryStatistics = Environment.IsDevelopment();
// Log execution times for performance monitoring
if (options.EnableExecutionTimeTracking)
{
// Monitor slow requests
if (executionTimeMs > 1000)
{
logger.LogWarning("Slow request detected: {RequestId} took {ExecutionTime}ms",
requestId, executionTimeMs);
}
}
🐛 Troubleshooting
Common Issues and Solutions
1. Responses Not Being Wrapped
Problem: Some controller responses are not wrapped.
Solutions:
- Ensure controllers have the
[ApiController]
attribute - Check that the endpoint is not in
ExcludedPaths
- Verify the result type is not in
ExcludedTypes
- Make sure
WrapSuccessResponses
is enabled
[ApiController] // Required for automatic wrapping
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
// This will be wrapped
[HttpGet]
public async Task<List<User>> GetUsers() { ... }
}
2. Pagination Not Detected
Problem: Pagination metadata is not extracted from custom pagination classes.
Solution: Ensure your pagination class has all required properties:
public class MyPagedResult<T>
{
// All these properties are required
public List<T> Items { get; set; } // Required
public int Page { get; set; } // Required
public int PageSize { get; set; } // Required
public int TotalPages { get; set; } // Required
public int TotalItems { get; set; } // Required
public bool HasNextPage { get; set; } // Required
public bool HasPreviousPage { get; set; } // Required
}
3. Status Codes Not Being Extracted
Problem: Application status codes are not appearing in responses.
Solution: Implement the IHasStatusCode
interface on your response DTOs:
public class MyResponse : IHasStatusCode
{
public string Data { get; set; }
public string StatusCode { get; set; } // This will be promoted to ApiResponse level
public string Message { get; set; } // This will also be promoted
}
4. Error Messages Not Showing
Problem: Custom error messages are not displayed.
Solutions:
- Ensure
GlobalExceptionHandlingMiddleware
is registered - Add the middleware early in the pipeline
- Check that
WrapErrorResponses
is enabled
var app = builder.Build();
// Add this EARLY in the pipeline
app.UseMiddleware<GlobalExceptionHandlingMiddleware>();
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
5. Performance Issues
Problem: API responses are slower after adding ResponseWrapper.
Solutions:
- Disable query statistics if not needed:
options.EnableQueryStatistics = false
- Exclude high-frequency endpoints:
options.ExcludedPaths = new[] { "/api/high-frequency" }
- Disable execution time tracking for production:
options.EnableExecutionTimeTracking = false
6. File Downloads Being Wrapped
Problem: File download endpoints are returning JSON instead of files.
Solutions:
- Use
ISpecialResult
interface for custom file results - Add file result types to
ExcludedTypes
- Use the
[SkipApiResponseWrapper]
attribute
// Option 1: Use ISpecialResult
public class FileDownloadResult : ActionResult, ISpecialResult { ... }
// Option 2: Exclude file types
options.ExcludedTypes = new[] { typeof(FileResult), typeof(FileStreamResult) };
// Option 3: Skip specific endpoints
[SkipApiResponseWrapper("File download endpoint")]
public async Task<IActionResult> DownloadFile(int id) { ... }
🤝 Contributing
We welcome contributions to FS.AspNetCore.ResponseWrapper! Here's how you can help:
Reporting Issues
- Check existing issues to avoid duplicates
- Provide detailed reproduction steps
- Include relevant code examples
- Specify your .NET and ASP.NET Core versions
Submitting Pull Requests
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature
) - Make your changes with tests
- Ensure all tests pass
- Update documentation if needed
- Commit your changes (
git commit -m 'Add amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
Development Setup
# Clone the repository
git clone https://github.com/furkansarikaya/FS.AspNetCore.ResponseWrapper.git
# Navigate to the project directory
cd FS.AspNetCore.ResponseWrapper
# Restore dependencies
dotnet restore
# Build the project
dotnet build
# Run tests
dotnet test
Coding Standards
- Follow Microsoft's C# coding conventions
- Add comprehensive XML documentation for public APIs
- Write unit tests for new features
- Ensure backward compatibility when possible
📜 License
This project is licensed under the MIT License - see the LICENSE file for details.
🙏 Acknowledgments
- Microsoft for the excellent ASP.NET Core framework
- The open-source community for inspiration and feedback
- All contributors who help make this project better
📞 Support
- GitHub Issues: Report bugs or request features
- NuGet Package: FS.AspNetCore.ResponseWrapper
- Documentation: GitHub Repository
Made with ❤️ by Furkan Sarıkaya
Transform your ASP.NET Core APIs with consistent, metadata-rich responses and intelligent status code management. Install FS.AspNetCore.ResponseWrapper today and experience the difference!
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | 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. |
-
net9.0
- FluentValidation (>= 12.0.0)
- Microsoft.EntityFrameworkCore.Abstractions (>= 9.0.0)
- Microsoft.EntityFrameworkCore.Relational (>= 9.0.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 9.2.0: Added intelligent application status code extraction and promotion feature. ResponseWrapper now automatically detects and promotes status codes from response data that implements IHasStatusCode interface, enabling complex workflow management and rich client-side conditional logic. Enhanced error handling with automatic error code extraction from all exception types.