When building APIs in .NET Core, maintaining a consistent response structure is extremely important. A common response format helps frontend developers handle API responses easily and improves debugging, scalability, and maintainability.
In this blog, we’ll create a reusable middleware in ASP.NET Core that automatically formats success and error responses in a standardized structure.
Why Use a Common Response Format?
Without a standard format, APIs may return inconsistent responses like:
Success Response
{
"message": "User fetched successfully",
"data": {
"id": 1,
"name": "John"
}
}
Error Response
{
"error": "User not found"
}
This inconsistency makes frontend handling difficult.
A standard response solves this problem.

Standard API Response Structure
Success Response
{
"success": true,
"statusCode": 200,
"message": "Data fetched successfully",
"data": {}
}
Failure Response
{
"success": false,
"statusCode": 500,
"message": "Something went wrong",
"errors": []
}
Benefits
- Consistent API responses
- Centralized error handling
- Cleaner controllers
- Better frontend integration
- Easier debugging
- Improved scalability
Step 1: Create Common Response Models
Create a folder:
Models/
ApiResponse.cs
// Generic API response model
namespace DemoAPI.Models
{
public class ApiResponse<T>
{
// Indicates success or failure
public bool Success { get; set; }
// HTTP status code
public int StatusCode { get; set; }
// Response message
public string Message { get; set; }
// Actual response data
public T Data { get; set; }
// Error details
public object Errors { get; set; }
}
}
Step 2: Create Exception Middleware
Create a folder:
Middleware/
ExceptionMiddleware.cs
using System.Net;
using System.Text.Json;
using DemoAPI.Models;
namespace DemoAPI.Middleware
{
public class ExceptionMiddleware
{
// Request delegate instance
private readonly RequestDelegate _next;
// Constructor
public ExceptionMiddleware(RequestDelegate next)
{
_next = next;
}
// Middleware execution method
public async Task InvokeAsync(HttpContext context)
{
try
{
// Move request to next middleware
await _next(context);
}
catch (Exception ex)
{
// Handle exception
await HandleExceptionAsync(context, ex);
}
}
// Exception handling method
private static Task HandleExceptionAsync(
HttpContext context,
Exception exception)
{
// Set response content type
context.Response.ContentType = "application/json";
// Set status code
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
// Create standardized response
var response = new ApiResponse<object>
{
Success = false,
StatusCode = context.Response.StatusCode,
Message = "Something went wrong",
Errors = exception.Message
};
// Convert object to JSON
var jsonResponse = JsonSerializer.Serialize(response);
// Return response
return context.Response.WriteAsync(jsonResponse);
}
}
}
Step 3: Create Response Helper
ResponseHelper.cs
using DemoAPI.Models;
namespace DemoAPI.Helpers
{
public static class ResponseHelper
{
// Success response
public static ApiResponse<T> Success<T>(
T data,
string message = "Success",
int statusCode = 200)
{
return new ApiResponse<T>
{
Success = true,
StatusCode = statusCode,
Message = message,
Data = data
};
}
// Error response
public static ApiResponse<object> Fail(
string message,
object errors = null,
int statusCode = 500)
{
return new ApiResponse<object>
{
Success = false,
StatusCode = statusCode,
Message = message,
Errors = errors
};
}
}
}
Step 4: Register Middleware
Program.cs (.NET 6 / .NET 7 / .NET 8)
using DemoAPI.Middleware;
var builder = WebApplication.CreateBuilder(args);
// Add services
builder.Services.AddControllers();
var app = builder.Build();
// Register custom exception middleware
app.UseMiddleware<ExceptionMiddleware>();
// Enable routing
app.MapControllers();
// Run application
app.Run();
Step 5: Use in Controller
UserController.cs
using Microsoft.AspNetCore.Mvc;
using DemoAPI.Helpers;
namespace DemoAPI.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class UserController : ControllerBase
{
// GET api/user
[HttpGet]
public IActionResult GetUser()
{
// Dummy user object
var user = new
{
Id = 1,
Name = "John Doe",
Email = "john@example.com"
};
// Return standardized success response
return Ok(
ResponseHelper.Success(
user,
"User fetched successfully"
)
);
}
}
}
Success Output
{
"success": true,
"statusCode": 200,
"message": "User fetched successfully",
"data": {
"id": 1,
"name": "John Doe",
"email": "john@example.com"
},
"errors": null
}
Error Output
{
"success": false,
"statusCode": 500,
"message": "Something went wrong",
"data": null,
"errors": "Object reference not set to an instance of an object"
}
Optional Improvements
You can enhance this middleware further by adding:
- Request tracking ID
- Timestamp
- Validation error handling
- Logging with Serilog
- Custom exception classes
- Localization support
- Pagination metadata
Example with Timestamp
public DateTime Timestamp { get; set; } = DateTime.UtcNow;
Recommended Folder Structure
DemoAPI/
│
├── Controllers/
│ └── UserController.cs
│
├── Helpers/
│ └── ResponseHelper.cs
│
├── Middleware/
│ └── ExceptionMiddleware.cs
│
├── Models/
│ └── ApiResponse.cs
│
├── Program.cs
│
└── appsettings.json
Best Practices
Use Proper HTTP Status Codes
| Status Code | Meaning |
|---|---|
| 200 | Success |
| 201 | Created |
| 400 | Bad Request |
| 401 | Unauthorized |
| 404 | Not Found |
| 500 | Internal Server Error |
Keep Response Structure Consistent
Never change response keys between APIs.
Centralize Error Handling
Avoid writing try-catch blocks in every controller.
Keep Coding.