Skip to content

Unified Response Format

MiCake provides a unified API response wrapping feature that automatically wraps controller return values into a standard format, making API responses more standardized and consistent.

MiCake’s standard response format contains three fields:

{
"code": "200",
"message": "Success",
"data": {
// The actual returned data
}
}

Field description:

  • code: the business status code (a string type)
  • message: the response message
  • data: the actual business data

MiCake enables response wrapping by default - no additional configuration is needed:

[ApiController]
[Route("api/[controller]")]
public class OrderController : ControllerBase
{
[HttpGet("{id}")]
public async Task<Order> GetOrder(int id)
{
var order = await _orderRepository.FindAsync(id);
return order;
}
}
// The actual response:
// {
// "code": "200",
// "message": "Success",
// "data": {
// "id": 1,
// "customerName": "Zhang San",
// "totalAmount": 999.00
// }
// }
[HttpGet]
public async Task<List<OrderDto>> GetOrders()
{
return await _orderService.GetAllOrders();
}
// Response:
// {
// "code": "200",
// "message": "Success",
// "data": [
// { "id": 1, "customerName": "Zhang San" },
// { "id": 2, "customerName": "Li Si" }
// ]
// }
[HttpPost]
public async Task<int> CreateOrder([FromBody] CreateOrderDto dto)
{
return await _orderService.CreateOrder(dto);
}
// Response:
// {
// "code": "200",
// "message": "Success",
// "data": 123
// }

Exceptions are automatically converted into the error response format:

[HttpGet("{id}")]
public async Task<Order> GetOrder(int id)
{
var order = await _orderRepository.FindAsync(id);
if (order == null)
throw new NotFoundException("Order", id);
return order;
}
// When the order does not exist:
// {
// "code": "NOT_FOUND",
// "message": "Order with id 123 was not found",
// "errors": null
// }
[HttpPost]
public async Task<int> CreateOrder([FromBody] CreateOrderDto dto)
{
if (!ModelState.IsValid)
{
var errors = ModelState.Values
.SelectMany(v => v.Errors)
.Select(e => e.ErrorMessage)
.ToList();
throw new ValidationException("Validation failed", errors);
}
return await _orderService.CreateOrder(dto);
}
// When validation fails:
// {
// "code": "VALIDATION_ERROR",
// "message": "Validation failed",
// "errors": [
// { "field": "CustomerName", "message": "The customer name cannot be empty" },
// { "field": "TotalAmount", "message": "The amount must be greater than zero" }
// ]
// }

If you need to customize the response, you can use the ApiResponse class:

using MiCake.AspNetCore.Responses;
[HttpPost]
public async Task<ApiResponse<int>> CreateOrder([FromBody] CreateOrderDto dto)
{
var orderId = await _orderService.CreateOrder(dto);
return new ApiResponse<int>
{
Code = "ORDER_CREATED",
Message = "The order was created successfully",
Data = orderId
};
}
// Response:
// {
// "code": "ORDER_CREATED",
// "message": "The order was created successfully",
// "data": 123
// }
[HttpPost]
public async Task<ApiResponse<bool>> ProcessOrder(int orderId)
{
try
{
await _orderService.ProcessOrder(orderId);
return new ApiResponse<bool>
{
Code = "SUCCESS",
Message = "The order was processed successfully",
Data = true
};
}
catch (BusinessException ex)
{
return new ApiResponse<bool>
{
Code = ex.Code ?? "BUSINESS_ERROR",
Message = ex.Message,
Data = false
};
}
}

For certain special endpoints, you may not want response wrapping:

using MiCake.AspNetCore.Responses;
[HttpGet("raw")]
[DisableResponseWrapper] // Disable response wrapping
public async Task<Order> GetRawOrder(int id)
{
return await _orderRepository.FindAsync(id);
}
// Returns the order object directly, unwrapped:
// {
// "id": 1,
// "customerName": "Zhang San",
// "totalAmount": 999.00
// }
[ApiController]
[Route("api/[controller]")]
[DisableResponseWrapper] // Disable response wrapping for the entire controller
public class RawDataController : ControllerBase
{
// None of the methods will have response wrapping
}

Configure the response wrapping options in Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
services.AddMiCakeWithDefault<MyAppModule, MyDbContext>(options =>
{
options.AspNetConfig = asp =>
{
// Configure the data wrapper
asp.DataWrapperOptions = wrapperOptions =>
{
// Set the default success code
wrapperOptions.DefaultSuccessCode = "0";
// Set the default success message
wrapperOptions.DefaultSuccessMessage = "Operation succeeded";
// Set the default error message
wrapperOptions.DefaultErrorMessage = "Operation failed";
};
};
}).Build();
}

If you need to fully customize the response format, you can implement the IResponseWrapper interface:

public class CustomResponseWrapper : IResponseWrapper
{
public string? Code { get; set; }
public string? Message { get; set; }
public object? Data { get; set; }
public DateTime Timestamp { get; set; } = DateTime.UtcNow;
public string? TraceId { get; set; }
}
// Register the custom wrapper
public class MyModule : MiCakeModule
{
public override void ConfigureServices(ModuleConfigServiceContext context)
{
context.Services.AddSingleton<IResponseWrapper, CustomResponseWrapper>();
base.ConfigureServices(context);
}
}

Use a unified response format throughout the entire application:

// ✅ Correct: let MiCake wrap the response automatically
[HttpGet("{id}")]
public async Task<Order> GetOrder(int id)
{
return await _orderRepository.FindAsync(id);
}
// ❌ Not recommended: manually build the response object
[HttpGet("{id}")]
public async Task<IActionResult> GetOrder(int id)
{
var order = await _orderRepository.FindAsync(id);
return Ok(new { code = "200", data = order });
}

MiCake automatically sets the correct HTTP status code:

// Success: 200 OK
[HttpGet("{id}")]
public async Task<Order> GetOrder(int id)
{
return await _orderRepository.FindAsync(id);
}
// Not found: 404 Not Found
[HttpGet("{id}")]
public async Task<Order> GetOrder(int id)
{
var order = await _orderRepository.FindAsync(id);
if (order == null)
throw new NotFoundException("Order", id);
return order;
}
// Validation error: 400 Bad Request
[HttpPost]
public async Task<int> CreateOrder([FromBody] CreateOrderDto dto)
{
if (!ModelState.IsValid)
throw new ValidationException("Validation failed");
return await _orderService.CreateOrder(dto);
}
// ✅ Correct: use meaningful error codes
throw new BusinessException("Insufficient stock", code: "INSUFFICIENT_STOCK");
throw new BusinessException("The order has been cancelled", code: "ORDER_CANCELLED");
// ❌ Wrong: use a generic error code
throw new Exception("Error");
// ✅ Correct: consistent return types
[HttpGet]
public async Task<List<OrderDto>> GetOrders()
{
return await _orderService.GetAllOrders();
}
// ❌ Not recommended: inconsistent return types
[HttpGet]
public async Task<IActionResult> GetOrders()
{
var orders = await _orderService.GetAllOrders();
if (!orders.Any())
return NotFound();
return Ok(orders);
}
  1. Enabled by default: MiCake enables response wrapping by default
  2. Automatic exception handling: exceptions are automatically converted into unified error responses
  3. Custom responses: you can customize the response using the ApiResponse class
  4. Disabling wrapping: use the [DisableResponseWrapper] attribute to disable wrapping
  5. HTTP status codes: MiCake automatically sets the correct HTTP status code based on the exception type