Deploying to Azure App Service
Un portal de Power Portals Pro es una aplicación Core de un solo ASP.NET según la pila que elijas, por lo que desplegarlo es algo ordinario dotnet publish para un servicio de aplicaciones ordinario. Esta página cubre los recursos a crear, los ajustes que lee el portal, qué sugerimos como predeterminados y los pocos pasos específicos de Power Portals Pro en lugar de ASP.NET Core.
Lo que estás desplegando
Ambas plantillas producen una aplicación web y una salida de publicación. No hay un host front-end separado que pueda montar, ni un recurso de sitio estático, ni un segundo servicio de aplicaciones:
- Blazor — el proyecto servidor aloja la aplicación. Bajo
ServeroAutointeractividad también sirve al circuito Blazor SignalR; bajoWebAssemblyeste sirve al paquete cliente y a los/api/*extremos que llama el cliente. - React —
dotnet publishejecuta la compilación Vite para el proyecto cliente y ladist/introduce en etapas en el delwwwroothost, así que un host ASP.NET Core sirve al SPA y a la API desde un único origen. No hay nada extra que cablear.
Nota
Como el react publish se distribuye a npm, Node tiene que instalarse en cualquier máquina que se ejecute
dotnet publish— tu portátil o el agente de compilación. El cliente de la plantilla pide Node^20.19.0 || >=22.12.0. App Service nunca necesita Node; solo ve la salida compilada.
Valores predeterminados sugeridos
Puntos de partida, no reglas — pero estos son los que elegiríamos para un nuevo portal de producción.
- Sistema operativo: Linux. Más barato por nivel, más rápido de empezar y mantiene IIS
web.configfuera de la ecuación por completo. Windows funciona bien si tu organización lo estandariza por completo. - Pila de tiempo de ejecución: .NET 10. Las plantillas tienen como objetivo
net10.0. Publicar dependiente del framework y dejar que App Service proporcione el tiempo de ejecución. - Plan: B1 — para cualquier entorno, incluida la producción. B1 es el nivel más barato que puede mantenerse caliente, y escalar más adelante es un comando sin redespliegue ni tiempo de inactividad significativo. Provisionar algo más grande "porque es producción" inicia una factura que nadie revisa; empieza aquí y sube cuando una medición o necesidad de espacios de despliegue lo requiera. Lo que renuncias hasta entonces: no hay ranuras de despliegue ni escalado automático (ambos empiezan en Standard — y compara S1 con P0v3, que suele ser más barato en Linux y tiene más memoria).
- Siempre activado: encendido. App Service descarga una app inactiva, y un inicio en frío significa reconectarse a Dataverse y reconstruir las cachés de metadatos y localización. El primer visitante tras un periodo de silencio paga todo.
- Solo HTTPS: activado, mínimo TLS 1.2, HTTP/2 habilitado. El portal emite cookies de autenticación; no hay razón para aceptar HTTP simple.
- Sockets web: activados para Blazor Server o Auto. Apagado es el valor predeterminado del servicio de aplicaciones, y sin él el circuito vuelve silenciosamente a sondeos largos.
- Afinidad de sesión: Activada para Blazor Server o Auto — un circuito pertenece a una instancia. Desactivala para portales React y Blazor WebAssembly: son sin estado sobre HTTP y la afinidad solo desequilibra tus instancias.
- Secretos en Key Vault, referenciados desde la configuración de la app. Asigna al App Service una identidad gestionada y apunta la configuración al vault, para que el secreto nunca quede en la hoja de configuración ni en un script de despliegue.
- Claves de Protección de Datos en Blob Storage tan pronto como usas ranuras de despliegue o escalas más allá de una instancia. El llavero predeterminado es por ranura, y un swap firma la salida de todos.
WEBSITE_RUN_FROM_PACKAGE=1: opcional, y merece la pena. Los despliegues se vuelven atómicos y el directorio de contenido solo lectura. El portal solo lee desde su raíz de contenido, así que nada se rompe — sube el búfer a través de la carpeta temporal del sistema, que permanece escribible.- Análisis de aplicaciones: activado. El framework registra rechazos de licencias y fallos de Dataverse a través de
ILogger. Sin un sumidero, estás leyendo el flujo de logs a mano justo en el momento en que preferirías no hacerlo.
1. Crear el servicio de aplicaciones
Tres comandos. Sustituid vuestros propios nombres — myportal se usa a lo largo de esta 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"
El nombre de la aplicación web se convierte https://myportal.azurewebsites.net y tiene que ser globalmente único. Elígelo deliberadamente: a menos que pongas un dominio personalizado delante, es la URL a la que se vincula tu clave de licencia.
2. Establecer las opciones de plataforma
Estos son los que no tienen ningún valor por defecto para un 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— necesario para Blazor Server y Auto. Inofensivo en portales React y WebAssembly, que nunca abren un circuito.--client-affinity-enabled— déjalotruepara Blazor Server y Auto; pasafalsepor React y Blazor WebAssembly para que las peticiones se distribuyan de forma uniforme entre instancias.--always-on true— Nivel básico o superior. No está disponible en los planes Gratis ni Compartidos.
3. Configuración
La configuración de la aplicación de App Service aparece como variables de entorno y anula appsettings.json, que es exactamente lo que quieres: deja el archivo intacto y establece aquí los valores por entorno.
Importante
Los secretos de usuario no viajan. Todo lo que almacenaste
dotnet user-secretsdurante el desarrollo solo está en tu máquina. Cada una de esas claves debe recrearse como configuración de aplicación — o como referencia de Key Vault — o la aplicación desplegada no arrancará.
Utiliza un guion bajo doble para el separador de sección: D365:ClientId se convierte D365__ClientIden . El formulario de dos puntos funciona en Windows App Service pero no en Linux, por lo que __ el formulario es para usar en todas partes.
Siempre es necesario
D365__Url— la URL del entorno Dataverse, por ejemplo.https://yourorg.crm.dynamics.comNo es un secreto.D365__ClientId— el ID de cliente del registro de la app Entra que el portal conecta a Dataverse como. No es un secreto.D365__ClientSecret— ese registro es secreto de cliente. Secreto: usa una referencia de Key Vault.D365__EmailSenderEmailAddress— la dirección desde la que el portal envía correos de confirmación de cuenta y restablecimiento de contraseña, a través de Dataverse. La más fácil de olvidar, porque no está yappsettings.jsonla app no arranca sin ella.ASPNETCORE_ENVIRONMENT— configúralo comoProduction. Esto hace más que cambiar la página de error: en un portal React, el respaldo SPA solo se registra fuera de Desarrollo, por lo que un sitio que queda enDevelopmentactivo redirige enlaces profundos ahttp://localhost:5173en lugar de servir la app.
Obligatorio si activaste ese proveedor de inicio de sesión
Authentication__Microsoft__ClientIdyAuthentication__Microsoft__ClientSecret— el registro de inicio de sesión, que es un registro de aplicación diferente al de Dataverse. Consulta Entrada ID Inicio de sesión.Authentication__Google__ClientIdyAuthentication__Google__ClientSecret.Authentication__Facebook__AppIdyAuthentication__Facebook__AppSecret.
Opcional
Azure__Translation__Key,DeepL__Translation__KeyoGoogle__Translation__Key— el proveedor de traducción automática que hayas registrado. Si lo dejas sin activar, la página de Administración de Localización simplemente oculta su panel de traducción; el resto de la página sigue funcionando.PortalIdentity__SystemAdminRoleName— el rol de seguridad de Dataverse que otorga acceso a los administradores del portal. Se envíaappsettings.json; lo sobrescribe aquí cuando el nombre del rol varía según el entorno.
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
Las claves requeridas se leen con
GetRequiredValue, así que una que falta se lanza durante el arranque en lugar de fallar más tarde en la primera llamada a Dataverse. En el servicio de aplicaciones que aparece como un sitio que nunca aparece — lee el flujo de registro, y la excepción nombra la clave.
4. Proporciona al portal una dirección desde la cual enviar
D365__EmailSenderEmailAddress es de donde proviene el correo de confirmación de cuenta y restablecimiento de contraseña, y el framework lo resuelve buscando un usuario de Dataverse con esa dirección, luego una cola, y después un equipo. Usa una cola. Es la única opción que no necesita ningún trabajo en Exchange: el buzón que Dataverse crea para una cola apunta al perfil existente de Exchange Online de la organización, así que no hay buzón que provisionar ni licencia para comprar la dirección. Apuntar la configuración a una persona real te cuesta tres cosas:
- Su nombre aparece en todos los mensajes automáticos. Los destinatarios ven a un compañero como el remitente de un restablecimiento de contraseña que no enviaron.
- Las respuestas llegan a su bandeja de entrada. La gente responde a los correos sin respuesta, y esas respuestas van a un sitio donde nadie las está vigilando.
- El portal se rompe cuando se van. Desactivar la cuenta se lleva al remitente, y el fallo aparece cuando la confirmación de la cuenta no funciona en silencio.
Tres pasos, todos dentro de Dataverse. El servidor MCP realiza los dos primeros en una sola llamada con create_email_queue:
- Crea una cola privada con la dirección (por ejemplo,
noreply@contoso.com), la entrega entrante Ninguna y la entrega saliente Sincronización del Lado del Servidor. El nombre de la cola es lo que los destinatarios ven como el remitente, así que ponle nombre para el portal en lugar de para una persona. - Aprobar la dirección. Dos banderas tienen que coincidir y están en registros diferentes: la aprobación de la dirección de correo electrónico de la cola y la aprobación del buzón por el administrador de O365. Se crea una cola pendiente en ambas.
- Prueba y activa el buzón en el centro de administración de Power Platform → Configuración → Configuración del correo electrónico → Buzones. Este es el único paso que hay que hacer a mano, y es un solo botón.
Importante
Un buzón no aprobado o no probado falla silenciosamente. Dataverse acepta el correo, lo registra y nunca lo entrega — lo cual, desde el lado del portal, es indistinguible de un envío exitoso. Nada se lanza, nada se registra como error, y la primera señal de problema es que un usuario diga que nunca recibió su enlace de confirmación. Si los correos de registro no llegan, comprueba el estado saliente del buzón antes de nada: No Ejecutar significa que se ha pasado este paso.
5. Guarda los secretos en la Bóveda de las Llaves
Dale a la app una identidad gestionada, concédele acceso de lectura a una bóveda y pon el URI de cada secreto en la configuración de la app en lugar del secreto en sí. App Service resuelve la referencia por ti, y el valor nunca aparece en la hoja de configuración, en un script o en un registro de despliegue.
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/)"
Cuando un secreto de cliente expira — Entra por defecto tiene un año — añade una nueva versión a la bóveda y reinicia la aplicación. La configuración de la app en sí no cambia.
El servidor MCP hace toda esta secuencia: create_managed_identity crea una identidad asignada por el usuario, enable_managed_identity la asocia a la app, create_key_vault crea la bóveda y concede acceso a esa identidad por lectura, y las herramientas secretas luego la aceptan destination: "key-vault" — lo que envía un secreto cliente recién creado de Entra directamente a la bóveda y deja solo la referencia en la configuración de la app. El valor nunca se convierte en una configuración literal y nunca pasa por las manos de nadie.
Importante
Un nombre de Key Vault está reservado para 90 días después de la eliminación. La eliminación suave está activada por defecto y no puede desactivarse, por lo que eliminar una bóveda no libera su nombre — recrear una con el mismo nombre dentro de esa ventana requiere una purga explícita, que a su vez necesita permiso y es irreversible. Es el único recurso aquí que no es fácilmente inaccesible, así que elige el nombre tan deliberadamente como lo harías con el del servicio de aplicaciones.
Nota
Mejor aún, elimina por completo el secreto del cliente de Dataverse. Si se configura
D365__ManagedIdentityIdel id de cliente de una identidad gestionada, el portal se autentica a Dataverse como esa identidad, sin secreto y por tanto sin nada que caduque. No hay comprobación de entorno ni switch de compilación: la configuración pertenece a la configuración del App Service en lugar deappsettings.json, por lo que está ausente localmente y presente cuando se despliega, y una compilación se comporta correctamente en ambas. Usa una identidad asignada por el usuario — una asignada por el sistema se crea y destruye con la app y varía según el slot de despliegue, así que reconstruir la app crea una nueva identidad y deja huérfanos que el usuario de la aplicación Dataverse construyó sobre la antigua. La identidad sigue necesitando ese usuario de la aplicación, creado a partir de su id de aplicación en lugar del id principal que muestra el portal de Azure.
6. Publicar
Publicar dependiente del framework, comprimir la salida y empujar el zip. En un portal React lo mismo dotnet publish también construye el SPA y lo está en etapas en 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
Comprime el contenido de la carpeta de publicación, no la carpeta en sí: App Service desempaqueta el archivo directamente en la raíz del sitio, así que un directorio anidado de nivel superior produce un sitio que no sirve para nada.
El diálogo Publish de Visual Studio hace lo mismo de forma interactiva, y un flujo de trabajo de GitHub Actions o Azure Pipelines lo hace en push. El único paso extra para un portal React es un paso de configuración de Node antes de la compilación, ya que dotnet publish se despliega a npm.
7. Apunta la licencia a la URL desplegada
La licencia del sitio web está vinculada a una URL. En la aplicación orientada a modelos Power Portals Pro, abre (o crea) el registro del Portal que contiene tu clave de licencia y establece su URL en la URL de producción del portal que acabas de desplegar — https://myportal.azurewebsites.net, o en tu dominio personalizado si uno va delante de él.
Registra solo la URL de producción . La validación también acepta -dev, -test, -uat y -stage hosts con sufijo, y los mismos cuatro como subdominios iniciales, por lo que los entornos inferiores no necesitan un registro separado. La página de Licencias tiene la lista completa.
Importante
Haz esto antes de enviar el tráfico. Cada endpoint de servicio de datos revisa la licencia, así que hasta que el registro exista y coincida con el host, el portal se renderiza pero todas las llamadas de datos regresan
402.
8. Añadir el URI de redirección de producción
Si el portal ofrece inicio de sesión con Microsoft, el registro de la app de inicio de sesión solo conoce el URI de redirección de tu localhost. Añade el desplegado junto a él — Entra acepta varios, así que el local sigue funcionando:
https://myportal.azurewebsites.net/signin-microsoft
Mismo patrón para los otros proveedores en sus propias rutas de callback (/signin-google, /signin-facebook). Añade uno por entorno y uno por dominio personalizado.
Ranuras de despliegue
Un espacio de staging y un swap te dan una app de calentamiento y un retroceso instantáneo. Son lo único por lo que realmente merece la pena dejar B1 — y la razón para dejarlo cuando los necesitas, y no antes de hacerlo. Hay dos cosas que necesitan cuidado.
Las claves de protección de datos son por ranura. ASP.NET Core mantiene el llavero bajo %HOME%, del cual cada ranura tiene su propia copia, por lo que un swap lo reemplaza — y cada cookie de autenticación emitida por la ranura anterior se vuelve indescifrable. Todos están desconectados. Mueve el llavero a un lugar donde compartan ambas ranuras antes de depender del intercambio:
builder.Services.AddDataProtection()
.PersistKeysToAzureBlobStorage(new Uri(blobUri), new DefaultAzureCredential())
.ProtectKeysWithAzureKeyVault(new Uri(keyUri), new DefaultAzureCredential());
Marca los ajustes específicos del entorno como ajustes de ranura para que se queden durante el intercambio. La URL del entorno de Dataverse es la que más afecta: sin ella, cambiar una ranura de staging que apunta a un sandbox promueve esa conexión sandbox en producción.
Escalando
Escalar es una decisión que debes tomar cuando mides la necesidad, no cuando provisionas. Una instancia del nivel por defecto tiene una sorprendente cantidad de tráfico de portal.
- Blazor Server / Auto: mantén activada la afinidad de sesión y presupuesta aproximadamente 250 KB de memoria de servidor por circuito concurrente como punto de partida antes de medir la tuya propia. El servicio Azure SignalR no es obligatorio en App Service — es una herramienta para usuarios con muy altas conexiones o distribuidos globalmente.
- React / Blazor WebAssembly: desactiva la afinidad de sesión y escala horizontalmente. Las peticiones son sin estado, por lo que las instancias son intercambiables.
- Las plantillas se registran
AddDistributedMemoryCache(), que es por instancia. En más de una instancia, cámbiala por una caché distribuida real para que los metadatos y cachés de localización se compartan en lugar de reconstruirse en cada una.
Todo esto desde un agente de IA
El servidor MCP de Power Portals Pro puede realizar todos los pasos de esta página. Vincula la carpeta a una suscripción una vez, en un terminal — la vinculación pertenece a esa carpeta, así que varios proyectos pueden dirigirse a diferentes clientes al mismo tiempo, y nada cambia a nivel de máquina:
ppp-mcp azure login
check_deployment— el primero a alcanzar. Compara lo que necesita el proyecto con lo que tiene el Servicio de Aplicaciones y nombra lo que está mal: una configuración obligatoria que solo existió en tus secretos de usuario, el entorno dejado en Desarrollo, WebSockets desactivados en un portal Blazor, un registro de Portal Website que no cubre el host. Solo lectura, por lo que es seguro ejecutarlo contra producción.get_deployment_plan— los escalones y cada nivel de plano con su precio real actual de la lista pública de precios de Azure.create_app_service— grupo de recursos, plan y aplicación web, con el par WebSockets y afinidad ya configurados para tu pila.set_app_service_settings— fusiona la configuración en lugar de reemplazarla, para que no pueda borrar las que guardan tus secretos.deploy_to_app_service— construye condotnet publishy empuja el resultado.create_dataverse_app_registration,create_signin_app_registrationyadd_app_registration_redirect_uri— el lado de Entra de los pasos 6 y 7, usando el rol de directorio de tu cuenta iniciada en sesión. Un secreto de cliente creado va directamente a los secretos de usuario o a la configuración del Servicio de Aplicaciones y nunca se muestra.create_managed_identity,enable_managed_identityycreate_key_vault— la identidad y el refugio anteriores, con las asignaciones de roles hechas para ti y reprobadas mientras se propagan.create_email_queue— la cola de remitente sin respuesta del paso 4, creada y aprobada en una sola llamada.
Nota
Cada herramienta que cambia algo en Azure te pide confirmar primero y te dice cuánto costará antes de que respondas — la cifra mensual real de tu región, con una nota de que la lista de tiendas ignora cualquier Acuerdo de Empresa o reserva que tengas. Las herramientas que solo leen nunca lo solicitan. El servidor no usa la CLI de Azure y nunca lee ni cambia su estado de inicio.
Resolución de problemas
- El sitio nunca aparece. Normalmente falta una configuración obligatoria de la aplicación. La excepción de inicio nombra la clave — léela en Flujo de registro o Diagnostica y resuelve problemas.
- El portal se renderiza pero cada llamada de datos devuelve 402. La comprobación de licencia está rechazando al host. Confirma que la URL del registro del sitio web del portal coincide con el host que realmente está usando el navegador, y que la solución gestionada está instalada en el entorno al que se conecta el portal. El rechazo se registra con el host exacto, host reenviado y esquema que fueron validados.
- bucles de redirección, o una
http://URL donde esperabashttps://. La app no está respetando los encabezados reenviados desde el frontend del servicio de aplicaciones. ConfiguraASPNETCORE_FORWARDEDHEADERS_ENABLED=truepara que el esquema de peticiones y la IP del cliente reflejen la solicitud original. - "No se ha conseguido conectar vía WebSockets, usando el fallback Long Polling" en la consola del navegador en un portal Blazor — Web sockets sigue desactivado en el servicio de aplicaciones.
- Todos están desconectados tras un despliegue o un intercambio. El llavero de Protección de Datos ha cambiado; ver Ranuras de despliegue arriba.
- Un enlace profundo de React 404s o redirecciona a localhost.
ASPNETCORE_ENVIRONMENTnoProductionlo es — el respaldo SPA solo se registra fuera de Desarrollo. - Nada útil en los registros. El registro de aplicaciones de aplicaciones de App Service está desactivado por defecto: activa el registro del sistema de archivos a nivel de información y luego lo sigas con
az webapp log tail.
