Uma estrutura de resposta padronizada é crucial para manutenção e clareza em APIs. Retornos inconsistentes dificultam a depuração e integração.
Um padrão comum é retornar JSON com uma estrutura fixa, independentemente de erros:
public class ApiResponse<T> where T : class
{
public ResponseStatusCode StatusCode { get; set; }
public string Message { get; set; }
public T Payload { get; set; }
}
Para simplificar a criação, adicione métodos estáticos de fábrica:
public class ApiResponse<T> where T : class
{
// ... propriedades ...
private ApiResponse(ResponseStatusCode status, string message, T data)
{
StatusCode = status;
Message = message;
Payload = data;
}
public static ApiResponse<T> Success(T data)
{
return new ApiResponse<T>(ResponseStatusCode.Success, "Operação concluída", data);
}
public static ApiResponse<T> Failure(string message, ResponseStatusCode code = ResponseStatusCode.Error)
{
return new ApiResponse<T>(code, message, default);
}
}
Exemplo de uso no controlador:
[HttpGet]
public ApiResponse<ProductDto> GetById(int id)
{
var product = _service.Find(id);
return ApiResponse<ProductDto>.Success(product);
}
Para capturar exceções não tratadas e formatá-las uniformemante, um middelware de tratamento global pode ser registrado.
// No Program.cs ou Startup.cs
app.UseMiddleware<UnifiedExceptionHandler>();
public class UnifiedExceptionHandler
{
private readonly RequestDelegate _next;
private readonly ILogger<UnifiedExceptionHandler> _logger;
public UnifiedExceptionHandler(RequestDelegate next, ILogger<UnifiedExceptionHandler> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception exception)
{
var errorResponse = HandleException(exception);
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
context.Response.ContentType = "application/json";
await context.Response.WriteAsJsonAsync(errorResponse);
}
}
private ApiResponse<object> HandleException(Exception exception)
{
// Registrar exceções não esperadas
if (!(exception is BusinessException))
{
_logger.LogError(exception, "Erro inesperado");
}
return ApiResponse<object>.Failure(exception.Message);
}
}
Exceções de negócio epseradas (ex: validação) podem lançar uma exceção customizada para evitar logging desnecessário.
public class BusinessException : Exception
{
public BusinessException(string message) : base(message) { }
}
Para validar dados de entrada, um filtro de ação pode ser criado para transformar erros de modelo na resposta padrão.
public class RequestValidationFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
var errors = context.ModelState
.Where(e => e.Value.Errors.Count > 0)
.SelectMany(e => e.Value.Errors
.Select(err => new { Field = e.Key, Error = err.ErrorMessage }))
.ToList();
var response = ApiResponse<object>.Failure("Dados de entrada inválidos", ResponseStatusCode.ValidationError);
response.Payload = errors;
context.Result = new ObjectResult(response) { StatusCode = (int)HttpStatusCode.BadRequest };
}
}
public void OnActionExecuted(ActionExecutedContext context) { }
}
Registrar o filtro globalmente na configuração dos serviços:
builder.Services.AddControllers(options =>
{
options.Filters.Add<RequestValidationFilter>();
});
Esta abordagem garante que os consumidores da API sempre recebam uma estrutura de resposta previsível, simplificando o tratamento de erros no lado do cliente.