Publicando Aplicativos Web no Azure utilizando Identity SQLite e GitHub Actions
Publicar um aplicativo web no Azure pode ser simples e prático quando você usa a linha de comando do início ao fim. Neste artigo, vamos criar uma aplicação Razor Pages com autenticação via Identity, usar o SQLite como banco de dados e fazer o deploy em um App Service Linux, automatizado pelo GitHub Actions.
O código-fonte completo da aplicação que vamos criar está no repositório crisnordev/Article. Assim, você pode comparar cada etapa com o resultado final.
Visão geral do aplicativo web no Azure
O fluxo completo passa por cinco etapas. Primeiro, criamos o repositório remoto e o projeto local; em seguida, geramos as páginas do Identity e personalizamos o cadastro do usuário.
Depois disso, configuramos o envio de e-mails com o SendGrid. Por fim, criamos os recursos no portal e ligamos o repositório ao aplicativo web no Azure por meio de um workflow do GitHub Actions.
O projeto usa o .NET 7, versão disponível quando este fluxo foi montado. Por isso, os comandos e o workflow seguem essa versão do SDK.
Ferramentas necessárias
Para executar os passos deste artigo, você precisa das CLIs do .NET, do Git, do GitHub e do Azure instaladas. Eu uso o Windows Terminal, mas os comandos funcionam em qualquer terminal, no Windows, no Linux ou no Mac.
Para editar o código, você também vai precisar de uma IDE. Eu uso o Rider, porém o VS Code e o Visual Studio atendem igualmente bem.
Criando o repositório remoto no GitHub
Vamos começar pelo repositório remoto. No navegador, acesse o GitHub, abra a página dos seus repositórios e clique em "New".
Na página seguinte, dê um nome ao repositório e não adicione o arquivo .gitignore, porque vamos gerá-lo pela CLI. Em seguida, clique em "Create repository" e copie a URL que o GitHub disponibiliza.
Criando o aplicativo web local antes do Azure
Na sua máquina, é recomendado guardar o repositório local em uma pasta de caminho curto, como C:/dev/. Nela, vamos criar um projeto do tipo webapp chamado Article.
Depois, dentro da pasta do projeto, geramos o .gitignore, iniciamos o Git, renomeamos a branch principal para main e adicionamos a referência ao repositório remoto:
cd ./dev/
dotnet new webapp -o Article
cd ./Article/
dotnet new gitignore
git init
git branch -M main
# Troque a URL pela referência ao seu repositório remoto
git remote add origin https://github.com/crisnordev/Article.git
Lembre-se de trocar a URL do último comando pela do seu repositório. Caso contrário, o push vai falhar por falta de permissão.
Gerando as páginas do Identity
O ASP.NET Core Identity é a biblioteca do .NET que vai cuidar da autenticação dos usuários do nosso aplicativo web no Azure. Para gerar as páginas dele pela linha de comando, usamos o scaffolder do Identity.
Instalando o gerador e os pacotes
Primeiro, instale a ferramenta de geração de código do ASP.NET e os pacotes de que o projeto precisa:
dotnet tool install -g dotnet-aspnet-codegenerator
dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore
dotnet add package Microsoft.AspNetCore.Identity.UI
dotnet add package Microsoft.EntityFrameworkCore.Tools
Se o scaffolder retornar algum erro mais adiante, confira as versões dos pacotes. Afinal, elas precisam ser compatíveis com o framework alvo do projeto.
Executando o scaffolder
Agora, liste as opções do scaffolder e execute-o informando a classe do usuário, as páginas que devem ser criadas e o uso do SQLite:
dotnet aspnet-codegenerator identity --help
dotnet aspnet-codegenerator identity --userClass ApplicationUser --files "Account.Register;Account.Login;Account.Logout;Account.RegisterConfirmation;Account._StatusMessage;Account.Manage.Index" --useSqLite
Como o PowerShell usa o ponto e vírgula para separar comandos, a lista de arquivos fica entre aspas. Se você quiser gerar todas as páginas do Identity, basta remover o parâmetro --files.
Para exibir os botões de login, abra Pages/Shared/_Layout.cshtml. Logo depois do </ul> que fecha os links de navegação, adicione a linha <partial name="_LoginPartial" />.
Modelo de usuário personalizado
Junto com as páginas, o scaffolder criou a classe do modelo de usuário, que vai acompanhar o aplicativo web até o Azure. Como sugestão, eu movi a pasta Data e criei uma pasta Models na raiz do projeto para guardar a classe ApplicationUser.
Essa classe herda de IdentityUser. Além disso, vamos trocar o tipo do Id para Guid, que por padrão é string, e adicionar a propriedade Name:
// Usuário da aplicação com Id do tipo Guid e um nome personalizado
public class ApplicationUser : IdentityUser<Guid>
{
public string Name { get; set; }
}
Se você mudou o local do DataContext e do ApplicationUser, corrija os namespaces e os using das classes que os utilizam. Do contrário, o projeto não compila.
No DataContext, também precisamos referenciar o ApplicationUser e o IdentityRole, ambos usando Guid como tipo do Id:
// Contexto do Identity com usuário e papel usando Guid como chave
public class DataContext : IdentityDbContext<ApplicationUser, IdentityRole<Guid>, Guid>
{
public DataContext(DbContextOptions<DataContext> options) : base(options) { }
}
Personalizando o registro e o perfil
Com o modelo pronto, o próximo passo é receber o nome do usuário nas telas do aplicativo web antes de levá-lo ao Azure. Para isso, vamos alterar a página de registro e a página de gerenciamento da conta.
Página de registro
Em Areas/Identity/Pages/Account/Register.cshtml.cs, adicione a propriedade Name à classe InputModel:
[Required]
[Display(Name = "Name")]
public string Name { get; set; }
Em seguida, no método OnPostAsync(), logo após a linha var user = CreateUser();, adicione user.Name = Input.Name;. Dessa forma, o valor digitado vai para a propriedade do usuário.
Depois, em Register.cshtml, antes da <div> que recebe o Input.Email, inclua o campo do nome:
<div class="form-floating mb-3">
<input asp-for="Input.Name" class="form-control" autocomplete="name" aria-required="true" placeholder="Name"/>
<label asp-for="Input.Name">Name</label>
<span asp-validation-for="Input.Name" class="text-danger"></span>
</div>
Página de gerenciamento da conta
Em Account/Manage/Index.cshtml.cs, adicione também a propriedade Name ao InputModel. Então, no método LoadAsync(), preencha o nome ao criar o InputModel:
Input = new InputModel
{
Name = user.Name,
PhoneNumber = phoneNumber
};
No método OnPostAsync(), depois do if que altera o telefone, atualize o nome quando ele mudar:
// Atualiza o nome apenas se o usuário alterou o valor
if (Input.Name != user.Name)
{
user.Name = Input.Name;
}
await _userManager.UpdateAsync(user);
Em Account/Manage/Index.cshtml, depois da <div> do UserName, adicione o campo:
<div class="form-floating mb-3">
<input asp-for="Input.Name" class="form-control" placeholder="Please choose your name." />
<label asp-for="Input.Name" class="form-label"></label>
</div>
Por fim, em Pages/Shared/_LoginPartial.cshtml, troque o trecho Hello @UserManager.GetUserName(User)! por Hello @((await UserManager.GetUserAsync(User))?.Name)!. Assim, a saudação exibe o nome do usuário no lugar do e-mail.
Serviço de e-mail com SendGrid
Para que o aplicativo web no Azure envie os e-mails de confirmação, eu uso a API do SendGrid, que oferece uma conta gratuita. No portal dele, acesse "Settings", "API Keys" e "Create API Key".
Dê um nome à chave, escolha "Restricted Access" e leve a opção "Mail Send" até "Full Access". Em seguida, clique em "Create API Key".
O SendGrid mostra a chave uma única vez. Portanto, copie e guarde o valor em um local seguro, porque ninguém além de você deve ter acesso a ele.
Implementando o IEmailSender
Na aplicação, adicione o pacote com dotnet add package SendGrid. Depois, crie a pasta Services com a classe EmailSender:
public class EmailSender : IEmailSender
{
public async Task SendEmailAsync(string toEmail, string subject, string message)
{
// Interrompe o envio se a chave do SendGrid não foi configurada
if (string.IsNullOrEmpty(Configuration.SendGridKey.SendGridApiKey))
throw new Exception("SendGrid key is missing");
await Execute(Configuration.SendGridKey.SendGridApiKey, subject, message, toEmail);
}
public static async Task Execute(string apiKey, string subject, string message, string toEmail)
{
var client = new SendGridClient(apiKey);
var msg = new SendGridMessage
{
From = new EmailAddress("noreply@example.com", "Article App"),
Subject = subject,
PlainTextContent = message,
HtmlContent = message
};
msg.AddTo(new EmailAddress(toEmail));
// Desativa o rastreamento de cliques nos links do e-mail
msg.SetClickTracking(false, false);
await client.SendEmailAsync(msg);
}
}
O Identity usa injeção de dependência da interface IEmailSender para enviar e-mails. Por isso, no Program.cs, registre a implementação com builder.Services.AddSingleton<IEmailSender, EmailSender>();.
O ciclo de vida Singleton faz sentido aqui, já que a classe não guarda estado no aplicativo web, nem localmente nem no Azure. Com isso, sempre que o Identity precisar do IEmailSender, ele usa a implementação EmailSender.
Lendo a chave da API
No appsettings.json, adicione uma seção para a chave. O valor real ficará nas configurações do aplicativo web no Azure, então aqui usamos apenas um marcador:
{
"SendGridKey": {
"SendGridApiKey": "My_SendGrid_Key"
}
}
Na raiz do projeto, crie a classe Configuration para guardar essa chave:
public class Configuration
{
public static SendGridConfig SendGridKey { get; set; }
public class SendGridConfig
{
public string SendGridApiKey { get; set; }
}
}
Então, no Program.cs, leia a seção e atribua o valor à propriedade estática:
// Lê a seção SendGridKey e preenche a configuração estática
var sendGridKey = new Configuration.SendGridConfig();
app.Configuration.GetSection("SendGridKey").Bind(sendGridKey);
Configuration.SendGridKey = sendGridKey;
Por fim, ajuste o final do OnPostAsync() em Register.cshtml.cs e a página RegisterConfirmation, como mostra a imagem a seguir. Além disso, confirme que options.SignIn.RequireConfirmedAccount está como true na configuração do Identity.
Esse ajuste é importante porque, segundo a documentação do ASP.NET Core Identity, a página RegisterConfirmation padrão exibe o link de confirmação apenas para testes. Em produção, a verificação automática deve ficar desativada.
Criando o aplicativo web no Azure
Todo o deploy acontece no Microsoft Azure, a plataforma de nuvem da Microsoft, que permite criar uma conta gratuita. Ao criar a conta, o portal gera uma assinatura chamada "Azure Subscription 1".
Grupo de recursos do aplicativo web no Azure
Com a assinatura pronta, procure por "Grupos de recursos" na barra de busca e clique em "+ Criar". Selecione a assinatura, dê um nome ao grupo e escolha a região "(South America) Brazil South".
Em seguida, clique em "Revisar + criar" e depois em "Criar". O grupo de recursos vai reunir tudo o que o aplicativo web no Azure precisa.
Da mesma forma, abra "Serviços de Aplicativos" e clique em "+ Criar". Selecione a assinatura, o grupo de recursos criado e um nome único para a aplicação, já que ele vira parte da URL.
Em "Publicar", escolha "Código"; na "Pilha de runtime", selecione a versão do .NET do projeto; e em "Sistema operacional", escolha "Linux", na região "Brazil South". No plano Linux, clique em "Criar novo", dê um nome a ele e, em "Alterar tamanho", selecione o plano "Gratuito F1, 1 GB de memória".
Para finalizar, clique em "Revisar + criar" e em "Criar". Pronto, o aplicativo web no Azure já existe, embora ainda sem o nosso código.
Connection string e configurações do aplicativo web no Azure
Quando criamos o projeto com SQLite, a connection string aponta para a raiz da aplicação. No entanto, para publicar o aplicativo web no Azure, precisamos apontar o banco para a pasta wwwroot, senão a aplicação lança uma exceção ao tentar acessar o arquivo.
No portal, abra "Serviços de Aplicativos" e clique no nome da aplicação. Você pode parar a execução em "Parar" enquanto configura tudo.
No menu lateral, vá até "Configurações" e clique em "+ New connection string". Preencha o nome DefaultConnection, o valor DataSource=wwwroot/app.db;Cache=Shared e o tipo Custom.
No appsettings.json, a connection string fica apenas com um marcador:
{
"ConnectionStrings": {
"DefaultConnection": "My_Connection_String"
}
}
No Program.cs, use o mesmo nome ao ler a connection string:
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
Na mesma página, clique em "+ New application setting" para guardar a chave do SendGrid. Em um App Service Linux, o separador entre seção e item é o sublinhado duplo, então o nome fica SendGridKey__SendGridApiKey.
Segundo a documentação de configuração do App Service, o Linux não aceita dois-pontos nos nomes das configurações. Por isso, a chave hierárquica SendGridKey:SendGridApiKey vira SendGridKey__SendGridApiKey, e o valor é a chave criada no SendGrid, que nunca deve ser exposta.
Se você quer entender melhor como essas configurações sobrescrevem o appsettings.json, o artigo sobre deploy de ASP.NET com GitHub Actions e Microsoft Azure complementa este passo.
Deploy do aplicativo web no Azure com GitHub Actions
Com o ambiente pronto, falta automatizar a publicação. A partir daqui, toda mudança na branch main vai atualizar o aplicativo web no Azure automaticamente.
Centro de Implantação
No portal, abra a aplicação e selecione "Centro de Implantação" no menu lateral. Em "Source", escolha "GitHub" e autentique sua conta pelo próprio navegador.
Depois, selecione seu usuário em "Organization", o repositório da aplicação em "Repository" e a branch main em "Branch". Se quiser conferir o workflow antes, clique em "Preview file" e, em seguida, salve com "Save".
O Azure então cria a pasta .github/workflows no repositório remoto, com o arquivo main_{nome-do-app}.yml. Esse arquivo contém os passos que o GitHub Actions vai executar.
Secrets do repositório
No GitHub, abra "Settings", depois "Actions" e "General". Em "Actions permissions", marque "Allow all actions and reusable workflows" e salve.
Em seguida, abra "Secrets" e "Actions". Lá você encontra o secret AZUREAPPSERVICE_PUBLISHPROFILE_... que o Azure criou, e vamos adicionar mais dois.
O primeiro se chama AZURE_SQLITE_CONNECTION_STRING e recebe o valor DataSource=wwwroot/app.db;Cache=Shared. O segundo, AZURE_CREDENTIALS, depende de um service principal criado pela CLI do Azure:
# Troque AppName, SubscriptionId e ResourceGroupName pelos seus valores
az ad sp create-for-rbac --name "AppName" --role contributor --scopes /subscriptions/SubscriptionId/resourceGroups/ResourceGroupName --sdk-auth
Esse comando retorna um JSON, que você copia e cola como valor do secret. Vale saber que versões recentes da CLI marcam a opção --sdk-auth como obsoleta, e a Microsoft recomenda o login com OpenID Connect em projetos novos.
Ajustando o workflow do aplicativo web no Azure
Execute um git pull para trazer o workflow para o repositório local. Então, adicione a connection string, o login no Azure, a instalação do EF Tools e o database update:
name: Build and deploy ASP.NET Core app to Azure Web App - articleidentity
on:
push:
branches:
- main
workflow_dispatch:
env:
ConnectionStrings__DefaultConnection: ${{ secrets.AZURE_SQLITE_CONNECTION_STRING }}
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '7.x'
- name: Azure Login
uses: azure/login@v2
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Install EF Tools
run: dotnet tool install --global dotnet-ef
- name: Build with dotnet
run: dotnet build --configuration Release
- name: Update database
run: dotnet ef database update
- name: dotnet publish
run: dotnet publish -c Release -o ${{ env.DOTNET_ROOT }}/myapp
- name: Upload artifact for deployment job
uses: actions/upload-artifact@v4
with:
name: .net-app
path: ${{ env.DOTNET_ROOT }}/myapp
deploy:
runs-on: ubuntu-latest
needs: build
environment:
name: 'Production'
url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}
steps:
- name: Download artifact from build job
uses: actions/download-artifact@v4
with:
name: .net-app
- name: Deploy to Azure Web App
id: deploy-to-webapp
uses: azure/webapps-deploy@v3
with:
app-name: 'articleidentity'
slot-name: 'Production'
publish-profile: ${{ secrets.AZUREAPPSERVICE_PUBLISHPROFILE_XXXXXXXX }}
package: .
A variável ConnectionStrings__DefaultConnection faz o dotnet ef database update criar o banco em wwwroot/app.db no runner. Como o dotnet publish copia o conteúdo de wwwroot, o arquivo do banco segue no pacote publicado.
As versões das actions acima são as majors atuais, porque as versões antigas de upload-artifact e download-artifact foram descontinuadas pelo GitHub. Para aprofundar o assunto, veja também como criar workflows reutilizáveis no GitHub Actions.
Primeiro deploy do aplicativo web no Azure
Agora, crie a primeira migration com dotnet ef migrations add v1. Em seguida, troque temporariamente a connection string do appsettings.json por DataSource=wwwroot/app.db;Cache=Shared e execute dotnet ef database update.
Depois, desfaça essa alteração e volte para o marcador My_Connection_String. Assim, nenhum valor real fica no repositório.
No portal, abra a aplicação e clique em "Iniciar" para colocá-la em execução. Por fim, envie tudo ao repositório remoto para disparar o workflow:
git add .
git commit -m "Add app.db, migrations and workflow changes"
git push -u origin main
Você acompanha cada etapa na aba "Actions" do repositório. Quando o workflow terminar, o aplicativo web no Azure estará publicado, com deploy automatizado, e você pode testá-lo pela URL exibida em "Serviços de Aplicativos".
Cuidados com SQLite no aplicativo web no Azure
Esse fluxo é ótimo para estudos e protótipos, mas tem limites. O FAQ do App Service no Linux explica que o sistema de arquivos é um compartilhamento de rede montado, o que impede locks exclusivos no arquivo do SQLite.
Por isso, a Microsoft recomenda um banco gerenciado, como Azure SQL, MySQL ou PostgreSQL, para cargas reais. Além disso, como o arquivo app.db segue no pacote, um novo deploy pode sobrescrever o banco que já está em produção.
Em outras palavras, trate o SQLite aqui como um banco de demonstração. Quando o aplicativo web no Azure ganhar usuários de verdade, migre os dados para um serviço gerenciado.
Conclusão
Neste passo a passo, criamos uma aplicação Razor Pages com Identity, personalizamos o cadastro com o nome do usuário e configuramos a confirmação de conta por e-mail com o SendGrid. Depois, publicamos o aplicativo web no Azure em um App Service Linux gratuito.
Com o GitHub Actions, cada push na branch main gera um novo deploy, sem passos manuais. Portanto, você ganha um processo de entrega previsível desde o primeiro commit.
Se quiser se aprofundar em Azure, Git, GitHub e DevOps, o curso Fundamentos do Azure, Git, GitHub e DevOps cobre esses temas em detalhes.
FAQ
Por que usar dois sublinhados no nome da configuração no Azure?
No App Service Linux, os nomes de configuração não aceitam dois-pontos. Por isso, a hierarquia SendGridKey:SendGridApiKey é escrita como SendGridKey__SendGridApiKey.
Posso usar SQLite em produção no App Service?
Não é recomendado, porque o sistema de arquivos do App Service Linux é um compartilhamento de rede que impede locks exclusivos. Para produção, use Azure SQL, MySQL ou PostgreSQL.
Preciso do Visual Studio para gerar as páginas do Identity?
Não, o dotnet-aspnet-codegenerator gera as páginas pela linha de comando. Basta instalar a ferramenta e os pacotes de design no projeto.
Onde guardo a chave do SendGrid com segurança?
Guarde a chave nas configurações do App Service, e nunca no repositório. No appsettings.json, mantenha apenas um marcador.
A opção --sdk-auth ainda funciona?
Ela ainda existe, mas as versões recentes da CLI do Azure a marcam como obsoleta. Em projetos novos, prefira o login do GitHub Actions com OpenID Connect.