ASP .NET Core - Transformando código síncrono para assíncrono - I


 Hoje veremos como transformar código síncrono para assíncrono na plataforma .NET.

Converter um código síncrono existente para um código assíncrono não é uma tarefa difícil e pode até se tornar bastante tedioso depois de fazer isso algumas vezes.

Uma das práticas recomendadas na programação assíncrona é usar async/await em todo o código, ou seja, você não deve misturar código síncrono e assíncrono sem considerar cuidadosamente as consequências. Em geral é uma má ideia bloquear o código assíncrono chamando Task.Wait ou Task.Result.

A abordagem recomendada é começar a conversão nas camadas de nível inferior e seguir em direção aos níveis de mais alto, ou seja, comece introduzindo async nos métodos da camada que acessam um banco de dados ou que acessam as APIs. Em seguida, introduza a programação assíncrona em seus métodos de serviço, depois a lógica de negócios e, por fim, a camada do usuário.

Se o seu código não tiver camadas bem definidas, você ainda pode fazer a conversão de síncrono para assíncrono usando async/await.  Vais ser um pouco mais difícil mas não é impossível. (Esse é mais um motivo para você desenvolver usando camadas.)

A primeira etapa é identificar a operação assíncrona de baixo nível a ser convertida. Qualquer operação baseada em E/S é candidata para assíncrono. Exemplos comuns são consultas e comandos de banco de dados, chamadas a Web APIs  e acesso ao sistema de arquivos.

Se a biblioteca subjacente tiver uma API pronta para async, tudo que você precisa fazer é adicionar um sufixo Async (ou sufixo TaskAsync) no nome do método síncrono.

Por exemplo, uma chamada do Entity Framework para First pode ser substituída por uma chamada para FirstAsync. Em alguns casos, você pode querer usar um tipo alternativo. Por exemplo, HttpClient é um substituto mais assíncrono para WebClient e HttpWebRequest.

Conversão usando async/await

Para mostrar um exemplo prático de conversão de um código síncrono para assíncrono vamos assumir que temos um projeto ASP .NET Core Web API onde temos implementado um repositório ProdutoRepository para acessar informações de Produtos que vai ser consumido por um controlador ProdutosController.

Nossa aplicação utiliza o Entity Framework Core e define uma classe de contexto ApplicationDbContext.

Assim em nossa aplicação Web API temos o código da implementação do padrão repositório definidos da seguinte forma:

1- interface IProdutoRepository

public interface IProdutoRepository
{
        IEnumerable<Produto> GetProdutos();
        Produto GetProduto(int? id);
        Produto Create(Produto produto);
        Produto Update(Produto produto);
        Produto Remove(Produto produto);
}

2- ProdutoRepository

using API_Conversao.Context;
using API_Conversao.Models;
using System.Collections.Generic;
using System.Linq;

namespace API_Conversao.Repositories
{
    public class ProdutoRepository : IProdutoRepository
    {
        private ApplicationDbContext _produtoContext;
        public ProdutoRepository(ApplicationDbContext context)
        {
            _produtoContext = context;
        }

        public Produto Create(Produto produto)
        {
            _produtoContext.Add(produto);
            _produtoContext.SaveChanges();
            return produto;
        }

        public Produto GetProduto(int? id)
        {
            return _produtoContext.Produtos.SingleOrDefault(p => p.Id == id);
        }

        public IEnumerable<Produto> GetProdutos()
        {
            return _produtoContext.Produtos.ToList();
        }

        public Produto Remove(Produto produto)
        {
            _produtoContext.Remove(produto);
            _produtoContext.SaveChanges();
            return produto;
        }

        public Produto Update(Produto produto)
        {
            _produtoContext.Update(produto);
            _produtoContext.SaveChanges();
            return produto;
        }
    }

Temos assim um repositório para realizar o CRUD básico usando o contexto (_produtoContext) do EF Core e foi implementado usando o código síncrono.

A seguir temos o código do controlador que usa este repositório :

using API_Conversao.Models;
using API_Conversao.Repositories;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;

namespace API_Conversao.Controllers
{
    [Route("api/[Controller]")]
    [ApiController]

    public class ProdutosController : Controller
    {
        private readonly IProdutoRepository _produtoRepository;

        public ProdutosController(IProdutoRepository produtoService)
        {
            _produtoRepository = produtoService;
        }

        // api/produtos
        [HttpGet]

        public ActionResult<IEnumerable<Produto>> Get()
        {
            var produtos = _produtoRepository.GetProdutos();
            return Ok(produtos);
        }

        [HttpGet("{id}", Name = "GetProduto")]
        public ActionResult<Produto> Get(int id)
        {
            var produto = _produtoRepository.GetProduto(id);

            if (produto == null)
            {
                return NotFound();
            }
            return Ok(produto);
        }

        [HttpPost]
        public ActionResult Post([FromBody] Produto produto)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            _produtoRepository.Create(produto);

            return new CreatedAtRouteResult("GetProduto",
                new { id = produto.Id }, produto);
        }

        [HttpPut("{id}")]
        public ActionResult Put(int id, [FromBody] Produto produto)
        {
            if (id != produto.Id)
            {
                return BadRequest();
            }

            _produtoRepository.Update(produto);

            return Ok(produto);
        }

        [HttpDelete("{id}")]
        public ActionResult<Produto> Delete(int id)
        {
            var produto = _produtoRepository.GetProduto(id);
            if (produto == null)
            {
                return NotFound();
            }
            _produtoRepository.Remove(produto);
            return Ok(produto);
        }
    }
}

O controlador de ProdutosController funciona perfeitamente em conjunto com o EF Core; todos os métodos HTTP descritos  se relacionam com os respectivos métodos EF Core DbContext por meio do repositório.

Os tipos ActionResult representam vários códigos de status HTTP e permite retornar um tipo ou um código de status.

Os métodos do controlador pode ter os seguintes códigos de status HTTP :

Assim temos o controlador usando métodos Action todos com a programação síncrona.

Nosso objetivo será converter tanto o repositório como o controlador usando a programação assíncrona.

Seguindo as recomendações vamos iniciar a conversão pela camada de nível inferior que é representando pelo nosso Repositório.

Assim na próxima parte do artigo vamos iniciar a conversão do código usando async/await.

'Porque em esperança fomos salvos. Ora a esperança que se vê não é esperança; porque o que alguém vê como o esperará ? Mas, se esperamos o que não vemos, com paciência o esperamos.'
Romanos 8:24,25

Referências:


José Carlos Macoratti