Notes de version 4.0
Notes des versions 4.0.x du framework Lagoon, de la plus récente à la plus ancienne. Les versions marquées ⚠ contiennent des changements de rupture. Les autres versions sont listées sur la page Notes de version.
Version 4.0.1 ⚠
Version corrective : durcissement de l'authentification SAML2 et support de plusieurs fournisseurs d'identité, éditeur HTML Jodit 4 et assainissement serveur des CGU, correctifs de sécurité des dépendances.
Authentification SAML2 ⚠
La lecture des réponses SAML2 est durcie. Les contrôles suivants sont désormais toujours appliqués :
- la signature doit porter sur le nœud qui la contient, avec un identifiant unique (protection contre le « signature wrapping ») ;
- le statut de la réponse doit être un succès, et la réponse ne doit contenir qu'une seule assertion ;
- l'assertion doit porter une confirmation
beareravec une date limite de livraison (NotOnOrAfter) et, sousRequireDestination, leRecipientde l'application ; - seules les valeurs signées satisfont les contrôles : la
Destinationet l'InResponseTod'une enveloppe non signée ne suffisent plus ; - les signatures SHA-1 et les transformations non prévues par SAML sont refusées ;
- les documents contenant une DTD sont refusés, et la taille décompressée (3 Mo) et la profondeur des documents sont bornées.
La requête d'authentification pose un cookie de corrélation : la réponse n'est acceptée que par le navigateur qui a envoyé la requête. En HTTP, l'application poste de nouveau la réponse vers elle-même, ce qui ajoute un aller-retour.
Les métadonnées du fournisseur d'identité doivent être téléchargées en HTTPS (RequireHttpsMetadata). Elles sont conservées 6 heures par emplacement. Si un nouveau téléchargement échoue, la version précédente reste utilisée pendant IdpMetadataFallbackDuration (1 jour par défaut), puis l'authentification échoue jusqu'au prochain téléchargement réussi.
Les valeurs d'attribut typées string, quel que soit le préfixe (xs:string, xsd:string de Shibboleth/OpenSAML…), et tous les AttributeStatement de l'assertion sont maintenant lus.
Le relay state est lié au nom du schéma d'authentification : les authentifications commencées avant la mise à jour doivent être relancées.
Plusieurs schémas SAML2 sont supportés, chacun avec son fournisseur d'identité :
- la nouvelle option
CallbackPath(/samlpar défaut) donne à chaque schéma son chemin de retour et l'URL de ses métadonnées ; les chemins sont contrôlés au démarrage de l'application ; - la nouvelle option
AllowedNameIdDomainslimite les domaines des utilisateurs qu'un fournisseur d'identité peut connecter : les comptes sont retrouvés par e-mail, quel que soit le schéma.IsAllowedLoginapplique la même règle dans une recherche de compte personnalisée ; - une assertion est identifiée par son émetteur et son identifiant dans le cache anti-rejeu ;
- les métadonnées partagées par plusieurs schémas respectent la durée de repli de chacun.
Action :
- Un fournisseur d'identité qui signe en SHA-1 est refusé : passez-le en SHA-256, ou posez
options.Validation.AllowSha1 = true. - Des métadonnées téléchargées par une URL
http://sont refusées : passez àhttps://ou à un fichier local, ou posezoptions.RequireHttpsMetadata = falsepour un fournisseur d'identité de développement. - Un code qui lit lui-même une réponse avec
SamlResponse(ouSamlResponse.FromHttpRequest) sansForRequest(…)échoue désormais : fournissez l'identifiant de la requête et l'URL de retour avecValidation.ForRequest(requestId, acsUrl), ou désactivez explicitementRequireDestination/RequireInResponseTo. - ⚠ Une application qui déclare déjà plusieurs schémas SAML2 (les suivants étaient inopérants) ne démarre plus : donnez à chacun, sauf un, son
CallbackPath(par exemple/saml-partner), et enregistrez la nouvelle URL auprès de son fournisseur d'identité. Une application à un seul schéma n'a rien à changer. - Avec plusieurs fournisseurs d'identité, renseignez
AllowedNameIdDomainssur ceux qui ne gèrent pas tous les utilisateurs de l'application.
Voir Authentification SAML2 pour le détail des contrôles.
Éditeur HTML et CGU : correctifs de sécurité ⚠
LgHtmlEditor passe de Jodit 3.24.5 à Jodit 4.15.14, qui corrige plusieurs failles XSS de l'éditeur (collage de HTML, mode source, liens javascript:). L'assistant IA de Jodit 4 (boutons « AI Commands » et « AI Assistant ») est désactivé. Les composants de date (LgDateBox, LgMonthBox, LgWeekBox, LgYearBox) ne chargent plus le script de Jodit, qu'ils n'utilisaient pas.
Le nouveau service serveur LgHtmlSanitizer (Lagoon.Server) assainit un contenu HTML : il retire les scripts, les gestionnaires d'événements, les URL dangereuses, les formulaires et les cadres. Lagoon l'applique aux CGU, à l'écriture (SetEula) et à la lecture (GetAllEula, fichiers du mode partagé compris), sans modifier la base de données. Les clients téléchargent une fois de nouveau les CGU qu'ils avaient en cache, sans revalidation.
Action :
- Le constructeur de
LgEulaManagerreçoit un paramètreLgHtmlSanitizer: adaptez le code qui l'instancie directement ou qui en dérive. Une implémentation personnalisée deILgEulaManagerdoit assainir elle-même le contenu. - Vérifiez le rendu de vos CGU au format
.htm: les élémentsstyleet l'attributclasssont conservés, maismeta,title,scriptet les formulaires sont supprimés. - Assainissez côté serveur les autres contenus saisis avec
LgHtmlEditoravant de les afficher à d'autres utilisateurs.
Voir Assainissement HTML côté serveur et CGU.
Corrections
- Un
Prefix/SuffixsansPrefixType/SuffixTypeexplicite est de nouveau affiché comme du texte : depuis la 4.0.0, il était interprété comme un nom d'icône (Suffix="kg"n'affichait rien). Concerne les champs de saisie et les colonnes de grille qui exposent ces paramètres.
Sécurité
Cette version met à jour les dépendances suivantes :
- PuppeteerSharp 25.12.0 (Chrome 154, plus de 100 correctifs de sécurité du navigateur) : les applications utilisant
Lagoon.PDFavec téléchargement automatique du navigateur récupèrent la version corrigée ; celles qui fournissent leur propre Chrome/Chromium (Lagoon:Puppeteer:ExecutablePath, image Docker) doivent le mettre à jour. - MessagePack 3.1.10 : corrige GHSA-qhrr-8q5h-9q3h (CVE-2026-92707, amplification mémoire lors de la désérialisation en
object) et GHSA-fx6j-x9m5-vmrf (ReDoS avecAllowAssemblyVersionMismatch). Lagoon n'est pas directement exposé, mais les hubs SignalR applicatifs acceptant des paramètresobjectl'étaient. - Éditeur HTML (
LgHtmlEditor) : Jodit 4.15.14 : corrige les failles XSS et de pollution de prototype publiées en 2026 (dont GHSA-rxcw-mc6f-6hr3 et GHSA-w3xv-x3fm-59ph). - CGU assainies côté serveur : voir la section Éditeur HTML et CGU ci-dessus.
LgHtmlSanitizers'appuie sur HtmlSanitizer 9.2.1039 et AngleSharp 1.8.2, qui corrige GHSA-cgp3-27rh-pcp2 (mXSS à la sérialisation, non exploitable avec la configuration par défaut deLgHtmlSanitizer). - .NET 10.0.12 : alignement sur le Patch Tuesday de septembre 2026. Les correctifs sont dans le runtime et le SDK, pas dans les paquets : installez l'ASP.NET Core Runtime / Hosting Bundle 10.0.12 sur les serveurs (en particulier sous IIS, CVE-2026-69304) et le SDK 10.0.112 / 10.0.401 ainsi que Visual Studio à jour sur les postes et la CI (CVE-2026-58649, CVE-2026-69806, CVE-2026-69439, CVE-2026-71328, CVE-2026-69522). Les applications utilisant l'authentification SAML de
Lagoon.Serverdoivent en outre s'assurer que leurs serveurs n'exécutent pas un runtime antérieur à 10.0.10 (correctifsSystem.Security.Cryptography.Xmlde juillet 2026).
Dépendances
- Paquets ASP.NET Core, EF Core et
Microsoft.Extensions.*en 10.0.12 ; OpenIddict 7.7.1 ;Microsoft.IdentityModel.*8.23.0 ; MailKit 4.18.1 ;AWSSDK.S34.0.103.4. - La copie Lagoon du script d'authentification Blazor (
AuthenticationService.js) est alignée sur la version 10.0.12 du packageMicrosoft.AspNetCore.Components.WebAssembly.Authentication, y compris pour les applicationsnet8.0.
Version 4.0.0 ⚠
Version majeure : passage à .NET 10, refonte du rendu de la grille, OpenIddict 7 et nouvelle chaîne de build sans outil externe.
Suivez la page Migrer de 3.x vers 4.0 : prérequis, commande lgn update --allow-major --dotnet-fw, liste des actions manuelles, migration de la base de données et publication. Les sections ci-dessous décrivent chaque changement. Les mentions Action signalent ce que la mise à jour automatique ne fait pas.
Plateforme et dépendances
- Les applications ciblent .NET 10 (
net10.0,LangVersion12). Les modules sont publiés pournet8.0etnet10.0. .NET 6 n'est plus supporté. - Dépendances majeures : EF Core 10, OpenIddict 7 (depuis 4.10), MessagePack 3 (depuis 2.5).
- La montée de version nécessite
lgn4.0.5 ou supérieur et l'option--allow-major:lgn update --allow-major --dotnet-fw. appsettings.jsonest désormais facultatif au design-time (commandesdotnet ef).
Build : lgn n'est plus nécessaire pour compiler
Les ressources d'une application (CSS compilé depuis le SCSS, bundle JavaScript, icônes, dictionnaires fusionnés, pages d'index) sont générées par les tâches MSBuild du package Lagoon.Build. Elles remplacent l'appel à lgn build et le générateur de sources. Un poste de développement ou un agent de build n'a donc plus besoin de l'outil lgn : seul le SDK .NET est requis. lgn reste utile pour ses autres commandes (new, update…).
La compilation Release minifie le JavaScript avec Terser : elle nécessite node.js (Terser est installé automatiquement au premier usage). Le CSS est minifié en Release et reste lisible en Debug (propriété LagoonMinifyCss pour forcer l'un ou l'autre).
Grille
Rendu direct des cellules ⚠
La grille exécute désormais le fragment <Columns> une seule fois, sur un modèle de ligne, avant le premier rendu. Elle rend ensuite chaque cellule directement depuis l'état des colonnes, avec des accesseurs compilés. Le gain de performance est important, surtout sur les grandes pages.
Conséquence : la variable de contexte de <Columns Context="item"> désigne le modèle, pas la ligne affichée. Un paramètre de colonne qui la lisait (DisplayFormat="@(item.IsLate ? "N0" : "N2")", HideContent="@item.Masked"…) n'est plus réévalué par ligne. La casse est silencieuse : le code compile, mais toutes les lignes affichent la valeur du modèle.
- Dans les templates
CellContent/EditCellContent, lisez la ligne via le contexte de cellule, renommécell:cell.Itempour l'item de la ligne,cell.Valuepour la valeur liée. Les colonnes sans valeur (sélection, commande, édition, séparateur) exposent aussi@context.Item. - Pour un paramètre qui varie selon la ligne, utilisez sa variante
<Param>Selector(voir ci-dessous).
Il n'y a pas d'option globale : la grille repasse automatiquement à l'ancien rendu, en exécutant <Columns> pour chaque ligne, dès qu'une colonne visible porte une valeur sans expression. C'est le cas d'une valeur passée par Value="…" seul, sans @bind-Value ni ValueSelector. Les captures du contexte fonctionnent alors comme en 3.x, mais sans le gain de performance du rendu direct. Une telle colonne n'est ni triable ni filtrable, sauf avec un SortSelector / FilterSelector.
Action : la mise à jour convertit les cas simples en sélecteurs. Elle marque d'un LAGOON UNSUPPORTED les templates qui capturent la variable de <Columns>. Relisez les paramètres de colonne qui l'utilisent encore.
Sélecteurs par ligne
Chaque paramètre de colonne qui peut varier selon la ligne dispose d'une variante <Param>Selector, un délégué Func<TItem,…> évalué pour chaque ligne :
| Paramètre | Sélecteur | Colonne |
|---|---|---|
HideContent | HideContentSelector | toutes |
DisplayFormat | DisplayFormatSelector (affichage seul) | toutes |
CommandName | CommandNameSelector | LgGridCommandColumn |
CommandTooltip | CommandTooltipSelector | LgGridCommandColumn |
IconName | IconNameSelector | LgGridCommandColumn |
Prefix | PrefixSelector | LgGridNumericColumn |
Suffix | SuffixSelector | LgGridNumericColumn |
Min | MinSelector (édition seule) | LgGridDateColumn |
Max | MaxSelector (édition seule) | LgGridDateColumn |
<LgGridNumericColumn @bind-Value="item.Amount"
DisplayFormatSelector="i => i.IsLate ? "N0" : "N2""
PrefixSelector="i => i.Currency" />
<LgGridCommandColumn CommandNameSelector="i => i.CanValidate ? "Validate" : """ />
Règle : le sélecteur gagne. S'il est fourni, son résultat par ligne est utilisé ; le paramètre statique ne sert que de valeur par défaut quand aucun sélecteur n'est posé. CssClassSelector (additif) et CanEditSelector (combiné en ET) gardent leur sémantique propre. Au niveau de la grille, RowCanEditSelector (Func<TItem, bool>) autorise ou interdit l'édition d'une ligne entière. Sur une grille avec ligne de calcul, les sélecteurs de lecture sont appelés avec un item null : un sélecteur qui lit l'item doit gérer ce cas.
La mise à jour réécrit Xxx="@(context…)" en XxxSelector="@(context => …)", mais seulement quand le contexte s'appelle context et que la balise tient sur une ligne. Les autres cas sont à reprendre à la main.
Sélecteurs de valeur et d'expression
Ces sélecteurs sont des expressions (Expression<Func<TItem,…>>), pas des délégués : ils alimentent la valeur, le tri, le filtre, le groupement et l'export. Ils doivent rester traduisibles côté serveur sur une grille distante ([Queryable]). Ils sont figés une seule fois, au chargement de la colonne.
ValueSelector(Expression<Func<TItem,TColumnValue>>) : alternative à@bind-Value, sans capture de la variable de contexte. Il fournit une valeur complète (lecture, tri, filtre, groupe, export). Une colonne déclare soit@bind-ValuesoitValueSelector. Un chemin de membre simple (i => i.Amount) reste éditable et traduisible côté serveur. Une expression calculée (i => i.Qty * i.Price) rend la colonne lecture seule et limitée au tri/filtre local.SortSelector/GroupSelector/LambdaSelectorremplacentSortExpression/GroupExpression/LambdaExpression, désormais obsolètes. Poser unSortSelector(ou unLambdaSelector) active le tri, etLambdaSelectorsert aussi de clé de filtre par défaut.FilterSelector(colonnes filtrables) remplaceFilterExpression. Le poser active le filtre et fournit sa clé.WeightSelectorremplaceWeightExpression: poids de la moyenne pondérée (CalculationType="Average").
Une clé de type valeur est automatiquement « déboxée » pour rester traduisible côté serveur.
<LgGridNumericColumn TItem="Order" TColumnValue="decimal"
ValueSelector="o => o.Quantity * o.UnitPrice" Title="Total" />
<LgGridTextColumn @bind-Value="o.Reference"
SortSelector="o => o.CreatedOn" GroupSelector="o => o.Customer" />
La mise à jour transforme un Value="@(context…)" sans @bind-Value en ValueSelector et renomme les *Expression en *Selector. Une lambda écrite en clair est conservée, et le wrapper GetLambdaExpression(…) est remplacé par la lambda inline. Une forme @(GetLambdaExpression(…)), ou une expression lue dans une variable, reste à reprendre à la main. Sans @bind-Value, ajoutez TItem="…" si le compilateur le demande.
LgGridColumn scindée en LgGridTextColumn et LgGridCustomColumn ⚠
LgGridColumn est supprimée et remplacée par deux composants :
| Avant | Après | Quand |
|---|---|---|
<LgGridColumn @bind-Value="item.Name" /> | <LgGridTextColumn @bind-Value="item.Name" /> | La valeur liée est une string. |
<LgGridColumn @bind-Value="item.Reference" /> | <LgGridCustomColumn @bind-Value="item.Reference" /> | La valeur liée est d'un autre type (type métier, Guid…). |
LgGridTextColumn n'a plus qu'un paramètre de type (TItem, fourni par la grille) ; LgGridCustomColumn<TItem, TColumnValue> garde les deux. LgGridCustomColumn n'a pas de filtre : l'ancienne LgGridColumn imposait un filtre texte à toutes les valeurs. La cellule de filtre reste vide et un DefaultFilter éventuel est ignoré. Utilisez la colonne spécialisée du type, ou le template FilterCellContent. En contrepartie, LgGridCustomColumn applique DisplayFormat aux valeurs IFormattable.
La mise à jour convertit les colonnes. Elle s'appuie sur une analyse sémantique pour retrouver le type réel des valeurs liées. Une colonne non textuelle mal convertie provoque une erreur cannot convert 'X' to 'string'. Remplacez-la alors par la colonne spécialisée (LgGridNumericColumn, LgGridDateColumn, LgGridBooleanColumn, LgGridEnumColumn…) ou par LgGridCustomColumn. Un type de ligne non déduit laisse le marqueur LAGOON_SET_TItem_Here (erreur CS0246).
LgDynamicGridView supprimée ⚠
LgDynamicGridView et ses colonnes LgDynamicGrid…Column sont supprimées. Une grille dont le nombre de colonnes varie devient une LgGridView<TItem> typée. Les colonnes sont générées par une boucle @foreach / @for dans <Columns> et liées par indexeur sur la ligne (@bind-Value="@item.Absences[day]"). Une instance peuplée est passée par ItemModel pour fixer le nombre de colonnes. Voir Colonnes dynamiques.
Action : aucune conversion automatique. Remplacez chaque LgDynamicGridView à la main.
Nettoyage des paramètres de colonne
Les paramètres de filtre ne sont plus déclarés sur toutes les colonnes. Ils sont déplacés sur la base LgGridBaseFilterableColumn, dont héritent uniquement les colonnes qui exposent un filtre.
| Paramètre | Déclaré à partir de la 4.0 sur… | N'existe plus sur… |
|---|---|---|
AllowFilter, DefaultFilter, FilterExpression, FilterShowAllItems, FilterDisableSelection, FilterCellContent, MinSearchFilterCharacter | les colonnes filtrables (texte, numérique, date, heure, booléenne, énum, select, tagbox, summary…) | LgGridCustomColumn, LgGridColorColumn, LgGridImageColumn, LgGridCommandColumn, LgGridEditColumn, LgGridSelectionColumn, LgGridSeparatorColumn |
MaxLength | les colonnes texte et summary | toutes les autres colonnes |
InputDisplayFormat | LgGridNumericColumn (seule cellule à le lire) | toutes les autres colonnes, où il était sans effet |
Razor ne signale pas un attribut qui ne correspond à aucun paramètre du composant. Un MinSearchFilterCharacter ou un MaxLength restés sur une colonne qui ne les déclare pas lèvent une InvalidOperationException au rendu de la grille. La mise à jour les retire des colonnes concernées.
Masque de saisie et placeholder par colonne
La classe InputMaskOptions et le paramètre de colonne du même nom sont supprimés ([Obsolete(…, true)]). Les options du masque se déclarent directement sur la colonne, et chaque colonne n'expose que ce que son éditeur sait faire :
| Colonne | Paramètres |
|---|---|
Texte (LgGridTextColumn) | Placeholder, InputMask, InputMaskKind, InputMaskPlaceholder, AutoUnmask, ClearIncomplete (+ MaxLength) |
| Summary | Placeholder (+ MaxLength) |
| TagBox | les mêmes que Texte, plus ses propres paramètres d'étiquettes |
| Select, SelectMultiple, Enum, EnumList | Placeholder, InputMask, InputMaskKind, InputMaskPlaceholder |
| Numeric, Date, Month, Week, Year, Time | Placeholder |
| Custom, Image, Color, Boolean, Command, Edit, Selection, Separator | aucun |
InputMaskPlaceholder reste distinct de Placeholder. Il fixe le caractère qui remplit les positions vides du masque : avec # et le masque 99-99-99-99-99, le champ affiche ##-##-##-##-##. Placeholder reste le texte indicatif du champ vide. Il est disponible sur toutes les colonnes à masque, ainsi que sur LgTextBox, LgSelect, LgSelectMultiple, LgSuggestBox et LgTagBox.
Le masque ne s'applique qu'au mode texte simple de LgTextBox. LgSummaryBox et LgGridSummaryColumn perdent donc leurs paramètres de masque : leur éditeur est une zone multiligne, où le masque n'a jamais eu d'effet. LgTextFilterBox suit la même logique : sa propriété InputMaskOptions est remplacée par les paramètres séparés.
Le template FilterCellContent est réparé : il faisait planter le rendu de toute grille qui l'utilisait. Son contexte est FilterContext, dont la propriété Filter est lisible et modifiable.
Action : la mise à jour marque chaque InputMaskOptions="…" d'un LAGOON UNSUPPORTED. La valeur peut être construite n'importe où, elle n'est donc pas convertible automatiquement : déclarez les paramètres séparés sur la colonne.
Événements et méthodes
OnValueChangedevientOnValueChangingAsync(GridViewValueChangingEventArgs) : le gestionnaire renvoie unTask. La mise à jour adapte la signature. Action : unreturn;nu est marqué et doit renvoyerTask.CompletedTask.OnCellClickreçoit unGridViewCellClickEventArgs<TItem>typé.JsResizeColumnsAsyncetRefreshCssVariablesAsyncsont fusionnées dansAutoResizeColumnsAsync.
Nouveautés
-
Virtualisation des lignes (
Virtualize,VirtualizeItemSize) : seules les lignes visibles sont rendues, ce qui est utile avec de grandes pages (PageSize = 0). Elle est désactivée par défaut. On l'active par grille (Virtualize="true") ou pour toute l'application :Main.cs (Client)app.GridView(options => options.Virtualize = true);Elle ne s'applique pas à une grille groupée. Voir Virtualisation des lignes.
-
Cache des données locales filtrées et triées entre deux chargements de page.
-
Jeux de colonnes réutilisables (
LgGridColumnSet) : un composant peut regrouper des colonnes et être placé dans<Columns>. Les colonnes peuvent aussi être placées dans unLgGroupou unLgAuthorizeView.
Composants de saisie
Renommages (appliqués par la mise à jour) :
| Avant | Après |
|---|---|
LgSearchNavigation | LgNavigationBox (le paramètre Scrollable disparaît) |
LgInputRadioGroup / LgInputRadio | LgRadioGroup / LgRadio (Name → GroupName) |
OnChanging (LgCheckBox, LgToggle) | OnChangingAsync |
ChangingEventArgs | ChangingItemEventArgs |
<LgDateBox DateBoxKind="Week/Month/Year"> | LgWeekBox / LgMonthBox / LgYearBox |
LgOptionListItem TItemValue="…" | LgOptionListItem TValue="…" |
Nouveautés :
LgPasswordBox: champ de saisie de mot de passe.LgWeekBoxetLgMonthBox: saisie d'une semaine ou d'un mois, sur le modèle deLgYearBox(remplacentDateBoxKind).CascadingTypeParametersurLgSelectetLgSelectMultiple: le type de valeur est transmis aux options enfants, qui n'ont plus besoin de le déclarer.- Refonte interne de
LgSelect, des listes déroulantes, du sélecteur de couleur et des composants de saisie (sans changement d'API), avec de nombreuses corrections (ouverture/fermeture des listes déroulantes, position des libellés, restauration de la valeur après annulation d'une confirmation).
Application
- Écran de chargement unique : il reste affiché de la première image jusqu'à ce que l'application soit prête, au lieu d'être suivi d'un second écran « Authorizing… ». Son texte et le titre de la page reprennent le nom de l'application (réglage
App:Name). La mise à jour adapteindex.htmlet renomme les variables SCSS$auth-app-loading-*en$app-loading-*. - Spinner personnalisable : 17 animations de chargement au choix, pour l'écran de démarrage et
LgLoaderView. Le spinner et ses couleurs se choisissent dans lemain.scssdu client ($spinner,$sp-color,$sp-color-alt,$sp-bg-color,$sp-size). La page Spinner de chargement présente la galerie des spinners et génère le bloc SCSS. - EULA : case à cocher d'acceptation sur la page des conditions d'utilisation, et enregistrement de la date d'acceptation (nouvelle table
EulaAcceptances). OnConfigureHttpClient(IHttpClientBuilder, string): nouvelle méthode de la classeMaindu client pour configurer lesHttpClientde l'application.- Nouvelle échelle de z-index (surcharges Bootstrap) pour l'empilement des listes déroulantes et des fenêtres modales.
- Police Poppins livrée en WOFF2.
Serveur et base de données
OpenIddict 7 et clés Guid ⚠
Les entités OpenIddict utilisent désormais toujours une clé Guid, quel que soit le type de clé de l'utilisateur : builder.UseOpenIddict<Guid>(). Un UseOpenIddict() sans argument empêche l'application de démarrer. La mise à jour réécrit l'appel et génère la migration LagoonMigrationV4, adaptée à chaque moteur (SQL Server, MySQL/MariaDB, PostgreSQL, SQLite). Cette migration couvre aussi le schéma d'OpenIddict 7, les clés ASP.NET Identity ramenées à 128 caractères et la table EulaAcceptances.
Action : relisez la migration avant de l'appliquer (elle porte un marqueur LAGOON UNSUPPORTED) et sauvegardez la base. Quand la clé OpenIddict était une string, les tables OpenIddict sont vidées : tous les utilisateurs devront se reconnecter. Voir Migrer de 3.x vers 4.0.
Fabrique de contexte au design-time
La fabrique DesignTimeDbContextFactory de l'application dérive désormais de DesignTimeApplicationDbContextFactory<T>. La classe de base charge la configuration appsettings*.json et construit le DbContextOptionsBuilder, avec les mêmes options Identity qu'à l'exécution. Cela supprime l'avertissement PendingModelChangesWarning au démarrage. DesignTimeConfiguration.GetDevelopmentConfiguration() est obsolète (erreur de compilation). La mise à jour réécrit la fabrique.
MySQL / MariaDB : Pomelo remplacé par Microting
Pomelo ne publie rien pour .NET 10 : le paquet Pomelo.EntityFrameworkCore.MySql est remplacé par son fork Microting.EntityFrameworkCore.MySql. Le fork ne renomme que l'assembly et les espaces de noms. Le SQL généré, les annotations de modèle (MySql:CharSet, MySql:ValueGenerationStrategy…) et les migrations existantes sont inchangés. La mise à jour remplace le PackageReference et les using.
Les constantes CollationProvider nomment le moteur
Un moteur peut être servi par plusieurs providers : les constantes de CollationProvider contiennent désormais un nom de moteur stable, et non plus le nom de l'assembly du provider.
| Constante | Valeur 3.x | Valeur 4.0 |
|---|---|---|
CollationProvider.SQLServer | Microsoft.EntityFrameworkCore.SqlServer | SQLServer |
CollationProvider.Oracle | Oracle.EntityFrameworkCore | Oracle |
CollationProvider.MySQL | Pomelo.EntityFrameworkCore.MySql | MySQL |
CollationProvider.PostgreSQL | Npgsql.EntityFrameworkCore.PostgreSQL | PostgreSQL |
CollationProvider.SQLite | Microsoft.EntityFrameworkCore.Sqlite | SQLite |
CollationProvider.MariaDB | MariaDB | MariaDB (inchangé) |
DbContextOptionsBuilder.GetProviderName() retourne ces mêmes noms. La nouvelle méthode DatabaseEngine.FromProviderName(providerName) convertit un nom de provider EF Core (DbContext.Database.ProviderName) en nom de moteur.
Action : un code qui comparait une de ces constantes à Database.ProviderName doit passer par DatabaseEngine.FromProviderName(…). Une comparaison du type GetProviderName() == CollationProvider.MySQL reste correcte.
Publication : compression et fichiers statiques ⚠
Le toolkit remplace les formats de compression par défaut du SDK : aucune compression à la compilation, et Brotli seul à la publication. Le SDK produisait .gz à la compilation et .gz + .br à la publication. À la publication, le fichier original de chaque élément qui a une variante .br est supprimé. Sur l'application de référence, le wwwroot publié passe de 23,8 Mo à 10,4 Mo. Ne sont jamais supprimés : les fichiers sans variante compressée (images, polices) et ceux que le runtime Lagoon lit par leur nom non compressé.
Le serveur sert donc les fichiers statiques avec endpoints.MapStaticAssets() au lieu de app.UseStaticFiles(). MapStaticAssets négocie la variante compressée et gère les ETag et les en-têtes de cache. Il ne sert que les fichiers présents dans le manifeste de build. La mise à jour fait le remplacement dans Main.cs. Un UseStaticFiles avec options, ou un pipeline sans bloc UseEndpoints, est marqué et doit être adapté à la main.
Action :
- Une application servie en HTTP ne reçoit plus aucun fichier statique, car Brotli n'est négocié que sur une origine sécurisée. Posez
LagoonDefaultCompressionàfalsedans leDirectory.Build.propsde la solution : les variantes gzip sont rétablies et les fichiers non compressés conservés. - La propriété 3.x
DisablePublishCleaningprovoque une erreur de compilation : retirez-la.
<Project>
<PropertyGroup>
<LagoonDefaultCompression>false</LagoonDefaultCompression>
</PropertyGroup>
</Project>
Voir Compression des fichiers statiques pour le détail des formats, la liste des exclusions et la procédure de vérification.