Docker - Usando Docker-Compose, ASP .NET Core e SQL Server com VS Code
Hoje veremos como criar um arquivo Docker-Compose para uma aplicação ASP .NET Core que usa um banco de dados SQL Server usando o EF Core na abordagem Code-First. |
Se você esta chegando agora e não sabe o que é Docker sugiro que acompanhe o meu curso de introdução ao Docker nesta série de artigos: Docker - Uma introdução básica (Veja também o meu curso de Docker na Udemy)
Objetivos e requisitos
Vamos criar uma aplicação ASP .NET Core MVC para gerenciar informações sobre Livros como : Id e Titulo, fazendo um CRUD básico, e vamos conteinerizar esta aplicação MVC.
Nesta aplicação vamos usar o EF Core na abordagem Code-First e vamos usar o SQL Server em um contêiner. Assim teremos dois contêineres : a aplicação MVC que vai acessar o contêiner do banco de dados SQL Server.
Os recursos que iremos usar são todos grátis:
A primeira a coisa a fazer é instalar ao .NET Core SDK no seu ambiente, e a seguir os demais recursos. Após a instalação, para verificar o ambiente abra uma janela do PowerShell e digite o comando: dotnet --version
A seguir temos que
instalar a ferramenta dotnet-ef do EF Core
no ambiente do .NET Core. Para isso digite o seguinte comando :
dotnet tool install --global dotnet ef
A seguir para verificar digite o comando : dotnet ef
Criando a aplicação ASP .NET Core MVC
Para criar a aplicação ASP .NET Core MVC crie uma pasta onde deseja criar o projeto e a partir desta pasta digite o comando em uma janela de prompt de comandos:
dotnet new mvc -n "livraria" -lang "C#" -au none
Será criada a pasta livraria e nesta pasta teremos o projeto MVC criando.
Estando dentro da pasta do projeto vamos instalar os pacotes do EF Core digitando os comandos:
dotnet add package Microsoft.EntityFrameworkCore
--version 5.0.5
dotnet add package Microsoft.EntityFrameworkCore.SqlServer --version 5.0.5
dotnet add package Microsoft.EntityFrameworkCore.Design --version 5.0.5
Após esta instalação podemos verificar os pacotes incluídos no arquivo livraria.csproj :
Vamos buildar o projeto e executá-lo para ter certeza de que não existem erros:
dotnet build
dotnet run
Vamos criar agora o modelo de domínio da aplicação. Na pasta Models do projeto criado inclua a classe Livro:
public class
Livro { public int Id { get; set; } public string Titulo { get; set; } } |
A seguir nesta mesma pasta crie o arquivo de contexto ApplicationDbContext que herda da classe DbContext do EF Core:
using Microsoft.EntityFrameworkCore; namespace livraria.Models { public class ApplicationDbContext : DbContext { public DbSet<Livro> Livros { get; set; } public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } } } |
Vamos agora registrar o serviço do contexto no arquivo Startup:
public void ConfigureServices(IServiceCollection services)
{
var server = Configuration["DbServer"] ?? "localhost";
var port = Configuration["DbPort"] ?? "1433"; // Default SQL Server port
var user = Configuration["DbUser"] ?? "SA"; // Warning do not use the SA account
var password = Configuration["Password"] ?? "numsey#2021";
var database = Configuration["Database"] ?? "LivrosDb";
var connectionString = $"Server={server}, {port};Initial Catalog={database};User ID={user};Password={password}";
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
services.AddControllersWithViews();
}
|
Aqui além de registrar o serviço do contexto criamos a string de conexão definindo valores para as variáveis de ambiente para definir o nome do servidor, a porta , o usuário, a senha e o nome do banco de dados.
Vamos gerar o script de migração para o SQL Server digitando o comando:
dotnet ef database migrations add "Migracao_Inicial"
Não vamos executar o comando update-database pois não vamos aplicar o Migrations diretamente no SQL Server pois ainda não temos o banco de dados pois ele será inicializado em um contêiner Docker.
Vamos criar um serviço de migração de banco de dados, para executar a migração no contêiner quando da inicialização da nossa aplicação.
Para isso vamos criar uma pasta chamada Services, e dentro dessa pasta vamos criar a classe DatabaseManagementService :
Este método vai aplicar a migração inicial assim que aplicação for iniciada pela primeira vez.
A seguir vamos incluir a chamada deste método no método Configure da classe Startup :
Criando o Controller e as Views
Vamos criar o controlador LivrariaController na pasta Controllers e incluir dois métodos Action:
using System;
using System.Linq;
using System.Threading.Tasks;
using livraria.Models;
using Microsoft.AspNetCore.Mvc;
namespace livraria.Controllers
{
public class LivrariaController : Controller
{
private readonly ApplicationDbContext _context;
public LivrariaController(ApplicationDbContext context)
{
_context = context;
}
public IActionResult Index()
{
var books = _context.Livros.ToList();
return View(books);
}
[HttpGet]
public IActionResult Create()
{
return View();
}
[HttpPost]
public async Task<IActionResult> Create(Livro livro)
{
if (ModelState.IsValid)
{
try
{
_context.Livros.Add(livro);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
catch(Exception ex)
{
ModelState.AddModelError(string.Empty, $"Algo deu errado {ex.Message}");
}
}
ModelState.AddModelError(string.Empty, $"Algo deu errado, modelo inválido");
return View(livro);
}
}
}
|
Vamos agora criar as Views Index e Create na pasta /Views/Livraria do projeto:
1- Index.cshtml
@model IEnumerable<livraria.Models.Livro>
<h2>Livraria</h2>
<a asp-action="Delete" asp-route-id="@item.Id" title="Deleta" class="btn
btn-danger"> |
2- Create.cshtml
@model livraria.Models.Livro
<h2>Novo Livro</h2> <form asp-action="Create" method="post"> <div class="form-horizontal"> <hr /> <div class="form-group"> <label asp-for="Titulo" class="col-md-2 control-label"></label> <div class="col-md-10"> <input asp-for="Titulo" class="form-control" /> <span asp-validation-for="Titulo" class="text-danger"></span> </div> </div> <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <input type="submit" value="Create" class="btn btn-success" /> <a asp-action="Index" class="btn btn-info">Retornar</a> </div> </div> </div> </form> |
Pronto. Nossa aplicação ASP .NET Core MVC já esta pronta para exibir e incluir livros. Vamos fazer um último ajuste no arquivo Program do projeto incluindo o código abaixo para forçar a aplicação usar a porta 80.
... public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseUrls("http://*:80"); webBuilder.UseStartup<Startup>(); }); ... |
Vamos agora criar um arquivo Dockerfile no projeto com o código abaixo:
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build-env |
Código no VS Code :
Criando a imagem e contêiner para a aplicação ASP .NET Core
Já temos tudo pronto vamos criar a imagem para a nossa aplicação ASP .NET Core e a seguir criar e executar o respectivo contêiner.
Para criar a imagem digite o comando : docker build -t livraria .
Ao final podemos verificar a imagem criada usando o comando : docker images
Para criar e executar um contêiner com essa imagem vamos usar o comando:
docker run -p 8080:80 livraria
Mas o contêiner vai falhar na execução pois ainda não temos o banco de dados SQL Server pronto. Vamos fazer isso agora.
Vamos usar uma imagem do SQL Server a partir do repositório oficial do SQL Server no Docker Hub.
O nome da imagem : mcr.microsoft.com/mssql/server
A tag : 2017-latest-ubuntu
Assim para baixar esta imagem podemos usar o comando:
docker pull mcr.microsoft.com/mssql/server:2017-latest-ubuntu
Poderíamos tentar criar e executar um contêiner com essa imagem usando o seguinte comando:
docker run -d mcr.microsoft.com/mssql/server:2017-latest-ubuntu -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=numsey#2021' -e 'MSSQL_PID=Express' -p 1433:1433
Aqui estamos definindo as variáveis de ambiente:
'ACCEPT_EULA=Y'
'SA_PASSWORD=numsey#2021'
'MSSQL_PID=Express'
E o mapeamento da porta '1433:1433'.
E a seguir levantar o contêiner da aplicação ASP .NET Core e tentar conectar os dois contêineres mas isso iria falhar pois os contêineres não estão na mesma redes.
Orquestrando os contêineres com Docker-Compose
Para podermos acessar o contêiner do SQL Server a partir do contêiner da aplicação ASP .NET Core MVC vamos criar um arquivo Docker-Compose para orquestrar os dois contêineres.
Assim crie na pasta do projeto o arquivo docker-compose.yml com o seguinte código:
version: '3' services: mssql-server: image: mcr.microsoft.com/mssql/server:2017-latest-ubuntu environment: ACCEPT_EULA: "Y" SA_PASSWORD: "numsey#2021" MSSQL_PID: Express ports: - "1433:1433" volumes: - C:\dados\volumes\sqlserver:/var/opt/mssql/data livro-app: build: . environment: DbServer: "mssql-server" DbPort: "1433" DbUser: "SA" Password: "numsey#2021" Database: "LivrosDb" ports: - "8090:80" |
Código no VS Code :
Vamos entender o arquivo docker-compose.yml acima:
1- Criamos o serviço para o SQL Server definindo a imagem e os valores das variáveis de ambiente que serão usadas para preencher os dados da string de conexão e definimos a porta como 1433.
Aqui criamos um volume de forma a que quando o contêiner for destruído os dados estarão persistidos localmente na pasta indicada.
2- Criamos o serviço para a aplicação ASP .NET Core onde estamos dando um build no contexto do Dockerfile e definimos as variáveis de ambientes que para fazer a conexão com o SQL Server definindo o usuário, a senha e o banco de dados e definimos o mapeamento da porta de forma que vamos acessar a porta 8090.
Agora é só alegria....
Basta executar o comando : docker-compose up --build
E teremos os dois contêineres em execução:
Acessando o endereço http://localhost:8090 veremos nossa aplicação MCV e assim poderemos incluir livros no SQL Server e exibir os livros registrados conforme mostra a figura abaixo:
Vimos assim como criar um contêiner para uma aplicação ASP .NET Core e um contêiner para uma banco de dados SQL Server e fazer a comunicação entre os dois contêineres.
Se você der uma espiada na pasta c:\dados\volumes\sqlserver vai verificar a cópia do banco de dados usados pela aplicação ASP .NET Core MVC , graças ao volume que criamos. Assim o contêiner poderá ser destruído e os dados estarão preservados.
Pegue o projeto aqui : Livraria.zip (sem as referências)
"E em nada vos
espanteis dos que resistem, o que para eles, na verdade, é indício de perdição,
mas para vós de salvação, e isto de Deus. Porque a vós vos foi concedido, em
relação a Cristo, não somente crer nele, como também padecer por ele"
Filipenses 1:28,29
Referências:
C# - Tasks x Threads. Qual a diferença
VB .NET - Datas, horas: conceitos e operações
C# - Programação Assíncrona como : Asycn e Task
O tratamento de datas no VB.NET
C# - Obtendo a data e a hora por TimeZone
Docker - Uma introdução básica -
Docker - Criando um Contâiner para .NET Core ...
Docker - Trabalhando com contêineres
Motivos para usar Docker com .NET Core -
Docker - MiniCurso Básico