Desenvolvendo Controllers Robustos em ASP.NET: Implementando BaseController com BaseResponse para Respostas Eficientes
Akio Serizawa
Posted on December 9, 2023
A constante presença dos controllers no desenvolvimento web pode tornar o processo repetitivo e, muitas vezes, até monótono. Nos pegamos frequentemente criando as mesmas estruturas de forma padronizada, o que acaba causando aquela sensação de tédio. É hora de repensar isso e buscar maneiras mais empolgantes de encarar o desenvolvimento.
Então, percebendo essa necessidade no dia a dia, resolvi criar um BaseController
só na brincadeira mesmo e notei que deu uma bela ajuda. Com o tempo, fui aprimorando esse approach e também criei um BaseResponse
porque percebi a necessidade de expor as informações de forma mais eficiente para quem consome a minha API. É legal ver como essas mudanças podem fazer toda a diferença, trazendo mais vida e praticidade para o desenvolvimento.
Criando o BaseController
Quando eu criei o primeiro modelo do BaseController, a sacada era dar um basta na repetição irritante de código que sempre seguia o mesmo script. A ideia era simplificar as coisas, deixando apenas a chamada da interface que encaminharia para as minhas regras de negócios, junto com uma chamada de retorno. Com isso, cada endpoint se resumiria a apenas duas linhas, eliminando aquelas verificações chatas dentro do controller.
Perfeito! Então, começamos com o primeiro passo: a criação da nossa tão esperada BaseController, uma classe abstrata que herda da ControllerBase (a boa e velha classe padrão). É aqui que a mágica realmente começa!
Certo, então no segundo passo, entramos na fase de correção, onde lidamos com aquelas repetições irritantes nas nossas controllers. Como de praxe, eu curto adicionar alguns atributos para dar aquela organizada. O
ApiController
é quase como um must-have, né? Ele basicamente grita: "Ei, essa classe aqui é uma controller!". E o velho amigoAuthorize
, bom, esse é opcional, só se estivermos mexendo com autenticação. Mas, sabe como é, é sempre bom manter as coisas organizadas, não é mesmo?Agora, no terceiro passo, entramos na parte divertida. É hora de desenvolver os métodos de "Apresentação", por assim dizer. Esses serão os caras que chamaremos quando a coisa estiver rodando.
[ApiController]
[Authorize]
public abstract class BaseController : ControllerBase
{
protected ActionResult ResponseBase(HttpStatusCode statusCode, BasicReturn basicReturn, string responseMessage,
HttpStatusCode statusCodeError = HttpStatusCode.NotFound)
{
if (basicReturn.IsFailure)
{
basicReturn.Error.StatusCode = ((int)statusCodeError).ToString();
return StatusCode((int)statusCodeError, new BaseResponse<string>(basicReturn.Error));
}
return StatusCode((int)statusCode, new BaseResponse<string>(responseMessage));
}
protected ActionResult ResponseBase<T>(HttpStatusCode statusCode, BasicReturn<T> basicReturn,
HttpStatusCode statusCodeError = HttpStatusCode.NotFound)
{
if (basicReturn.IsFailure)
{
basicReturn.Error.StatusCode = ((int)statusCodeError).ToString();
return StatusCode((int)statusCodeError, new BaseResponse<string>(basicReturn.Error));
}
return StatusCode((int)statusCode, new BaseResponse<T>(basicReturn.Value));
}
}
A estrutura da classe se desdobra em arquivos cruciais, notadamente o BaseResponse
e BasicResult
.
Oque é BaseResponse?
Eu vejo o BaseResponse como a cara da minha API para quem a consome, um padrão de informações. Ele serve como uma estrutura puramente para apresentação, carregando consigo as propriedades Data
e Error
. Essa abordagem visa proporcionar uma experiência padronizada e compreensível para quem interage com a API, tornando a comunicação mais transparente por meio de dados consistentes e mensagens de erro claras.
Data
O Data ele seria a minha info de retorno como uma lista de clientes ou apenas uma mensagem de sucesso "Cadastro feito com sucesso"
Error
O Error ele é uma padronização de erros. Essa classe é totalmente personalizável, então fique à vontade para adicionar as informações que achar necessário. O objetivo é proporcionar flexibilidade para adaptar o Error de acordo com as necessidades específicas do projeto.
public class Error
{
public int StatusCode { get; set; }
public string Message { get; set; }
public Error(HttpStatusCode httpStatusCode, string message)
{
StatusCode = (int)httpStatusCode;
Message = messsage;
}
public static readonly Error None = new(HttpStatusCode.Continue, string.Empty);
}
Com essa implementação, agora temos a capacidade de personalizar erros de acordo com as necessidades específicas do contexto. Quando mencionamos "erro", não estamos restritos apenas a exceções; podemos tratá-lo como validações, situações em que nenhum registro é encontrado ou até mesmo lidar com exceções. Essa flexibilidade permite que adaptemos a representação de erro de forma mais precisa, tornando a resposta da API mais informativa e adaptada ao cenário em questão.
Criando o BaseResponse
Apenas para esclarecer alguns pontos, o nosso BaseResponse será uma classe genérica, o que significa que sempre será necessário fornecer algum tipo de informação ao utilizá-la. Essa característica oferece flexibilidade para adaptar o BaseResponse de acordo com as especificidades de cada contexto, garantindo que a classe atenda às necessidades específicas ao lidar com diferentes tipos de dados e situações.
public class BaseResponse<T>
{
public T Data { get; set; }
public Error Error { get; set; }
public BaseResponse(T data)
{
Data = data;
Error = Error.None;
}
public BaseResponse(Error error)
{
Data = default;
Error = error;
}
}
O que é BasicResult?
Quando falo sobre o BasicResult, enxergo-o como uma espécie de companheiro que vai percorrer toda a minha aplicação, trazendo os retornos(por exemplo de um services, usecase ou repository). A ideia é detalhar mais sobre ele em outro artigo, explorando quando é apropriado utilizá-lo e como implementá-lo de maneira eficaz para atender da melhor forma às necessidades específicas da aplicação. Essa abordagem visa estabelecer uma forma consistente e eficiente de lidar com resultados em diferentes partes da aplicação, proporcionando uma experiência mais homogênea e facilitando os retornos deixando tudo padrão. Nesse artigo utilizaremos uma forma mais simplificada para termos como base.
public class BasicResult
{
protected BasicResult(bool isSuccess, Error error)
{
IsSuccess = isSuccess;
Error = error;
}
public bool IsSuccess { get; }
public bool IsFailure => !IsSuccess;
public Error Error { get; }
public static BasicResultSuccess() => new(true, Error.None);
public static BasicResult<TValue> Success<TValue>(TValue value) => new(value, true, Error.None);
public static BasicResultFailure(Error error) => new(false, error);
public static BasicResult<TValue> Failure<TValue>(HttpStatusCode statusCode, List<ValidationFailure> errors) =>
new(default, false, ReturnErrorMessage(((int)statusCode).ToString(), errors));
public static BasicResult<TValue> Failure<TValue>(List<string> error) => new(default, false, error);
protected static BasicReturn<TValue> Create<TValue>(TValue? value) =>
value is not null ? Success(value) : Failure<TValue>(Error.None);
private static Error ReturnErrorMessage(string statusCode, List<ValidationFailure> errors)
{
string errorMessage = string.Join(Environment.NewLine,
errors.SelectMany(e => e.ErrorMessage!.Split('\n'))
.Select(s => s.Trim()));
return new Error(statusCode, errorMessage);
}
}
public class BasicResult<TValue>(TValue? value, bool isSucess, Error error)
: BasicReturn(isSucess, error)
{
public TValue Value { get; set; } = value!;
public static implicit operator BasicReturn<TValue>(TValue? value) => Create(value);
}
Implementando por completo
Com tudo isso desevolvido podemos agora implementar as nossas BaseController em uma Controller de exemplo
Route("api/v1/[controller]")]
public class ClientController : BaseController
{
private readonly IClientService _clientService;
public ClientController(IClientService clientService)
{
_clientService = clientService;
}
[HttpGet]
public async Task<ActionResult<BaseResponse<List<ClientDto>>>> Get()
{
BasicResturn<List<ClientDto>> clients = await _clientService.GetAll();
return ResponseBase(HttpStatusCode.OK, clients);
}
}
Conclusão
Escrevi este artigo no intuito de ajudar os Devs como eu, e também registrar mais um dos meus aprendizados neste vasto mundo que é a programação
Obrigado a todos que leram!!!
Posted on December 9, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.