ASP .NET Core - Web API com MySQL em um Contêiner Docker - I


Hoje veremos como criar uma Web API ASP.Net Core acessando o MySQL e executar a aplicação em um contêiner Docker.

Hoje vamos iniciar uma série de 3 artigos que aborda a utilização da criação de contêineres Docker para aplicações ASP .NET Core e para o MySql..

Objetivos e requisitos

Vamos criar uma aplicação ASP .NET Core WEb API para gerenciar informações sobre Produtos como : Id, Name, Description e Price, fazendo um CRUD básico no MySQL e vamos conteinerizar esta API.

Nesta aplicação vamos usar o EF Core e  o banco de dados MySQL em um contêiner. Assim teremos dois contêineres : a aplicação Web API que vai acessar o contêiner do banco de dados MySQL.

Os recursos que iremos usar são todos grátis:

A primeira a coisa a fazer é instalar ao .NET Core SDK 5.0 no seu ambiente.

Após a instalação, para verificar o ambiente abra uma janela do PowerShell e digite o comando:  dotnet --version

Com o .NET Core SDK instalado você pode instalar o Visual Studio 2019 Community que é grátis e a seguir instalar o Docker Desktop for Windows que também é grátis e iniciar o desenvolvimento.

Criando a Web API ASP .NET Core

Vamos criar uma solução em branco usando o template Blank Solution com o nome ApiMySqlDocker:

A seguir no menu File selecione Add-> New Project  e selecione o template ASP .NET Core Web API e clique em Next;

Informe o nome ApiMySqlDocker e clique em Next;

A seguir selecione o Target Framework, Authentication Type e demais configurações conforme mostrada na figura:

Note que não habilitamos o HTTPS para evitar ter que habilitar um certificado no contêiner.

Agora vamos remover os arquivos WeatherForecastController.cs e WeatherForecast.cs do projeto criado.

A seguir inclua no projeto uma referência ao pacote Pomelo.EntityFrameworkCore.MySql abrindo a janela Package Manager Console e digitando o comando:

install-package Pomelo.EntityFrameworkCore.MySql -Version 5.0.0

Como vamos gerar o Controlador ProductsController a partir do Scaffolding precisamos instalar também o pacote :

install-package Microsoft.EntityFrameworkCore.Design -Version 5.0.6

Crie uma pasta Entities no projeto e a seguir inclua nesta pasta a classe Product que representa a nossa entidade de domínio:

    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public decimal Price { get; set; }
        public string Description { get; set; }
    }

Crie uma pasta DataContext e inclua nesta pasta a classe ApplicationDbContext :

using EFCoreSqlServer.Entities;
using Microsoft.EntityFrameworkCore;

namespace EFCoreSqlServer.DataContext
{
    public class ApplicationDbContext : DbContext
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
        {
            this.Database.EnsureCreated();
        }

        public DbSet<Product> Products { get; set; }
    }
}

Definimos o arquivo de contexto que realiza o mapeamento ORM e permite definir as opções do contexto informando a string de conexão e o provedor do banco de dados.

O método Database.EnsureCreated() garante que o banco de dados para o contexto exista. Se ele existir, nenhuma ação será realizada. Se ele não existir, o banco de dados e todo o seu esquema serão criados.

Configurando o serviço e a string de conexão

Vamos registrar o contexto como um serviço no arquivo Startup e definir o provedor e a string de conexão com o MySQL que iremos usar.

No método ConfigureServices inclua o código a seguir:

public void ConfigureServices(IServiceCollection services)
 {
            string mySqlConnectionStr = Configuration.GetConnectionString("DefaultConnection");

            services.AddDbContextPool<ApplicationDbContext>(options =>
                options.UseMySql(mySqlConnectionStr,
                        ServerVersion.AutoDetect(mySqlConnectionStr)));

            services.AddControllers();
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "EFCoreSqlServer", Version = "v1" });
            });
}

Agora abra o arquivo appsettings.json e crie a seção ConnectionStrings definindo a string de conexão com o nome do banco de dados, o usuário e a senha para acessar o SQL Server.

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=localhost;DataBase=ProductsDB;Uid=user;Pwd=numsey"
  },
 
