Deploying to Azure App Service
Um portal Power Portals Pro é um único aplicativo Core ASP.NET qual for a pilha escolhida, então implantá-lo é algo comum dotnet publish para um App Service comum. Esta página aborda os recursos a serem criados, as configurações que o portal lê, o que sugerimos como padrão e os poucos passos específicos do Power Portals Pro em vez do ASP.NET Core.
O que você está implantando
Ambos os templates produzem uma aplicação web e uma saída de publicação. Não há um host front-end separado para montar, nenhum recurso de site estático e nenhum segundo App Service:
- Blazor — o projeto servidor hospeda o app. Sob
ServerouAutointeratividade, ele também atende ao circuito Blazor SignalR; sobWebAssemblyele serve o pacote cliente e os/api/*endpoints que o cliente chama. - React —
dotnet publishroda a build Vite para o projeto cliente e adist/faz etapas no hostwwwroot, então um ASP.NET host Core serve o SPA e a API a partir de uma única origem. Nada extra para conectar.
Nota
Como o react publish shells para npm, o Node precisa ser instalado em qualquer máquina que rode
dotnet publish— seu laptop ou o agente de compilação. O cliente do template pede o Node^20.19.0 || >=22.12.0. O próprio serviço de aplicativos nunca precisa do Node; ele só vê a saída construída.
Padrões sugeridos
Pontos de partida, não regras — mas são esses os que escolheríamos para um novo portal de produção.
- Sistema operacional: Linux. Mais barato por nível, mais rápido para começar, e mantém o IIS fora
web.configde cena completamente. O Windows funciona bem se sua organização padronizar com ele. - Pilha de runtime: .NET 10. Os templates têm como alvo
net10.0. Publicar dependente do framework e deixar o App Service fornecer o runtime. - Plano: B1 — para todo ambiente, incluindo produção. B1 é o nível mais barato que pode se manter aquecido, e escalar depois é um comando sem redeploy e sem tempo de inatividade significativo. Providenciar algo maior "porque é produção" inicia uma conta que ninguém revisa; comece aqui e suba quando uma medição, ou necessidade de slots de implantação, exigir. O que você abre mão até lá: sem slots de implantação e sem autoscale (ambos começam no Standard — e compare o S1 com o P0v3, que geralmente é mais barato no Linux e tem mais memória).
- Sempre ligado: ligado. O App Service descarrega um app ocioso, e um início a frio significa reconectar ao Dataverse e reconstruir os caches de metadados e localização. O primeiro visitante após um período de silêncio paga por tudo.
- Somente HTTPS: Ativado, TLS 1.2 mínimo, HTTP/2 ativado. O portal emite cookies de autenticação; não há motivo para aceitar HTTP simples.
- Soques web: Ligado para Blazor Server ou Auto. Desligado é o padrão do App Service, e sem ele o circuito silenciosamente volta a um longo sondamento.
- Afinidade de sessão: Ativada para Blazor Server ou Auto — um circuito pertence a uma única instância. Desligue para portais React e Blazor WebAssembly: eles são stateless sobre HTTP, e a afinidade só desequilibra suas instâncias.
- Segredos no Key Vault, referenciados nas configurações do app. Dê ao App Service uma identidade gerenciada e aponte a configuração para o vault, assim o segredo nunca fica na blade de configuração ou em um script de implantação.
- Chaves de Proteção de Dados no Blob Storage assim que você usa slots de implantação ou escala além de uma instância. O chaveiro padrão é por slot, e uma troca faz todo mundo sair do clube.
WEBSITE_RUN_FROM_PACKAGE=1: opcional, e vale a pena. Implantações tornam-se atômicas e o diretório de conteúdo somente leitura. O portal só lê da raiz de conteúdo, então nada quebra — faz upload do buffer pela pasta temporária do sistema, que permanece gravável.- Insights de Aplicação: em. O framework registra rejeições de licenças e falhas do Dataverse através
ILoggerde . Sem um sink, você está lendo o fluxo de log manualmente exatamente no momento em que preferiria não estar.
1. Criar o Serviço de Aplicativo
Três comandos. Substitua seus próprios nomes — myportal é usado ao longo desta página.
az group create --name myportal-rg --location eastus
az appservice plan create --name myportal-plan --resource-group myportal-rg --sku B1 --is-linux
az webapp create --name myportal --resource-group myportal-rg --plan myportal-plan --runtime "DOTNETCORE:10.0"
O nome do aplicativo web se torna https://myportal.azurewebsites.net e precisa ser globalmente único. Escolha deliberadamente: a menos que você coloque um domínio personalizado na frente, é a URL à qual sua chave de licença fica vinculada.
2. Defina as opções da plataforma
Esses são os que têm os padrões errados para um portal.
az webapp config set --name myportal --resource-group myportal-rg --always-on true --min-tls-version 1.2 --http20-enabled true --web-sockets-enabled true
az webapp update --name myportal --resource-group myportal-rg --https-only true --client-affinity-enabled true
--web-sockets-enabled true— necessário para Blazor Server e Auto. Inofensivo nos portais React e WebAssembly, que nunca abrem um circuito.--client-affinity-enabled— deixar paratrueBlazor Server e Auto; passarfalsepor React e Blazor WebAssembly para que as requisições sejam distribuídas igualmente entre as instâncias.--always-on true— Nível básico ou superior. Não está disponível nos planos Gratuito ou Compartilhado.
3. Configuração
As configurações do aplicativo de aplicativo aparecem como variáveis de ambiente e sobrescrevem appsettings.json, que é exatamente o que você quer: deixe o arquivo como está e defina os valores por ambiente aqui.
Importante
Segredos de usuário não viajam. Tudo com que você armazenou
dotnet user-secretsdurante o desenvolvimento fica apenas na sua máquina. Cada uma dessas chaves precisa ser recriada como uma configuração de aplicativo — ou como referência ao Key Vault — ou o aplicativo implantado não vai iniciar.
Use um duplo sublinhado para o separador de seção: D365:ClientId torna-se D365__ClientId. O formulário dos dois-pontos funciona no Windows App Service, mas não no Linux, então __ o formulário a ser usado em todos os lugares é o caso.
Sempre obrigatório
D365__Url— a URL do ambiente Dataverse, por exemplo.https://yourorg.crm.dynamics.comNão é segredo.D365__ClientId— o ID do cliente do registro do aplicativo Entra que o portal conecta ao Dataverse como. Não é segredo.D365__ClientSecret— esse registro é segredo do cliente. Segredo: use uma referência do Cofre de Chaves.D365__EmailSenderEmailAddress— o endereço de origem do portal envia e-mails de confirmação de conta e redefinição de senha, através do Dataverse. O mais fácil de esquecer, porque não está disponívelappsettings.jsone o aplicativo não inicia sem ele.ASPNETCORE_ENVIRONMENT— defina paraProduction. Isso faz mais do que alterar a página de erro: em um portal React, o recurso do SPA é registrado apenas fora do Desenvolvimento, então um site deixado em funcionamentoDevelopmentredireciona links profundos parahttp://localhost:5173o app em vez de servir o app.
Obrigatório se você ativou esse provedor de login
Authentication__Microsoft__ClientIdeAuthentication__Microsoft__ClientSecret— o registro de log-in, que é um registro de aplicativo diferente do do Dataverse. Veja Entrada ID Log-In.Authentication__Google__ClientIdeAuthentication__Google__ClientSecret.Authentication__Facebook__AppIdeAuthentication__Facebook__AppSecret.
Opcional
Azure__Translation__Key,DeepL__Translation__KeyouGoogle__Translation__Key— qualquer provedor de tradução automática que você tenha registrado. Deixe desconfigurado e a página de Administração de Localização simplesmente esconde seu painel de tradução; o restante da página ainda funciona.PortalIdentity__SystemAdminRoleName— o papel de segurança do Dataverse que concede acesso aos administradores do portal. Ele é enviadoappsettings.json; substitui aqui quando o nome do papel varia conforme o ambiente.
az webapp config appsettings set --name myportal --resource-group myportal-rg --settings ASPNETCORE_ENVIRONMENT=Production D365__Url="https://yourorg.crm.dynamics.com" D365__ClientId="00000000-0000-0000-0000-000000000000" D365__EmailSenderEmailAddress="portal@contoso.com"
Nota
As chaves necessárias são lidas com
GetRequiredValue, então uma que falta é lançada durante a inicialização em vez de falhar depois na primeira chamada Dataverse. Em um serviço de aplicativos que aparece como um site que nunca aparece — leia o fluxo de log, e a exceção nomeia a chave.
4. Dê ao portal um endereço para enviar
D365__EmailSenderEmailAddress é de onde vem o e-mail de confirmação de conta e redefinição de senha, e o framework resolve isso procurando um usuário do Dataverse com esse endereço, depois uma fila, e então uma equipe. Use uma fila. É a única opção que não precisa de nenhum trabalho no Exchange: a caixa de correio que o Dataverse cria para uma fila aponta para o perfil existente do Exchange Online da organização, então não há caixa de correio para provisionar nem licença para comprar para o endereço. Apontar a configuração para uma pessoa real, em vez disso, custa três coisas:
- O nome deles aparece em todas as mensagens automáticas. Os destinatários veem um colega como o remetente de uma redefinição de senha que não enviaram.
- As respostas chegam à caixa de entrada deles. As pessoas respondem a e-mails sem resposta, e essas respostas vão para algum lugar onde ninguém está de olho por elas.
- O portal quebra quando eles saem. Desabilitar a conta leva o remetente junto, e a falha aparece como a confirmação da conta silenciosamente sem funcionar.
Três etapas, todas dentro do Dataverse. O servidor MCP faz as duas primeiras em uma única chamada com create_email_queue:
- Crie uma fila privada com o endereço (por exemplo
noreply@contoso.com), a entrega recebida Nenhum e a Sincronização do Lado do Servidor da entrega de saída. O nome da fila é o que os destinatários veem como remetente, então nomeie para o portal em vez de uma pessoa. - Aprove o endereço. Dois flags precisam coincidir e eles ficam em registros diferentes: a aprovação do endereço de e-mail da fila e a caixa de correio aprovada pelo administrador do O365. Uma fila é criada pendente em ambos.
- Teste e ative a caixa de correio no centro de administração do Power Platform → Configurações → Configuração de e-mails → Caixas de Correio. Essa é a única etapa que precisa ser feita manualmente, e é um único botão.
Importante
Uma caixa de correio não aprovada ou não testada falha silenciosamente. O Dataverse aceita o e-mail, grava-o e nunca o entrega — o que, do lado do portal, é indistinguível de um envio bem-sucedido. Nada é lançado, nada é registrado como erro, e o primeiro sinal de problema é um usuário dizendo que nunca recebeu o link de confirmação. Se os e-mails de registro não estiverem chegando, verifique o status de saída da caixa antes de qualquer outra coisa: Não Executar significa que essa etapa foi perdida.
5. Guarde os segredos no Cofre da Chave
Dê ao app uma identidade gerenciada, conceda acesso de leitura a um cofre e coloque o URI de cada segredo na configuração do app em vez do segredo em si. O App Service resolve a referência para você, e o valor nunca aparece na blade de configuração, em um script ou em um log de implantação.
az webapp identity assign --name myportal --resource-group myportal-rg --query principalId --output tsv
az keyvault create --name myportal-kv --resource-group myportal-rg --enable-rbac-authorization true
az role assignment create --assignee PRINCIPAL_ID --role "Key Vault Secrets User" --scope VAULT_RESOURCE_ID
az keyvault secret set --vault-name myportal-kv --name D365-ClientSecret --value THE_SECRET
az webapp config appsettings set --name myportal --resource-group myportal-rg --settings D365__ClientSecret="@Microsoft.KeyVault(SecretUri=https://myportal-kv.vault.azure.net/secrets/D365-ClientSecret/)"
Quando um segredo do cliente expira — o Entra tem um ano por padrão — adicione uma nova versão ao cofre e reinicie o app. A configuração do app em si não muda.
O servidor MCP faz toda essa sequência: create_managed_identity cria uma identidade atribuída pelo usuário, enable_managed_identity anexa ao app, create_key_vault cria o cofre e concede acesso à leitura dessa identidade, e as ferramentas secretas então aceitam destination: "key-vault" — o que envia um cliente recém-criado do Entra direto para o cofre e deixa apenas a referência nas configurações do app. O valor nunca se torna uma configuração literal e nunca passa pelas mãos de ninguém.
Importante
Um nome de Cofre de Chaves é reservado por 90 dias após a exclusão. A exclusão suave está ativada por padrão e não pode ser desativada, então excluir um cofre não libera seu nome — recriar um com o mesmo nome dentro dessa janela exige uma purga explícita, que por si só precisa de permissão e é irreversível. É o único recurso aqui que não é facilmente desfazível, então escolha o nome com a mesma deliberação que escolheria o do App Service.
Nota
Melhor ainda, elimine completamente o cliente secreto do Dataverse. Defina
D365__ManagedIdentityIdpara o id de cliente de uma identidade gerenciada e o portal autentica o Dataverse como essa identidade, sem segredo e, portanto, sem nada a expirar. Não há verificação de ambiente nem switch de build: a configuração pertence à configuração do App Service em vez deappsettings.json, então ela está ausente localmente e presente quando implantada, e uma build se comporta corretamente em ambas. Use uma identidade atribuída pelo usuário — uma identidade atribuída pelo sistema é criada e destruída junto com o app e varia conforme o slot de implantação, então reconstruir o app cria uma nova identidade e deixa órfãos que o usuário do aplicativo Dataverse construiu sobre a antiga. A identidade ainda precisa desse usuário do aplicativo, criado a partir do id de aplicação em vez do id principal que o portal Azure mostra primeiro.
6. Publicar
Publicar dependente do framework, zipar a saída, empurrar o zip. Em um portal React, o mesmo dotnet publish também constrói o SPA e o posiciona em wwwroot.
dotnet publish ./MyPortal/MyPortal.csproj -c Release -o ./publish
Compress-Archive -Path ./publish/* -DestinationPath ./publish.zip -Force
az webapp deploy --name myportal --resource-group myportal-rg --src-path ./publish.zip --type zip
Zipe o conteúdo da pasta de publicação, não a pasta em si — o App Service desembala o arquivo diretamente na raiz do site, então um diretório aninhado de nível superior produz um site que não serve a nada.
O diálogo Publish do Visual Studio faz a mesma coisa de forma interativa, e um fluxo de trabalho GitHub Actions ou Azure Pipelines faz isso no push. O único passo extra para um portal React é um passo de configuração do Node antes da build, já que dotnet publish o CPL é enviado para o npm.
7. Apontar a licença para a URL implantada
A licença do site está vinculada a uma URL. No aplicativo orientado por modelos Power Portals Pro, abra (ou crie) o registro do site do portal que contém sua chave de licença e defina sua URL para a URL de produção do portal que você acabou de lançar — https://myportal.azurewebsites.net, ou seu domínio personalizado, se estiver indo à frente dele.
Registre apenas a URL de produção . Validação também aceita -dev, -test, -uat e -stage hosts com sufixo, e os mesmos quatro como subdomínios iniciais, então ambientes inferiores não precisam de registro separado. A página de Licenciamento tem a lista completa.
Importante
Faça isso antes de enviar tráfego. Todo endpoint de serviço de dados verifica a licença, então até que o registro exista e corresponda ao host, o portal renderiza mas toda chamada de dados retorna
402.
8. Adicionar o URI de redirecionamento de produção
Se o portal oferece login Microsoft, o registro do aplicativo de login ainda só conhece o URI do seu redirecionamento localhost. Adicione o portal implantado junto — o Entra aceita vários, então o local continua funcionando:
https://myportal.azurewebsites.net/signin-microsoft
Mesmo padrão para os outros provedores em seus próprios caminhos de callback (/signin-google, /signin-facebook). Adicione um por ambiente e um por domínio personalizado.
Espaços de implantação
Um slot de staging e uma troca dão um aplicativo aquecido e um retrocesso instantâneo. Eles são a única coisa que realmente vale a pena sair do B1 — e o motivo para sair quando você precisar, e não antecipadamente. Duas coisas precisam de cuidados.
As chaves de Proteção de Dados são por slot. ASP.NET Core mantém o chaveiro sob %HOME%, do qual cada slot tem sua própria cópia, então uma troca o substitui — e todo cookie de autenticação emitido pelo slot anterior se torna indecifrável. Todos estão desconectados. Mova o chaveiro para algum lugar onde ambos os slots sejam compartilhados antes de depender da troca:
builder.Services.AddDataProtection()
.PersistKeysToAzureBlobStorage(new Uri(blobUri), new DefaultAzureCredential())
.ProtectKeysWithAzureKeyVault(new Uri(keyUri), new DefaultAzureCredential());
Marque as configurações específicas do ambiente como configurações de slot para que fiquem para o lado durante a troca. A URL do ambiente do Dataverse é a que mais funciona: sem ela, trocar um slot de staging que aponta para um sandbox promove essa conexão sandbox para a produção.
Escalonamento
Escalar é uma decisão a ser tomada quando você mede a necessidade dela, não quando você provisiona. Uma instância do nível padrão carrega uma quantidade surpreendente de tráfego de portais.
- Blazor Server / Auto: mantenha a afinidade de sessão ativada e prevê cerca de 250 KB de memória de servidor por circuito concorrente como ponto de partida antes de medir o seu próprio. O Azure SignalR Service não é obrigatório no App Service — é uma ferramenta para usuários com muito alta quantidade de conexões ou distribuídos globalmente.
- React / Blazor WebAssembly: desative a afinidade da sessão e escale horizontalmente. As requisições são stateless, então as instâncias são intercambiáveis.
- Os templates registram
AddDistributedMemoryCache(), que é por instância. Em mais de uma instância, troque-o por um cache distribuído real para que metadados e caches de localização sejam compartilhados em vez de reconstruídos em cada um.
Fazendo tudo isso a partir de um agente de IA
O servidor MCP Power Portals Pro pode realizar todas as etapas desta página. Vincule a pasta a uma assinatura uma vez, em um terminal — o vínculo pertence a essa pasta, então vários projetos podem direcionar clientes diferentes ao mesmo tempo, e nada muda em toda a máquina:
ppp-mcp azure login
check_deployment— o que deve ser alcançado primeiro. Ele compara o que o projeto precisa com o que o App Service tem e nomeia o que está errado: uma configuração obrigatória que só existiu nos seus segredos de usuário, o ambiente deixado no Desenvolvimento, WebSockets desligados em um portal Blazor, um registro do site do portal que não cobre o host. Somente leitura, então é seguro rodar contra produção.get_deployment_plan— os passos, e cada nível de plano com seu preço real atual da lista pública de preços do Azure.create_app_service— grupo de recursos, plano e aplicativo web, com o par WebSockets e afinidade já definidos para sua pilha.set_app_service_settings— funde configurações em vez de substituí-las, para que não possa apagar as que guardam seus segredos.deploy_to_app_service— constrói comdotnet publishe empurra o resultado.create_dataverse_app_registration,create_signin_app_registrationeadd_app_registration_redirect_uri— o lado do Entra dos passos 6 e 7, usando o papel de diretório da sua própria conta logada. Um segredo de cliente criado vai direto para os segredos de usuário ou para a configuração do Serviço de Aplicativos e nunca é exibido.create_managed_identity,enable_managed_identityecreate_key_vault— a identidade e o cofre acima, com as atribuições de função feitas para você e reavaliadas enquanto se propagam.create_email_queue— a fila do remetente sem resposta do passo 4, criada e aprovada em uma única chamada.
Nota
Toda ferramenta que altera algo no Azure pede para você confirmar primeiro, e informa quanto vai custar antes de você responder — o valor mensal real da sua região, com uma nota de que a lista de varejo ignora qualquer Acordo de Empresa ou reserva que você tenha. Ferramentas que apenas leem nunca solicitam. O servidor não usa a CLI do Azure e nunca lê ou altera seu estado de logado.
Solução de problemas
- O site nunca aparece. Geralmente falta uma configuração obrigatória de app. A exceção inicial nomeia a chave — leia em Log stream ou Diagnostice e resolva problemas.
- O portal renderiza, mas toda chamada de dados retorna 402. A verificação de licença está rejeitando o host. Confirme que a URL do registro do site do portal corresponde ao host que o navegador está realmente usando e que a solução gerenciada está instalada no ambiente ao qual o portal se conecta. A rejeição é registrada com o host exato, host encaminhado e esquema que foram validados.
- Loops de redirecionamento, ou um
http://URL onde você esperavahttps://. O app não está respeitando os cabeçalhos encaminhados pelo front end do serviço de aplicativos. DefinaASPNETCORE_FORWARDEDHEADERS_ENABLED=truepara que o esquema de requisição e o IP do cliente reflitam a solicitação original. - "Falhou ao conectar via WebSockets, usando o recurso de enquete Longa" no console do navegador em um portal Blazor — Web sockets ainda está desligado no App Service.
- Todos são desconectados após uma implantação ou troca. O anel de chaveiro de Proteção de Dados foi alterado; veja Espaços de implantação acima.
- Um React deep link 404s ou redireciona para localhost.
ASPNETCORE_ENVIRONMENTnãoProductioné — o recurso de reposição SPA é registrado apenas fora do Desenvolvimento. - Nada útil nos logs. O registro de aplicativos de aplicativos está desativado por padrão: ative o registro do sistema de arquivos no nível da informação, depois faça um follow com
az webapp log tail.
