meta données pour cette page
Différences
Ci-dessous, les différences entre deux révisions de la page.
| Les deux révisions précédentesRévision précédenteProchaine révision | Révision précédente | ||
| certif:procedure:miseenplaceserveur:serveurdotnet [2026/08/18 14:16] – nicolas | certif:procedure:miseenplaceserveur:serveurdotnet [2026/09/30 14:39] (Version actuelle) – nicolas | ||
|---|---|---|---|
| Ligne 1: | Ligne 1: | ||
| - | ====== | + | |{{: |
| + | |**Document technique** |Rattaché à [[certif: | ||
| + | ====== | ||
| - | //Projet | + | //Version enrichie suite à un déploiement |
| - | //Topologie retenue | + | //Projet |
| ---- | ---- | ||
| 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 | + | Ce guide déploie |
| - | Comme pour tout ASP.NET Core, IIS héberge l' | + | **Schéma :** Angular (autre domaine) → appel HTTPS avec en-tête CORS → IIS (port 443, certificat SSL, liaison par nom d' |
| - | **Schéma :** Angular | + | **Ce qui a posé problème lors du premier déploiement, |
| + | | ||
| + | - Après installation du Hosting Bundle, IIS trouvait le module | ||
| + | - 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' | ||
| + | - Une fois ANCM capable de démarrer le process, l'app restait bloquée en **503 " | ||
| + | - 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 | ||
| - | **Point d' | + | ===== 2. Prérequis — à vérifier avant tout déploiement |
| - | ===== 2. Prérequis ===== | + | |
| - | + | * Le rôle « Serveur Web (IIS) » installé. | |
| - | | + | |
| - | * Le rôle « Serveur Web (IIS) » disponible via Gestionnaire de serveur. | + | |
| * Le SDK .NET 8 installé sur le poste/ | * Le SDK .NET 8 installé sur le poste/ | ||
| - | * Un nom de domaine ou sous-domaine dédié à l' | + | * Un nom de domaine dédié à l'API, distinct de celui de l' |
| - | * Un certificat SSL pour ce domaine | + | * Un certificat SSL couvrant |
| - | * Le fichier de licence Stimulsoft | + | * Le fichier |
| + | |||
| + | > **Nouveau, issu de l' | ||
| + | > - Le **.NET Hosting Bundle** (runtime + module IIS), PAS seulement le SDK ou le runtime " | ||
| + | > - Le **Visual C++ Redistributable 2015-2022 (x64)** — dépendance native souvent oubliée, cause de l' | ||
| ===== 3. Étape 1 — Installer le rôle IIS ===== | ===== 3. Étape 1 — Installer le rôle IIS ===== | ||
| <code powershell> | <code powershell> | ||
| - | # PowerShell, en tant qu' | ||
| Install-WindowsFeature -Name Web-Server -IncludeManagementTools | Install-WindowsFeature -Name Web-Server -IncludeManagementTools | ||
| </ | </ | ||
| - | ===== 4. Étape 2 — Installer le .NET Hosting Bundle ===== | + | ===== 4. Étape 2 — Installer le .NET Hosting Bundle |
| + | |||
| + | **C' | ||
| + | |||
| + | ==== 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:// | ||
| - | Indispensable pour qu'IIS sache exécuter une application .NET 8 (sans elle, erreur 500.19 ou 502.5). | + | ==== 4.2 Installer/ |
| - | * Télécharger le « ASP.NET Core Runtime 8.0 – Windows | + | Le Hosting Bundle |
| - | * Lancer l' | + | |
| - | * Redémarrer IIS : | + | |
| <code powershell> | <code powershell> | ||
| - | net stop was /y | + | Invoke-WebRequest -Uri " |
| - | net start w3svc | + | Start-Process " |
| </ | </ | ||
| - | * Vérifier dans IIS Manager → nœud serveur → « Modules » → présence | + | ==== 4.3 Redémarrer complètement le serveur (pas seulement IIS) ==== |
| + | |||
| + | <code powershell> | ||
| + | Restart-Computer | ||
| + | </ | ||
| + | |||
| + | 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' | ||
| + | |||
| + | ==== 4.4 Vérifier | ||
| + | |||
| + | Après le redémarrage, | ||
| + | |||
| + | <code powershell> | ||
| + | # Le module IIS doit exister physiquement | ||
| + | Test-Path " | ||
| + | # doit renvoyer True | ||
| + | |||
| + | # Le runtime .NET doit être listé | ||
| + | dotnet --list-runtimes | ||
| + | # doit afficher une ligne " | ||
| + | </ | ||
| + | |||
| + | 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, | ||
| ===== 5. Étape 3 — Configurer CORS dans le code ===== | ===== 5. Étape 3 — Configurer CORS dans le code ===== | ||
| - | Comme l'API et l' | + | Si votre code lit les origines |
| <code csharp> | <code csharp> | ||
| - | var builder = WebApplication.CreateBuilder(args); | ||
| - | |||
| builder.Services.AddCors(options => | builder.Services.AddCors(options => | ||
| { | { | ||
| - | options.AddPolicy(" | + | options.AddPolicy(" |
| { | { | ||
| - | policy.WithOrigins(" | + | policy.WithOrigins(builder.Configuration.GetSection("Cors: |
| .AllowAnyHeader() | .AllowAnyHeader() | ||
| .AllowAnyMethod(); | .AllowAnyMethod(); | ||
| - | // .AllowCredentials() si l'API utilise des cookies d' | ||
| }); | }); | ||
| }); | }); | ||
| + | // ... | ||
| + | app.UseCors(" | ||
| + | </ | ||
| - | // ... reste de la configuration ... | + | > **Rappel important :** l' |
| - | var app = builder.Build(); | + | ===== 6. Étape 4 — Base SQLite : emplacement persistant et droits ===== |
| - | app.UseCors(" | + | **Deuxième étape critique.** Si le chemin de la base pointe |
| - | // ... app.MapControllers(); | + | ==== 6.1 Choisir un emplacement en dehors du dossier de déploiement ==== |
| - | </ | + | |
| - | > **Remplacer l'URL par la vraie origine de production** de l' | + | Un dossier |
| - | ===== 6. Étape 4 — Préparer un emplacement persistant pour la base SQLite ===== | + | Si le code suit le pattern de ce projet (``Storage: |
| - | **C' | + | ==== 6.2 Donner les droits d'écriture |
| - | * Créer un dossier dédié, en dehors de tout dossier de déploiement, | + | Identifiez d'abord l'identité du pool (souvent ``ApplicationPoolIdentity`` par défaut, ce qui correspond au compte virtuel ``IIS AppPool\< |
| - | * Dans '' | + | |
| - | < | + | < |
| - | { | + | Import-Module WebAdministration |
| - | " | + | Get-ItemProperty |
| - | " | + | |
| - | } | + | |
| - | } | + | |
| </ | </ | ||
| - | * S' | + | Puis donnez les droits sur le dossier |
| - | * Donner au compte | + | |
| - | * Si des migrations EF Core doivent s' | + | <code powershell> |
| + | icacls "C:\RemoteApp\Formulaire-Data" /grant " | ||
| + | </ | ||
| - | > **Sauvegardes | + | > **Comment on a isolé ce problème en pratique |
| ===== 7. Étape 5 — Publier l' | ===== 7. Étape 5 — Publier l' | ||
| Ligne 104: | Ligne 135: | ||
| </ | </ | ||
| - | Le dossier '' | + | Vérifiez |
| - | + | ||
| - | > Vérifier | + | |
| ===== 8. Étape 6 — Copier les fichiers vers le serveur ===== | ===== 8. Étape 6 — Copier les fichiers vers le serveur ===== | ||
| - | |||
| - | Copier le dossier '' | ||
| <code bash> | <code bash> | ||
| - | robocopy publish | + | robocopy publish |
| </ | </ | ||
| - | |||
| - | > **Rappel :** ''/ | ||
| ===== 9. Étape 7 — Créer le site dans IIS Manager ===== | ===== 9. Étape 7 — Créer le site dans IIS Manager ===== | ||
| - | - Ouvrir | + | - IIS Manager → « Sites » → Ajouter un site web. |
| - | - Nom du site : ex. QuestionnaireAPI. | + | - Nom du site : ex. ``Formulaire``. |
| - | - Chemin d' | + | - Chemin d' |
| - | - Liaison | + | - Liaison : https, port 443, nom d' |
| - | - Pool d' | + | - Pool d' |
| - | - Forcer explicitement l' | + | |
| - | + | ||
| - | <code xml> | + | |
| - | < | + | |
| - | < | + | |
| - | < | + | |
| - | </ | + | |
| - | </ | + | |
| - | </ | + | |
| - 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: |
| - | Si le code charge la licence par fichier (``Stimulsoft.Base.StiLicense.LoadFromFile(path)``), vérifier que ce fichier '' | + | <code json> |
| + | { | ||
| + | " | ||
| + | " | ||
| + | }, | ||
| + | " | ||
| + | " | ||
| + | } | ||
| + | } | ||
| + | </code> | ||
| - | ==== 10.2 Taille maximale des requêtes | + | ===== 11. Étape 9 — Vérification manuelle AVANT de tester via IIS ===== |
| - | La génération/ | + | **Ne testez pas directement via IIS en premier.** Lancer l'app manuellement isole immédiatement les problèmes |
| - | < | + | < |
| - | < | + | cd C: |
| - | < | + | dotnet |
| - | < | + | |
| - | < | + | |
| - | <!-- 50 Mo, à ajuster --> | + | |
| - | </ | + | |
| - | </ | + | |
| - | </system.webServer> | + | |
| </ | </ | ||
| - | ===== 11. Étape 9 — Configurer HTTPS ===== | + | Vous devez voir les migrations EF Core s' |
| + | < | ||
| + | info: Microsoft.Hosting.Lifetime[0] | ||
| + | Application started. Press Ctrl+C to shut down. | ||
| + | </ | ||
| - | * Obtenir un certificat SSL pour '' | + | Si ça plante ici (licence, connexion à la base, exception applicative), |
| - | | + | |
| - | * Vérifier que ``app.UseHttpsRedirection()`` est bien présent dans '' | + | |
| - | ===== 12. Étape 10 — Pare-feu Windows | + | ===== 12. Étape 10 — Tester via IIS (méthode fiable) |
| + | |||
| + | **Piège à éviter :** ``curl -H "Host: monapi.fr" | ||
| + | |||
| + | **La bonne commande**, qui force le bon SNI tout en testant en local : | ||
| <code powershell> | <code powershell> | ||
| - | New-NetFirewallRule | + | curl.exe |
| - | New-NetFirewallRule -DisplayName "IIS HTTP API" -Direction Inbound -Protocol TCP -LocalPort 80 -Action Allow | + | |
| </ | </ | ||
| - | ===== 13. Étape 11 — Tester | + | Un ``404`` sur ``/`` est normal et attendu pour une API sans route racine |
| - | - Depuis un poste : '' | + | Puis testez depuis l'extérieur |
| - | | + | |
| - | - Générer un formulaire PDF de test de bout en bout, en conditions réelles depuis l' | + | |
| - | - Vérifier que la base SQLite se remplit bien au bon endroit ('' | + | |
| - | - Redémarrer le serveur et vérifier la reprise automatique du site. | + | |
| - | ===== 14. Étape 12 — Logs et diagnostic ===== | + | <code powershell> |
| + | curl.exe -v https:// | ||
| + | </ | ||
| - | Activer temporairement les logs stdout | + | Enfin, testez depuis l' |
| - | <code xml> | + | ===== 13. Étape 11 — Diagnostiquer une erreur 500/503 (méthode complète) ===== |
| - | < | + | |
| - | stdoutLogEnabled=" | + | |
| - | hostingModel=" | + | |
| - | </ | + | |
| - | Créer le sous-dossier '' | + | Si malgré tout l' |
| - | ===== 15. Étape 13 — Sécuriser | + | - **Le 500 générique masque le détail par défaut pour toute requête " |
| + | - **Lisez le code d' | ||
| + | - **Observateur d' | ||
| + | * ``IIS AspNetCore Module V2`` : erreurs de démarrage du module lui-même (runtime introuvable, | ||
| + | * ``.NET Runtime`` : exceptions applicatives non gérées, avec pile d' | ||
| + | - **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' | ||
| + | - **Vérifiez l' | ||
| + | - **Activez les logs stdout** (``stdoutLogEnabled=" | ||
| - | * Confirmer que Swagger/ | + | ===== 14. Étape 12 — Sécuriser |
| - | * Restreindre la politique CORS à l' | + | |
| - | * Restreindre les droits NTFS de '' | + | |
| - | * Ne jamais committer '' | + | |
| - | * 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/ |
| + | * CORS restreint | ||
| + | * 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 : '' | + | ===== 15. Étape 13 — Procédure |
| - | - Optionnel mais recommandé avant une mise à jour de schéma : copier '' | + | |
| - | - Déposer un '' | + | |
| - | - '' | + | |
| - | - Supprimer '' | + | |
| - | - 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 ./ |
| + | - Sauvegarde de la base SQLite avant toute mise à jour de schéma (simple copie du fichier ``.db``, pool arrêté ou via l' | ||
| + | - Déposer ``app_offline.htm`` à la racine du site pour libérer les fichiers verrouillés. | ||
| + | - ``robocopy publish C: | ||
| + | - Supprimer ``app_offline.htm``. | ||
| + | - Revérifier avec la méthode de l' | ||
| + | |||
| + | ===== 16. Checklist finale | ||
| ^ É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é/ | ||
| + | | Redémarrage complet du serveur effectué après ces installations | ||
| | Politique CORS configurée avec l' | | Politique CORS configurée avec l' | ||
| | 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é |
| - | | Droits NTFS du pool d' | + | | appsettings.Production.json déployé |
| - | | Application publiée | + | | license.key présent |
| - | | Fichiers copiés | + | | Application testée manuellement (dotnet |
| - | | 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 |
| - | | Certificat SSL installé, | + | |
| | 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' | ||
| | Appel depuis l' | | Appel depuis l' | ||
| - | | Génération de PDF testée | + | | Une vraie route API testée |
| - | | Base SQLite vérifiée au bon emplacement, | + | | stdoutLogEnabled repassé à false après diagnostic |
| | Sauvegarde de la base SQLite planifiée | ☐ | | | Sauvegarde de la base SQLite planifiée | ☐ | | ||
| - | | Swagger désactivé/ | ||
| - | ===== 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, | ||
| + | | 500.19, code 0x8007000d | Module ANCM non reconnu par IIS (Hosting Bundle absent/ | ||
| + | | 500.31 " | ||
| + | | 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 " | ||
| + | | 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' | ||
| + | | Erreur CORS dans la console navigateur | Origine absente ou incorrecte dans Cors: | ||
| + | | 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 | ||
| ^ Symptôme ^ Piste de résolution ^ | ^ Symptôme ^ Piste de résolution ^ | ||
| - | | Erreur | + | | Erreur |
| - | | Les données disparaissent après un déploiement | + | | Erreur 500.31 |
| - | | Erreur d' | + | | Event ID 1010, code 0x8000ffff | VC++ Redistributable manquant — réinstaller puis redémarrer complètement |
| - | | Erreur HTTP 500.19 | + | | 503 en boucle, app OK en manuel |
| - | | Erreur HTTP 500.30 (in-process start failure) | Activer stdoutLogEnabled et consulter l' | + | | curl : Recv failure / Connection was reset | Utiliser --resolve au lieu de -H Host pour un test SNI correct. | |
| - | | Erreur de licence Stimulsoft en production | + | | Erreur de licence Stimulsoft en prod uniquement |
| - | | Erreur 404.13 / 413 sur soumission de formulaire | Augmenter maxAllowedContentLength (étape 8.2). | | + | | Erreur 404.13 / 413 sur soumission de formulaire | Augmenter maxAllowedContentLength |
| + | | Erreur CORS dans la console navigateur | Origine incorrecte dans Cors: | ||
| ---- | ---- | ||
| - | // | + | // |