"Loggi
ng": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

Estou assumindo que o banco de dados e as tabelas já existam, no entanto para criar o banco e as tabelas basta aplicar o Migrations usando o comando 

add-migration Inicial

A seguir basta verificar o script gerado e executar o comando para aplicar o script e gerar o banco e as tabelas:

update-database

Criando a API - ProductsController

Como nosso objetivo é mostrar a conteinerização da API e do MySQL eu vou gerar o controlador de forma automática usando a ferramenta Scaffold do Visual Studio.

Para criar o controlador clique com o botão direito do mouse sobre a pasta Controllers e selecione a opção Add -> Controller;

Na janela - Add New Scaffolded Item - selecione API e a seguir a opção - API Controller with actions, using Entity Framework e clique em Add;

 Na próxima janela informe :

Clique em Add e aguarde...

Será gerado o arquivo ProductsController contendo os métodos Action para as operações CRUD usando os verbos Http : Get, Post, Put e Delete.

using ApiMySqlDocker.DataContext;
using ApiMySqlDocker.Entities;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace ApiMySqlDocker.Controllers
{
    [Route("api/[controller]")]
    [ApiController]

    public class ProductsController : ControllerBase
    {
        private readonly ApplicationDbContext _context;

        public ProductsController(ApplicationDbContext context)
        {
            _context = context;
        }

        [HttpGet]
        public async Task<ActionResult<IEnumerable<Product>>> GetProducts()
        {
            return await _context.Products.ToListAsync();
        }

        [HttpGet("{id}")]
        public async Task<ActionResult<Product>> GetProduct(int id)
        {
            var product = await _context.Products.FindAsync(id);

            if (product == null)
            {
                return NotFound();
            }

            return product;
        }

        [HttpPut("{id}")]
        public async Task<IActionResult> PutProduct(int id, Product product)
        {
            if (id != product.Id)
            {
                return BadRequest();
            }

            _context.Entry(product).State = EntityState.Modified;

            try
            {
                await _context.SaveChangesAsync();
            }
            catch (DbUpdateConcurrencyException)
            {
                if (!ProductExists(id))
                {
                    return NotFound();
                }
                else
                {
                    throw;
                }
            }

            return NoContent();
        }

        [HttpPost]
        public async Task<ActionResult<Product>> PostProduct(Product product)
        {
            _context.Products.Add(product);
            await _context.SaveChangesAsync();

            return CreatedAtAction("GetProduct", new { id = product.Id }, product);
        }

        [HttpDelete("{id}")]
        public async Task<IActionResult> DeleteProduct(int id)
        {
            var product = await _context.Products.FindAsync(id);
            if (product == null)
            {
                return NotFound();
            }

            _context.Products.Remove(product);
            await _context.SaveChangesAsync();

            return NoContent();
        }

        private bool ProductExists(int id)
        {
            return _context.Products.Any(e => e.Id == id);
        }
    }
}

Com isso foram criados os métodos que definem os endpoints da API e realizam um CRUD onde podemos incluir, consultar, excluir e editar produtos.

O código gerado faz a injeção do DbContext representando pela classe ApplicationDbContext e assim não vamos criar um repositório.

Executando o projeto neste momento temos nossa API Products exibindo os endpoints definidos na interface do Swagger.

A seguir criamos os métodos que definem os endpoints da API e realizam um CRUD onde podemos incluir, consultar, excluir e editar produtos.

Temos assim nossa API criada e funcionando acessando o banco de dados MySQL local e agora precisamos criar o contêiner usando a imagem do MySQL a partir do Docker Hub e a seguir criar o contêiner da aplicação API e acessar o contêiner do MySQL.

Na próxima parte do artigo vamos criar o Dockerfile e o arquivo de composição usando o Docker Compose via integração com o Visual Studio 2019.

"Vigiai, estai firmes na fé; portai-vos varonilmente, e fortalecei-vos.
Todas as vossas coisas sejam feitas com amor."
1 Coríntios 16:13,14

Referências:


José Carlos Macoratti