Desenvolvendo Controllers Robustos em ASP.NET: Implementando BaseController com BaseResponse para Respostas Eficientes

akioserizawa

Akio Serizawa

Posted on December 9, 2023

Desenvolvendo Controllers Robustos em ASP.NET: Implementando BaseController com BaseResponse para Respostas Eficientes

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 amigo Authorize, 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));
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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!!!

💖 💪 🙅 🚩
akioserizawa
Akio Serizawa

Posted on December 9, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related