meta données pour cette page
  •  

Différences

Ci-dessous, les différences entre deux révisions de la page.

Lien vers cette vue comparative

Les deux révisions précédentesRévision précédente
Prochaine révision
Révision précédente
certif:procedure:miseenplaceserveur:serveurdotnet [2026/08/18 14:16] – nicolascertif:procedure:miseenplaceserveur:serveurdotnet [2026/09/30 14:39] (Version actuelle) – nicolas
Ligne 1: Ligne 1:
-====== Guide de déploiement — API .NET (Stimulsoft.PDF.Forms + EF Core SQLite) sur Windows Server + IIS ======+|{{:undo-2.svg?30|}} [[certif:procedure:miseenplaceserveur|Retour à Proc#144 - Installation et mise à jour des serveurs et des applications]]|| 
 +|**Document technique** |Rattaché à [[certif:procedure:miseenplaceserveur|Proc#144]]. Non soumis à approbation ([[certif:procedure:maitrisedocumentaire|Proc#163]]) : tenu à jour par l'équipe de développement.| 
 +====== Document technique — API .NET (Stimulsoft.PDF.Forms + EF Core SQLite) sur Windows Server + IIS ======
  
-//Projet réel : ASP.NET Core Web API, .NET 8, packages Stimulsoft.PDF.Forms (génération de formulaires PDF côté serveur, en paire avec le package Angular ''stimulsoft-forms'' côté client) et Microsoft.EntityFrameworkCore.Sqlite (base de données SQLite locale).//+//Version enrichie suite à un déploiement réel (serveur app-logeas-ovh, site "Formulaire") : chaque piège rencontré en production a été intégré à ce guide, avec sa cause exacte et sa correction. Suivre ce guide dans l'ordre, en particulier l'étape 2 (souvent sous-estimée), évite la quasi-totalité des problèmes rencontrés lors du premier déploiement.//
  
-//Topologie retenue : cette API est déployée sur un **site/domaine séparé** de l'application Angular (ex. ''api.mondomaine.local'' vs ''questionnaire.mondomaine.local''). Cela implique de configurer explicitement **CORS** pour que le frontend Angular soit autorisé à appeler l'API — voir étape 5.//+//Projet : ASP.NET Core Web API, .NET 8, Stimulsoft.PDF.Forms (formulaires PDF, en paire avec le package Angular ''stimulsoft-forms'' côté client) et Microsoft.EntityFrameworkCore.Sqlite. API déployée sur un domaine séparé de l'Angular (CORS nécessaire).//
  
 ---- ----
Ligne 9: Ligne 11:
 ===== 1. Objectif et architecture ===== ===== 1. Objectif et architecture =====
  
-Ce guide décrit le déploiement d'une API ASP.NET Core (.NET 8) sur un Windows Server via IIS, en tant que backend séparé pour l'application Angular (déjà couverte dans un guide dédié). L'API génère des formulaires PDF via Stimulsoft.PDF.Forms et persiste ses données dans une base SQLite locale au serveur (fichier unique, pas de serveur de base de données séparé).+Ce guide déploie une API ASP.NET Core (.NET 8) sur Windows Server via IIS, en tant que backend séparé pour une application Angular. IIS héberge l'API **in-process** via le module ASP.NET Core (ANCM) : pas de process séparé à surveiller, pas de reverse proxy manuel — IIS démarre/redémarre l'API automatiquement.
  
-Comme pour tout ASP.NET Core, IIS héberge l'application **in-process** via le module ASP.NET Core (ANCM) : pas de process séparé à surveiller, pas de reverse proxy manuel, IIS démarre/redémarre l'API automatiquement.+**Schéma :** Angular (autre domaine) → appel HTTPS avec en-tête CORS → IIS (port 443, certificat SSL, liaison par nom d'hôte/SNI) → module ANCM → API .NET (Stimulsoft.PDF.Forms + EF Core) → fichier SQLite sur le serveur, dans un dossier persistant distinct du dossier de déploiement.
  
-**Schéma :** Angular (autre site/domaine) → appels HTTPS avec en-têtes CORS → IIS (port 443, certificat SSL) → module ANCM → API .NET (Stimulsoft.PDF.Forms + EF Core) → fichier SQLite local sur le serveur.+**Ce qui a posé problème lors du premier déploiement, dans l'ordre où ça s'est manifesté** (pour comprendre pourquoi certaines étapes ci-dessous insistent autant sur la vérification) : 
 +  - Le rôle IIS et le site étaient en place, mais le **.NET Hosting Bundle n'avait en réalité jamais été installé correctement** → erreur 500.19 (config invalide, code 0x8007000d) parce qu'IIS ne reconnaissait pas l'élément ``<aspNetCore>``. 
 +  - Après installation du Hosting Bundle, IIS trouvait le module mais pas le runtime → erreur 500.31 ("Failed to load ASP.NET Core runtime"). 
 +  - Une fois le runtime confirmé présent, le démarrage plantait quand même avec un code générique **0x8000ffff** → cause : le **Visual C++ Redistributable 2015-2022 (x64)** n'était pas installé (dépendance native d'ANCM, censée être installée par le Hosting Bundle mais pas toujours effective sans réparation + redémarrage complet du serveur). 
 +  - Une fois ANCM capable de démarrer le process, l'app restait bloquée en **503 "Application Shutting Down"** en boucle → cause : droits NTFS insuffisants pour le compte du pool IIS sur le dossier contenant la base SQLite (l'app fonctionnait très bien lancée manuellement sous un compte Administrateur, ce qui a permis d'isoler le problème aux droits, pas au code). 
 +  - Une fois les droits corrigés, des tests ``curl`` intermittents donnaient encore des erreurs de négociation TLS — cause : un problème de méthodologie de test (SNI), pas du serveur (voir étape 12).
  
-**Point d'attention spécifique à ce projet, à garder en tête tout du long :** le fichier de base SQLite ne doit **jamais** se trouver dans le dossier qui sera écrasé à chaque déploiement (sous peine de perdre les données à la prochaine mise à jour) — voir étape 4.+===== 2. Prérequis — à vérifier avant tout déploiement =====
  
-===== 2. Prérequis ===== +  * Accès administrateur au Windows Server. 
- +  * Le rôle « Serveur Web (IIS) » installé.
-  * Accès administrateur au Windows Server (2016, 2019, 2022 ou équivalent). +
-  * Le rôle « Serveur Web (IIS) » disponible via Gestionnaire de serveur.+
   * Le SDK .NET 8 installé sur le poste/pipeline de build.   * Le SDK .NET 8 installé sur le poste/pipeline de build.
-  * Un nom de domaine ou sous-domaine dédié à l'API (distinct de celui de l'Angular), ex. ''api.mondomaine.local''. +  * Un nom de domaine dédié à l'API, distinct de celui de l'Angular (ex. ``api.mondomaine.fr``). 
-  * Un certificat SSL pour ce domaine — indispensable ici en plus d'être une bonne pratique : la plupart des navigateurs bloquent les appels CORS depuis une origine HTTPS (l'Angular) vers une API en simple HTTP. +  * Un certificat SSL couvrant ce domaine (souvent un wildcard déjà utilisé par d'autres sites du même serveur). 
-  * Le fichier de licence Stimulsoft (``license.key`` ou clé en dur selon votre implémentation).+  * Le fichier ou la clé de licence Stimulsoft. 
 + 
 +> **Nouveau, issu de l'expérience réelle :** avant même de commencer, vérifiez que les deux dépendances suivantes sont déjà présentes sur le serveur cible si des applications .NET Core y tournent déjà — sinon, prévoyez leur installation dès l'étape 4 : 
 +> - Le **.NET Hosting Bundle** (runtime + module IIS), PAS seulement le SDK ou le runtime "autonome". 
 +> - Le **Visual C++ Redistributable 2015-2022 (x64)** — dépendance native souvent oubliée, cause de l'erreur la plus difficile à diagnostiquer de tout ce guide (voir étape 4 et l'annexe B).
  
 ===== 3. Étape 1 — Installer le rôle IIS ===== ===== 3. Étape 1 — Installer le rôle IIS =====
  
 <code powershell> <code powershell>
-# PowerShell, en tant qu'administrateur 
 Install-WindowsFeature -Name Web-Server -IncludeManagementTools Install-WindowsFeature -Name Web-Server -IncludeManagementTools
 </code> </code>
  
-===== 4. Étape 2 — Installer le .NET Hosting Bundle =====+===== 4. Étape 2 — Installer le .NET Hosting Bundle ET le VC++ Redistributable, puis vérifier ===== 
 + 
 +**C'est l'étape la plus critique de tout le guide.** Ne passez pas à la suite sans avoir validé chaque commande de vérification ci-dessous — c'est exactement ce qui a manqué lors du premier déploiement, et qui a coûté la majorité du temps de dépannage. 
 + 
 +==== 4.1 Installer le Hosting Bundle ==== 
 + 
 +Téléchargez et exécutez le **« ASP.NET Core Runtime 8.0.x – Windows Hosting Bundle »** depuis https://dotnet.microsoft.com/download/dotnet/8.0 — bien le lien **"Hosting Bundle"**, pas "Runtime" seul (qui n'installe pas le module IIS).
  
-Indispensable pour qu'IIS sache exécuter une application .NET 8 (sans elle, erreur 500.19 ou 502.5).+==== 4.2 Installer/réparer le Visual C++ Redistributable ====
  
-  * Télécharger le « ASP.NET Core Runtime 8.0 – Windows Hosting Bundle » depuis dotnet.microsoft.com/download/dotnet/8.0 (bien prendre le Hosting Bundle, pas le runtime seul). +Le Hosting Bundle est censé l'installer automatiquement, mais ce n'est pas toujours effectif. Installez-le (ou réparez-le s'il est déjà présent) explicitement :
-  * Lancer l'installeur sur le serveur. +
-  * Redémarrer IIS :+
  
 <code powershell> <code powershell>
-net stop was /y +Invoke-WebRequest -Uri "https://aka.ms/vs/17/release/vc_redist.x64.exe" -OutFile "$env:TEMP\vc_redist.x64.exe" 
-net start w3svc+Start-Process "$env:TEMP\vc_redist.x64.exe" -ArgumentList "/install /quiet /norestart" -Wait
 </code> </code>
  
-  * Vérifier dans IIS Manager → nœud serveur → « Modules » → présence de ''AspNetCoreModuleV2''.+==== 4.3 Redémarrer complètement le serveur (pas seulement IIS) ==== 
 + 
 +<code powershell> 
 +Restart-Computer 
 +</code> 
 + 
 +Un simple ``iisreset`` ou ``net stop was`` / ``net start w3svc`` ne suffit pas toujours : le chargement de bibliothèques natives (hostfxr, VC++ runtime) peut nécessiter un redémarrage complet pour libérer des fichiers verrouillés ou finaliser l'installation. 
 + 
 +==== 4.4 Vérifier — ne pas sauter cette sous-étape ==== 
 + 
 +Après le redémarrage, dans une **nouvelle** session PowerShell (le PATH n'est chargé qu'à l'ouverture de session) : 
 + 
 +<code powershell> 
 +# Le module IIS doit exister physiquement 
 +Test-Path "C:\Program Files\IIS\Asp.Net Core Module\V2\aspnetcorev2.dll" 
 +# doit renvoyer True 
 + 
 +# Le runtime .NET doit être listé 
 +dotnet --list-runtimes 
 +# doit afficher une ligne "Microsoft.AspNetCore.App 8.x.x" ET une ligne "Microsoft.NETCore.App 8.x.x" 
 +</code> 
 + 
 +Si l'une de ces deux vérifications échoue, ne continuez pas — reprenez l'installation avant d'aller plus loin. C'est ce contrôle, sauté lors du premier déploiement, qui aurait évité une bonne partie du dépannage.
  
 ===== 5. Étape 3 — Configurer CORS dans le code ===== ===== 5. Étape 3 — Configurer CORS dans le code =====
  
-Comme l'API et l'Angular sont sur deux origines différentes, le navigateur bloquera par défaut les appels de l'Angular vers l'API tant que CORS n'est pas explicitement autorisé côté serveur. Dans ''Program.cs'' :+Si votre code lit les origines autorisées depuis la configuration (pattern recommandé, déjà en place dans ce projet via ``Cors:Origins``), il n'y a rien à changer dans le code — seulement dans ``appsettings.Production.json`` (étape 8). Sinon, dans ``Program.cs`` :
  
 <code csharp> <code csharp>
-var builder = WebApplication.CreateBuilder(args); 
- 
 builder.Services.AddCors(options => builder.Services.AddCors(options =>
 { {
-    options.AddPolicy("AllowAngularApp", policy =>+    options.AddPolicy("logeas-web", policy =>
     {     {
-        policy.WithOrigins("https://questionnaire.mondomaine.local") // origine exacte de l'Angular+        policy.WithOrigins(builder.Configuration.GetSection("Cors:Origins").Get<string[]>() ?? [])
               .AllowAnyHeader()               .AllowAnyHeader()
               .AllowAnyMethod();               .AllowAnyMethod();
-        // .AllowCredentials() si l'API utilise des cookies d'authentification 
     });     });
 }); });
 +// ...
 +app.UseCors("logeas-web");
 +</code>
  
-// ... reste de la configuration ...+> **Rappel important :** l'origine à autoriser est celle du site qui **appelle** l'API (l'Angular, ex. ``interface.logeas-web.fr``), jamais celle de l'API elle-même. CORS ne protège pas contre un appel direct (curl/Postman) — il ne concerne que les appels JavaScript faits depuis un navigateur, depuis une autre origine.
  
-var app = builder.Build();+===== 6. Étape 4 — Base SQLite : emplacement persistant et droits =====
  
-app.UseCors("AllowAngularApp");+**Deuxième étape critique.** Si le chemin de la base pointe (par défaut ou par erreur de configuration) vers un dossier à l'intérieur du dossier de déploiement, deux problèmes surviennent : la base est effacée à chaque mise à jour (``robocopy /MIR``), et le compte du pool IIS peut ne pas avoir les droits d'écriture nécessaires.
  
-// ... app.MapControllers(); etc. +==== 6.1 Choisir un emplacement en dehors du dossier de déploiement ====
-</code>+
  
-> **Remplacer l'URL par la vraie origine de production** de l'Angular (protocole + domaine + port exact). Une politique CORS trop permissive (``AllowAnyOrigin``) est déconseillée dès lors que l'API traite des données de questionnaire.+Un dossier **frère** du dossier de déploiement (ex. ``C:\RemoteApp\Formulaire-Data`` à côté de ``C:\RemoteApp\Formulaire``) convient parfaitement : un ``robocopy /MIR`` ciblant uniquement ``Formulaire`` ne le touche pas. Un chemin totalement séparé (ex. ``D:\Data\...``) fonctionne tout aussi bien.
  
-===== 6. Étape 4 — Préparer un emplacement persistant pour la base SQLite =====+Si le code suit le pattern de ce projet (``Storage:DatabasePath`` lu depuis la configuration, avec ``Directory.CreateDirectory`` automatique), il suffit de renseigner ce chemin dans ``appsettings.Production.json`` (étape 8) — aucune modification de code nécessaire.
  
-**C'est l'étape la plus importante à ne pas manquer sur ce projet.** Si la chaîne de connexion SQLite par défaut (souvent ''Data Source=app.db'' dans ''appsettings.json'') pointe vers un chemin relatif, le fichier se crée dans le dossier de l'application — c'est-à-dire exactement le dossier qui sera écrasé/synchronisé à chaque redéploiement. Résultat : toutes les réponses au questionnaire disparaissent au prochain déploiement.+==== 6.2 Donner les droits d'écriture au compte du pool IIS ====
  
-  * Créer un dossier dédié, en dehors de tout dossier de déploiement, ex. ''D:\Data\questionnaire-api\''. +Identifiez d'abord l'identité du pool (souvent ``ApplicationPoolIdentity`` par défaut, ce qui correspond au compte virtuel ``IIS AppPool\<NomDuPool>`` et qui est automatiquement membre du groupe local ``IIS_IUSRS``) :
-  * Dans ''appsettings.Production.json'' (créé s'il n'existe pas encore, à côté d'''appsettings.json'' dans le projet), définir un chemin absolu :+
  
-<code json> +<code powershell> 
-{ +Import-Module WebAdministration 
-  "ConnectionStrings": { +Get-ItemProperty "IIS:\AppPools\<NomDuPool>" -Name processModel.identityType
-    "DefaultConnection": "Data Source=D:\\Data\\questionnaire-api\\app.db" +
-  } +
-}+
 </code> </code>
  
-  * S'assurer que le code lit bien cette clé de configuration (``builder.Configuration.GetConnectionString("DefaultConnection")``) plutôt qu'une valeur en dur. +Puis donnez les droits sur le dossier de données (le groupe ``IIS_IUSRS`` couvre le cas ``ApplicationPoolIdentity`` par défaut ; si l'identité est un compte de service spécifique, remplacez ``IIS_IUSRS`` par ce compte) : 
-  * Donner au compte du pool d'application IIS (par défaut ''IIS AppPool\<NomDuPool>'') les droits de **lecture et écriture** sur ''D:\Data\questionnaire-api\'' (clic droit sur le dossier → Propriétés → Sécurité → Ajouter → saisir ''IIS AppPool\NomDuPool'' → Modifier). + 
-  * Si des migrations EF Core doivent s'appliquer au démarrage (``dbContext.Database.Migrate()``), vérifier qu'elles s'exécutent bien contre ce chemin en production, et prévoir une sauvegarde du fichier ''app.db'' avant toute mise à jour du schéma.+<code powershell> 
 +icacls "C:\RemoteApp\Formulaire-Data" /grant "IIS_IUSRS:(OI)(CI)M" 
 +</code>
  
-> **Sauvegardes :** un fichier SQLite se sauvegarde simplement en copiant le fichier ''.db'' (idéalement pool arrêté, ou via l'outil ''backup'' de SQLite pour une copie à chaud cohérente). Mettre en place une tâche planifiée de copie régulière de ''D:\Data\questionnaire-api\app.db'' vers un emplacement de sauvegarde.+> **Comment on a isolé ce problème en pratique :** l'application plantait sous IIS (503 en boucle) mais fonctionnait parfaitement lancée manuellement avec ``dotnet .\VotreApp.dll`` depuis une session Administrateur — la différence entre les deux étant précisément les droits du compte utilisé. Lancer l'app manuellement est le test le plus rapide pour distinguer un problème de code d'un problème de droits/environnement IIS (voir étape 11).
  
 ===== 7. Étape 5 — Publier l'application ===== ===== 7. Étape 5 — Publier l'application =====
Ligne 104: Ligne 135:
 </code> </code>
  
-Le dossier ''publish/'' contient les DLL, les dépendances (dont les assemblies Stimulsoft.PDF.Forms), et un ''web.config'' généré automatiquement configurant le module ANCM. +Vérifiez que ``appsettings.Production.json`` et le fichier de licence Stimulsoft (si utilisé par fichier) sont bien inclus dans ce dossier.
- +
-> Vérifier que ''appsettings.Production.json'' (étape 4) et, le cas échéant, ''license.key'' (étape 8) sont bien présents dans ce dossier publié — les fichiers de configuration/licence ne sont inclus que s'ils sont correctement référencés dans le ''.csproj'' (propriété « Copy to Output Directory »).+
  
 ===== 8. Étape 6 — Copier les fichiers vers le serveur ===== ===== 8. Étape 6 — Copier les fichiers vers le serveur =====
- 
-Copier le dossier ''publish/'' vers un dossier dédié, par exemple ''D:\Sites\questionnaire-api\'' — bien distinct du dossier de données créé à l'étape 4. 
  
 <code bash> <code bash>
-robocopy publish D:\Sites\questionnaire-api /MIR+robocopy publish C:\RemoteApp\Formulaire /MIR
 </code> </code>
- 
-> **Rappel :** ''/MIR'' supprime côté serveur tout ce qui n'est pas dans le dossier source. Comme la base SQLite est désormais hors de ce dossier (étape 4), elle ne sera jamais affectée par cette commande — c'est précisément l'objectif. 
  
 ===== 9. Étape 7 — Créer le site dans IIS Manager ===== ===== 9. Étape 7 — Créer le site dans IIS Manager =====
  
-  - Ouvrir IIS Manager → clic droit sur « Sites » → Ajouter un site web. +  - IIS Manager → « Sites » → Ajouter un site web. 
-  - Nom du site : ex. QuestionnaireAPI. +  - Nom du site : ex. ``Formulaire``. 
-  - Chemin d'accès physique : D:\Sites\questionnaire-api\. +  - Chemin d'accès physique : ``C:\RemoteApp\Formulaire\``. 
-  - Liaison (Binding) : type https de préférence dès le départ (voir étape 10 pour le certificat), nom d'hôte = ''api.mondomaine.local''. +  - Liaison : https, port 443, nom d'hôte = ``formulaire.mondomaine.fr`` (liaison par SNI si le certificat est partagé avec d'autres sites du serveur — cas fréquent avec un certificat wildcard). 
-  - Pool d'applications : dédié, en mode « No Managed Code ». +  - Pool d'applications dédié, en « No Managed Code ».
-  - Forcer explicitement l'environnement de production, pour être sûr qu'''appsettings.Production.json'' est bien utilisé et que les pages d'erreurs détaillées de développement sont désactivées — ajouter dans le web.config (dans la balise ''<aspNetCore>'') : +
- +
-<code xml> +
-<aspNetCore processPath="dotnet" arguments=".\QuestionnaireApi.dll" hostingModel="InProcess"> +
-  <environmentVariables> +
-    <environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Production" /> +
-  </environmentVariables> +
-</aspNetCore> +
-</code> +
   - Démarrer le site.   - Démarrer le site.
  
-===== 10. Étape 8 — Licence Stimulsoft et taille des requêtes =====+===== 10. Étape 8 — Configuration de production (appsettings.Production.json) =====
  
-==== 10.1 Déployer la clé de licence ====+Créez ou complétez ``C:\RemoteApp\Formulaire\appsettings.Production.json`` avec, au minimum, les origines CORS et le chemin de la base :
  
-Si le code charge la licence par fichier (``Stimulsoft.Base.StiLicense.LoadFromFile(path)``), vérifier que ce fichier ''license.key'' est bien copié dans ''publish/'' puis présent sur le serveur au chemin attendu. Si elle est chargée par code (clé en dur dans ''Program.cs''), rien à déployer séparément, mais vérifier qu'elle n'est pas accidentellement liée à un environnement de développement uniquement (ex. clé d'essai).+<code json> 
 +{ 
 +  "Cors": { 
 +    "Origins": [ "https://interface.mondomaine.fr" ] 
 +  }, 
 +  "Storage": { 
 +    "DatabasePath": "C:\\RemoteApp\\Formulaire-Data\\forms.db" 
 +  } 
 +} 
 +</code>
  
-==== 10.2 Taille maximale des requêtes ====+===== 11. Étape 9 — Vérification manuelle AVANT de tester via IIS =====
  
-La génération/soumission de formulaires PDF peut impliquer des payloads plus volumineux que la moyenne (documents, images intégrées). Si erreur 404.13 ou 413 :+**Ne testez pas directement via IIS en premier.** Lancer l'app manuellement isole immédiatement les problèmes de code/configuration des problèmes d'environnement IIS — c'est le test le plus rapide et le plus fiable de tout ce guide, et celui qui a permis de trancher le plus vite lors du déploiement réel.
  
-<code xml> +<code powershell> 
-<system.webServer> +cd C:\RemoteApp\Formulaire 
-  <security> +dotnet .\Logeas.Forms.Server.dll
-    <requestFiltering> +
-      <requestLimits maxAllowedContentLength="52428800" /> +
-      <!-- 50 Mo, à ajuster --> +
-    </requestFiltering> +
-  </security> +
-</system.webServer>+
 </code> </code>
  
-===== 11. Étape 9 — Configurer HTTPS =====+Vous devez voir les migrations EF Core s'exécuter, puis : 
 +<code> 
 +info: Microsoft.Hosting.Lifetime[0] 
 +      Application started. Press Ctrl+C to shut down. 
 +</code>
  
-  * Obtenir un certificat SSL pour ''api.mondomaine.local'' (interne ou public selon l'exposition). +Si ça plante ici (licence, connexion à la base, exception applicative), l'erreur s'affiche directement dans la console — corrigez avant de continuer. Si ça démarre correctement, **arrêtez le process (Ctrl+C)** avant de passer à l'étape suivante : le laisser tourner peut verrouiller le fichier SQLite ou entrer en conflit avec l'instance qu'IIS va lancer.
-  * IIS Manager → site → « Certificats de serveur » → importer le certificat, puis ajouter une liaison https sur le port 443. +
-  * Vérifier que ``app.UseHttpsRedirection()`` est bien présent dans ''Program.cs'' pour rediriger automatiquement le trafic HTTP restant vers HTTPS.+
  
-===== 12. Étape 10 — Pare-feu Windows =====+===== 12. Étape 10 — Tester via IIS (méthode fiable) ===== 
 + 
 +**Piège à éviter :** ``curl -H "Host: monapi.fr" https://localhost/`` change l'en-tête HTTP mais **pas le SNI** envoyé pendant la négociation TLS. Si le site est en liaison par nom d'hôte (cas courant avec un certificat partagé entre plusieurs sites), ce test peut échouer de façon incohérente (parfois ça "marche" par repli de schannel sur un certificat par défaut, parfois non, notamment après un ``iisreset``). 
 + 
 +**La bonne commande**, qui force le bon SNI tout en testant en local :
  
 <code powershell> <code powershell>
-New-NetFirewallRule -DisplayName "IIS HTTPS API" -Direction Inbound -Protocol TCP -LocalPort 443 -Action Allow +curl.exe -v -k --resolve formulaire.mondomaine.fr:443:127.0.0.1 https://formulaire.mondomaine.fr/
-New-NetFirewallRule -DisplayName "IIS HTTP API" -Direction Inbound -Protocol TCP -LocalPort 80 -Action Allow+
 </code> </code>
  
-===== 13. Étape 11 — Tester le déploiement =====+Un ``404`` sur ``/`` est normal et attendu pour une API sans route racine — c'est le signe que l'app tourne. Testez ensuite une vraie route de vos contrôleurs pour confirmer complètement.
  
-  - Depuis un poste : ''https://api.mondomaine.local/swagger'' (si Swagger est activé pour un test ponctuel) ou un endpoint connu de l'API doit répondre. +Puis testez depuis l'extérieur (sans ``--resolve`` ni ``-k``, depuis n'importe quel poste) pour valider le DNS public et le certificat de bout en bout :
-  - Depuis l'application Angular réelle (pas juste Postman) : vérifier qu'un appel vers l'API ne remonte pas d'erreur CORS dans la console navigateur (message typique : « has been blocked by CORS policy »). Si c'est le cas, revérifier l'origine exacte configurée à l'étape 5 (protocole, casse du domaine, port). +
-  - Générer un formulaire PDF de test de bout en bout, en conditions réelles depuis l'Angular. +
-  - Vérifier que la base SQLite se remplit bien au bon endroit (''D:\Data\questionnaire-api\app.db'' grossit après une soumission). +
-  - Redémarrer le serveur et vérifier la reprise automatique du site.+
  
-===== 14. Étape 12 — Logs et diagnostic =====+<code powershell> 
 +curl.exe -v https://formulaire.mondomaine.fr/ 
 +</code>
  
-Activer temporairement les logs stdout dans le web.config en cas de problème :+Enfin, testez depuis l'application Angular réelle, dans un navigateur, pour valider l'absence d'erreur CORS.
  
-<code xml> +===== 13. Étape 11 — Diagnostiquer une erreur 500/503 (méthode complète) =====
-<aspNetCore processPath="dotnet" arguments=".\QuestionnaireApi.dll" +
-            stdoutLogEnabled="true" stdoutLogFile=".\logs\stdout" +
-            hostingModel="InProcess" /> +
-</code>+
  
-Créer le sous-dossier ''logs\'' avec droits d'écriture pour le pool d'application. Consulter aussi l'Observateur d'événements Windows (Journaux Windows → Application) pour les erreurs de démarrage du module ANCM. Repasser ''stdoutLogEnabled'' à ''false'' une fois le diagnostic terminé.+Si malgré tout l'application ne démarre pas sous IIS, suivez cette séquence dans l'ordre — c'est celle qui a permis de résoudre le déploiement réel, étape par étape, sans deviner :
  
-===== 15. Étape 13 — Sécuriser l'API =====+  - **Le 500 générique masque le détail par défaut pour toute requête "distante".** Testez en local avec le bon SNI (étape 10) pour obtenir la page d'erreur IIS détaillée plutôt qu'une page générique. 
 +  - **Lisez le code d'erreur précis** dans la page détaillée (500.19, 500.30, 500.31, etc.) — chacun a une cause différente (voir annexe B). 
 +  - **Observateur d'événements Windows → Journaux Windows → Application.** Deux sources à distinguer : 
 +    * ``IIS AspNetCore Module V2`` : erreurs de démarrage du module lui-même (runtime introuvable, échec catastrophique de chargement). 
 +    * ``.NET Runtime`` : exceptions applicatives non gérées, avec pile d'appels — c'est celle-ci qui donne la vraie cause si l'app plante après avoir démarré. 
 +  - **Lancez l'app manuellement** (``dotnet .\VotreApp.dll`` depuis le dossier de déploiement) — voir étape 9. Si ça fonctionne manuellement mais pas sous IIS, la cause est presque toujours l'identité du pool (droits NTFS) ou une variable d'environnement absente sous IIS. 
 +  - **Vérifiez l'état du pool** (``Get-WebAppPoolState -Name "<Pool>"``) — un pool ``Stopped`` peut résulter de la protection contre les échecs rapides (Rapid-Fail Protection) après plusieurs plantages successifs pendant les tests ; ``Start-WebAppPool`` le redémarre, mais ne résout pas la cause sous-jacente. 
 +  - **Activez les logs stdout** (``stdoutLogEnabled="true"`` dans le web.config) si aucune des étapes précédentes n'a suffi — mais vérifiez d'abord que le compte du pool a les droits d'écriture sur le dossier ``logs\`` (``icacls ... /grant "IIS_IUSRS:(OI)(CI)M"``), sinon aucun fichier n'apparaîtra malgré l'activation.
  
-  * Confirmer que Swagger/OpenAPI (s'il est présent dans le projet) est bien désactivé ou protégé en production — ne pas l'exposer publiquement sans authentification. +===== 14. Étape 12 — Sécuriser l'API =====
-  * Restreindre la politique CORS à l'origine exacte de l'Angular (étape 5), jamais ``AllowAnyOrigin`` en production. +
-  * Restreindre les droits NTFS de ''D:\Sites\questionnaire-api\'' et ''D:\Data\questionnaire-api\'' aux seuls comptes nécessaires (déploiement, pool d'application). +
-  * Ne jamais committer ''appsettings.Production.json'' ou ''license.key'' dans un dépôt Git public — les gérer comme des secrets de déploiement. +
-  * Valider côté serveur toutes les entrées reçues de l'Angular, même si elles sont déjà validées côté client.+
  
-===== 16. Étape 14 — Procédure de mise à jour de l'application =====+  * Swagger/OpenAPI désactivé ou protégé en production. 
 +  * CORS restreint à l'origine exacte de l'Angular, jamais ``AllowAnyOrigin`` en production sur un endpoint qui écrit des données. 
 +  * Droits NTFS restreints sur le dossier de déploiement et le dossier de données. 
 +  * Aucun secret (connection strings, licence) committé dans un dépôt Git public. 
 +  * Validation côté serveur de toutes les entrées, même déjà validées côté client.
  
-  - Sur le poste de build : ''dotnet publish -c Release -o ./publish''. +===== 15. Étape 13 — Procédure de mise à jour =====
-  - Optionnel mais recommandé avant une mise à jour de schéma : copier ''D:\Data\questionnaire-api\app.db'' vers un emplacement de sauvegarde. +
-  - Déposer un ''app_offline.htm'' à la racine de ''D:\Sites\questionnaire-api\'' pour libérer proprement les fichiers verrouillés. +
-  - ''robocopy publish D:\Sites\questionnaire-api /MIR'' (la base de données, hors de ce dossier, n'est pas affectée). +
-  - Supprimer ''app_offline.htm''. +
-  - Vérifier (étape 11) que l'API répond correctement et que les données existantes sont toujours présentes.+
  
-===== 17. Checklist finale =====+  - ``dotnet publish -c Release -o ./publish``. 
 +  - Sauvegarde de la base SQLite avant toute mise à jour de schéma (simple copie du fichier ``.db``, pool arrêté ou via l'outil ``backup`` de SQLite pour une copie à chaud cohérente). 
 +  - Déposer ``app_offline.htm`` à la racine du site pour libérer les fichiers verrouillés. 
 +  - ``robocopy publish C:\RemoteApp\Formulaire /MIR`` (la base, hors de ce dossier, n'est pas affectée). 
 +  - Supprimer ``app_offline.htm``. 
 +  - Revérifier avec la méthode de l'étape 10 (pas juste ``curl https://localhost/`` sans ``--resolve``). 
 + 
 +===== 16. Checklist finale (exhaustive) =====
  
 ^ Élément ^ Statut ^ ^ Élément ^ Statut ^
 | Rôle IIS installé | ☐ | | Rôle IIS installé | ☐ |
-| .NET 8 Hosting Bundle installé, AspNetCoreModuleV2 vérifié | ☐ |+| .NET 8 Hosting Bundle installé | ☐ | 
 +| **Vérifié** : Test-Path aspnetcorev2.dll = True | ☐ | 
 +| **Vérifié** : dotnet --list-runtimes affiche Microsoft.AspNetCore.App 8.x | ☐ | 
 +| Visual C++ Redistributable x64 installé/réparé | ☐ | 
 +| Redémarrage complet du serveur effectué après ces installations | ☐ |
 | Politique CORS configurée avec l'origine exacte de l'Angular | ☐ | | Politique CORS configurée avec l'origine exacte de l'Angular | ☐ |
 | Dossier de données SQLite créé hors du dossier de déploiement | ☐ | | Dossier de données SQLite créé hors du dossier de déploiement | ☐ |
-| Chaîne de connexion pointant vers ce dossier (appsettings.Production.json) | ☐ | +| Droits NTFS (IIS_IUSRS ou identité du pool) sur ce dossier | ☐ | 
-| Droits NTFS du pool d'application sur le dossier de données | ☐ | +| appsettings.Production.json déployé (Cors:Origins, Storage:DatabasePath) | ☐ | 
-| Application publiée (dotnet publish -c Release) | ☐ | +| license.key présent sur le serveur si chargement par fichier | ☐ | 
-| Fichiers copiés sur le serveur (web.config inclus) | ☐ | +| Application testée manuellement (dotnet .\App.dll) avant IIS | ☐ | 
-| license.key présent sur le serveur si utilisé | ☐ | +| Process manuel bien arrêté (Ctrl+C) avant test IIS | ☐ | 
-| Limite de taille des requêtes ajustée si besoin | ☐ | +| Site créé dans IIS, pool en « No Managed Code » | ☐ | 
-| Site créé dans IIS, pool en « No Managed Code », ASPNETCORE_ENVIRONMENT=Production | ☐ | +| Certificat SSL/binding SNI configuré | ☐ |
-| Certificat SSL installé, binding https configuré | ☐ |+
 | Pare-feu : ports 80/443 ouverts | ☐ | | Pare-feu : ports 80/443 ouverts | ☐ |
 +| Test IIS via --resolve (pas -H Host seul) réussi (404 sur / = OK) | ☐ |
 +| Test depuis l'extérieur (DNS public) réussi | ☐ |
 | Appel depuis l'Angular réel sans erreur CORS | ☐ | | Appel depuis l'Angular réel sans erreur CORS | ☐ |
-| Génération de PDF testée de bout en bout | ☐ | +| Une vraie route API testée avec succès | ☐ | 
-| Base SQLite vérifiée au bon emplacement, hors dossier de déploiement | ☐ |+| stdoutLogEnabled repassé à false après diagnostic | ☐ |
 | Sauvegarde de la base SQLite planifiée | ☐ | | Sauvegarde de la base SQLite planifiée | ☐ |
-| Swagger désactivé/protégé en production | ☐ | 
  
-===== 18. Annexe — Dépannage rapide =====+===== 17. Annexe A — Table de décision : quel symptôme, quelle cause ===== 
 + 
 +^ Symptôme observé ^ Cause la plus probable ^ Où vérifier ^ 
 +| 500 générique, masque tout détail | Requête traitée comme "distante" par IIS | Retester en local avec --resolve (étape 10) | 
 +| 500.19, code 0x8007000d | Module ANCM non reconnu par IIS (Hosting Bundle absent/incomplet) | Test-Path aspnetcorev2.dll | 
 +| 500.31 "Failed to load ASP.NET Core runtime" | Runtime .NET absent ou bitness du pool incohérente | dotnet --list-runtimes + réglage "32 bits" du pool | 
 +| Erreur générique 0x8000ffff au démarrage du module | VC++ Redistributable manquant ou non finalisé | Réinstaller vc_redist.x64.exe + redémarrage complet | 
 +| 503 "Application Shutting Down" en boucle, mais l'app tourne manuellement | Droits NTFS insuffisants pour l'identité du pool (souvent sur le dossier de données) | icacls sur le dossier de données, identité du pool | 
 +| Connexion TLS reset par intermittence en testant avec curl -H Host | SNI non pris en compte par curl (-H Host ne suffit pas) | Utiliser --resolve à la place | 
 +| Aucun fichier stdout créé malgré stdoutLogEnabled=true | Droits d'écriture manquants sur le dossier logs\ | icacls sur le dossier logs | 
 +| Erreur CORS dans la console navigateur | Origine absente ou incorrecte dans Cors:Origins | Vérifier appsettings.Production.json, protocole/domaine/port exacts | 
 +| Les données disparaissent après une mise à jour | Base SQLite encore dans le dossier de déploiement écrasé par /MIR | Déplacer vers un dossier persistant hors déploiement | 
 + 
 +===== 18. Annexe B — Dépannage rapide (référence courte) =====
  
 ^ Symptôme ^ Piste de résolution ^ ^ Symptôme ^ Piste de résolution ^
-| Erreur CORS dans la console navigateur | Vérifier l'origine exacte déclarée dans WithOrigins (protocole, domaine, port), et que app.UseCors(...) est bien appelé avant app.MapControllers(). | +| Erreur 500.19 | web.config invalide ou Hosting Bundle absent — vérifier Test-Path du module ANCM. | 
-| Les données disparaissent après un déploiement | La base SQLite était encore dans le dossier de déploiement écrasé par /MIR — la déplacer vers un dossier persistant (étape 4) et restaurer depuis une sauvegarde si disponible. | +| Erreur 500.31 | Runtime absent ou bitness du pool incohérente — vérifier dotnet --list-runtimes et le réglage 32 bits. | 
-| Erreur d'accès refusé sur le fichier .db | Droits NTFS insuffisants pour le compte IIS AppPool\<NomDuPool> sur le dossier de données. | +| Event ID 1010, code 0x8000ffff | VC++ Redistributable manquant — réinstaller puis redémarrer complètement le serveur. | 
-| Erreur HTTP 500.19 | web.config invalide ou Hosting Bundle absent. | +| 503 en boucle, app OK en manuel | Droits NTFS du compte du pool sur le dossier de données. | 
-| Erreur HTTP 500.30 (in-process start failure) | Activer stdoutLogEnabled et consulter l'Observateur d'événements Windows pour l'exception exacte (souvent une erreur de connexion à la base ou de licence Stimulsoft au démarrage). | +| curl : Recv failure / Connection was reset | Utiliser --resolve au lieu de -H Host pour un test SNI correct. | 
-| Erreur de licence Stimulsoft en production | license.key absent du dossier publié, ou chemin de chargement incorrect (voir étape 8.1). | +| Erreur de licence Stimulsoft en prod uniquement | license.key absent du dossier publié, chemin incorrect. | 
-| Erreur 404.13 / 413 sur soumission de formulaire | Augmenter maxAllowedContentLength (étape 8.2). |+| Erreur 404.13 / 413 sur soumission de formulaire | Augmenter maxAllowedContentLength dans le web.config. | 
 +| Erreur CORS dans la console navigateur | Origine incorrecte dans Cors:Origins, ou app.UseCors() appelé après app.MapControllers(). |
  
 ---- ----
  
-//Document préparé le 18 août 2026.//+//Document mis à jour le 18 août 2026, à partir d'un déploiement réel et de son dépannage complet.//