Aller au contenu principal

Notes de version 4.1

Notes des versions 4.1.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.1.1​

Version corrective : démarrage des applications console (batch) en .NET 10 et mises à jour mineures de dépendances.

Corrections​

  • Batch : une application console ne s'arrête plus au démarrage sur AmbiguousMatchException: Multiple properties or methods match the name GetVersion. L'erreur survenait à la construction de l'hôte, avant toute commande (--version compris) : en .NET 10 pour toute classe Main dérivant de LgApplication ou de LgApplication<TDbContext>, en .NET 8 seulement si l'application redéfinissait GetVersion(). Cette méthode reste redéfinissable pour personnaliser le texte affiché par --version.

Sécurité​

Aucune vulnérabilité de dépendance n'est corrigée par cette version : les montées de version ci-dessous sont des correctifs fonctionnels. Le runtime .NET 10.0.13 reste requis sur les serveurs (voir la section Sécurité de la version 4.1.0). Les applications qui fournissent leur propre Chrome à Lagoon.PDF (Lagoon:Puppeteer:ExecutablePath) doivent le maintenir à jour (Chrome 155.0.8059.84 ou plus récent).

Dépendances​

  • Mises à jour : Swashbuckle.AspNetCore 10.3.1 (corrige notamment une AmbiguousMatchException sur les paramètres [FromQuery] à la génération du document OpenAPI), AWSSDK.S3 4.0.104.5.

Version 4.1.0 ⚠​

Version mineure : refonte de LgListView (tri, filtres, pagination, données distantes, glisser-déposer), validation des lignes et sélection de LgGridView, placeholders ${Clé} dans la configuration, utilisateurs sans stockage en base, chargement différé des bibliothèques Razor, .NET 10.0.13 et correctifs de sécurité.

LgListView : tri, filtres, pagination et données distantes ⚠​

LgListView est réécrit sur le pipeline de données de LgGridView : la liste est alimentée par Items (local) ou par un contrôleur [Queryable] via ControllerUri (filtrage, tri et pagination faits par le serveur, argument métier ControllerQueryArg, annulation par l'utilisateur avec AllowCancellation). Voir la page LgListView.

  • Tri : les champs triables sont déclarés dans SortOptions par des composants LgListViewSort dont SortSelector est une expression typée (SortSelector="@(x => x.Name)", comme le SortSelector des colonnes de LgGridView ; le type de l'élément est déduit de la liste). La clé du tri est le chemin du membre, ou Key pour une expression calculée ; Text est facultatif (à défaut, le [Display] du membre puis la clé). Le tri initial est la déclaration marquée IsDefault="true" : DefaultSortProperty est supprimé et, sans déclaration par défaut, la liste n'est pas triée au départ. OnSortChanged signale chaque changement (Key, SortDirection, et Sorts sous la même forme que LgGridView). Le tri s'applique désormais aussi sans regroupement (il ne s'appliquait auparavant qu'aux listes groupées).
  • Filtres : nouvelle section Filters avec le composant LgListViewFilter (FilterSelector="@(x => x.Status)", Key, Text, Items), rendu avec la boîte de filtre de LgGridView correspondant au type du membre (texte, numérique, date, heure, booléen, énumération, sélection de valeurs pour les autres types) ; l'onglet « Sélection » propose les valeurs de la source filtrées par les autres filtres. Panneau repliable dont la visibilité est liable en two-way (ShowFilters, DefaultShowFilters) et qui conserve les valeurs quand il est masqué, méthode ClearFiltersAsync. Comme pour LgGridView, OnFilterChanged fournit les filtres actifs par clé (Filters, HasFilters()) et n'est déclenché que lorsqu'ils changent, y compris à leur restauration.
  • Pagination : AllowPaging, DefaultPageSize, PaginationSizeSelector (0 = « Tout »), CurrentPage / CurrentPageChanged, et comme pour LgGridView PaginationButtonCount, DisplayInputPageSelector, PagerOnTop et les compteurs liables ListViewDataCounter (ActiveRows, TotalPages, TotalRows).
  • Compteurs dans le titre : comme pour LgGridView, {0} est remplacé dans Title par le nombre total d'éléments et {1} par le nombre d'éléments de la page affichée (Title="Projets ({0})"), y compris dans un titre traduit (#clé). Un compteur inconnu s'affiche ... (avant le premier chargement, et pour {1} quand la page est vide) ; pendant un rechargement, les compteurs du dernier chargement restent affichés.
  • Virtualisation : Virtualize / VirtualizeItemSize, combinable avec AllowPaging (les éléments de la page courante sont virtualisés). La liste défile avec le plus proche ancêtre défilant (le document par défaut) ; pour la faire défiler elle-même, donnez une hauteur et overflow-y: auto à .list-view-virtualized.
  • Sélection : comme pour LgGridView, elle est activée par SelectableRows ; SelectedItems est renommé Selection (ICollection<TItem>, créée si elle est omise). Avec KeyField, la sélection est comparée sur une propriété clé et survit au rechargement des éléments ; sans KeyField, une liste distante utilise la propriété Id du modèle quand il en a une. OnRowSelection signale chaque changement.
  • Persistance : StateId mémorise dans le stockage local du navigateur le tri, la taille de page, les valeurs des filtres, l'état du panneau de filtres et les groupes repliés.
  • Carte par défaut : sans ItemTemplate, chaque élément est rendu dans une LgCard paramétrable : BodyContent (corps, item.ToString() à défaut), ColorSelector et StripeCssClassSelector (couleur ou classe CSS de la LgCardStripe), StripeIconSelector (icône de la bande), StripeContent (contenu rendu dans la bande) et CssClassSelector (classes CSS de la carte). LgCardStripe accepte pour cela un contenu enfant (ChildContent). La bande est rendue dès qu'un élément a une couleur, une classe, une icône ou un StripeContent, de la couleur du thème à défaut de couleur ou de classe.
  • Cartes cliquables et élément actif : OnItemClick rend les cartes cliquables et reçoit l'élément cliqué ; @bind-ActiveItem met en évidence l'élément actif (classe active, comparé comme la sélection), qu'un clic sur une carte rend actif. Par élément : LinkSelector (page ouverte par la carte), TooltipSelector / TooltipIsHtml (infobulle) et ConfirmationMessageSelector (confirmation avant l'action du clic).
  • Disposition en grille : ItemMinWidth (ex. "18rem") dispose les cartes en grille, avec autant de colonnes que la largeur le permet (sans effet avec Virtualize).
  • Regroupement : GroupSelector (x => x.Statut) regroupe les éléments affichés sous des en-têtes repliables (DefaultGroupCollapsed), triés par GroupOrderSelector puis par clé, y compris côté serveur avec ControllerUri. L'en-tête affiche par défaut la clé et le nombre d'éléments ; GroupHeaderContent le personnalise (ListViewGroupContext<TItem> : Key, Text, Items, Count, Color, IsCollapsed), GroupColorSelector lui ajoute une bande de couleur et, avec SelectableRows, une case à cocher sélectionne les éléments du groupe (SelectGroup / UnselectGroup). Avec AllowPaging, les groupes sont ceux de la page courante. Comme sur une LgGridView groupée, deux boutons de l'en-tête (et ExpandAllGroups() / CollapseAllGroups()) déplient ou replient tous les groupes. Le nouveau paramètre GroupCollapsable (true par défaut) empêche, à false, de replier les groupes : l'en-tête d'un groupe n'a plus de bouton de repli et n'est plus cliquable, les boutons « Déplier les groupes » / « Replier les groupes » ne sont pas affichés et les groupes restent dépliés. Voir Regroupement.
  • En-tête : l'en-tête de la liste n'est plus rendu quand il est vide (ni titre, ni tri, ni filtre, ni barre d'actions, ni sélection).
  • Contexte d'ItemTemplate : ItemTemplate reçoit un ListViewItemContext<TItem> au lieu de l'élément : Item (l'élément), Index (position parmi les éléments affichés), IsSelected, Color et CssClass (résultats de ColorSelector et CssClassSelector), les méthodes RenderStripe() (bande de couleur avec StripeContent) et RenderBody() (div.card-body avec BodyContent, ou ToString() à défaut) pour reprendre la carte par défaut dans un template, RenderCard(contenu) (la LgCard de l'élément, avec le clic, l'élément actif, le lien, l'infobulle et la confirmation, autour d'un contenu libre), IsActive, ActivateAsync() (rend l'élément actif sans déclencher OnItemClick) et ToggleSelectionAsync() pour sélectionner l'élément depuis le template (comme sa case à cocher). Le contexte s'affiche comme l'élément (@context rend item.ToString()).
  • Messages : NoDataTitle, NoDataDescription et DataErrorTitle personnalisent les états « aucune donnée » et « erreur de chargement » ; RefreshAsync() recharge les données.
  • Items est désormais de type ICollection<TItem> (une List<TItem> reste acceptée). Au rendu suivant du parent, la liste se recharge quand la collection a été remplacée, quand son nombre d'éléments a changé ou après un événement d'une ObservableCollection, ainsi qu'à un changement de ControllerUri ou de ControllerQueryArg ; hors d'un rendu du parent, appelez RefreshAsync().
  • Données locales mises en cache : avec Items, le résultat filtré et trié est calculé une fois puis réutilisé par les changements de page et de taille de page. Il est recalculé quand les filtres, le tri, le regroupement ou la source changent (autre collection, autre nombre d'éléments, évènement d'une ObservableCollection). Comme pour la grille, un élément modifié sur place n'est pris en compte qu'après RefreshAsync().
  • Une propriété de type énumération est triée sur sa valeur, et non plus sur son nom d'affichage.

La mise à niveau automatique remplace SelectedItems par SelectableRows="true" Selection, convertit les <LgOptionListItem Value="@nameof(Projet.Nom)" Text="…" /> de SortOptions en <LgListViewSort SortSelector="@(x => x.Nom)" Text="…" /> et reporte DefaultSortProperty en IsDefault="true" sur la déclaration du même membre. Les cas non convertibles (Value calculée, DefaultSortProperty sans déclaration correspondante) sont signalés par un marqueur LAGOON UNSUPPORTED.

Action

  • GroupBy (nom de propriété) est remplacé par GroupSelector (expression typée x => x.Propriete) et GroupHeaderContent reçoit un ListViewGroupContext<TItem> : les listes concernées sont signalées par un marqueur LAGOON UNSUPPORTED.
  • Remplacez les appels à SortListViewAsync(...) par RefreshAsync().
  • LgGridView : si vous construisiez un GridViewSortEventArgs, passez-lui la liste des tris (Sorts).

LgListView : en-tête aligné sur celui de LgGridView ⚠​

La liste est désormais rendue dans un LgFrame, comme la grille : son en-tête a le même aspect (bandeau, titre, taille des boutons, Kind) et la liste peut être repliée. Voir En-tête : titre, actions et repli.

  • Nouveaux paramètres repris de LgFrame : IconName, Kind (défaut Kind.Primary), Collapsable, Collapsed / CollapsedChanged, DefaultCollapsed et TitleContent.
  • La barre d'actions suit l'ordre de la grille : les boutons de Toolbar, puis « Filtres » et « Effacer », puis le menu de tri. Elle n'utilise plus de taille Large mais celle des toolbars de cadre (FrameToolbarButtonSize). Les boutons intégrés sont masqués quand la liste est repliée.
  • Le tri se choisit dans un menu « Trier par : … » qui propose les déclarations de SortOptions puis les sens « Croissant » / « Décroissant » (grisés tant qu'aucun tri n'est choisi). La flèche du sens suit les en-têtes de colonnes de la grille : ↑ croissant, ↓ décroissant (c'était l'inverse).
  • Le bouton « Filtres » prend les icônes de la grille ; le bouton « Effacer » est placé à côté de lui quand le panneau est affiché (le bouton « Vider » du panneau est supprimé).
  • Nouveau paramètre ShowFiltersSummary : comme dans la grille, les filtres actifs sont résumés sous le titre (« Libellé : valeur », deux au plus puis « +N »), avec l'apparence du résumé de la grille dans chaque thème. Le résumé n'est affiché que lorsque le panneau de filtres est masqué.
  • Nouveau paramètre FiltersPosition (énumération ListViewFiltersPosition) : le panneau de filtres s'affiche au-dessus des éléments (Top, par défaut), dans une fenêtre modale centrée (Popup) ou dans un tiroir à gauche ou à droite de l'écran (Left, Right). Fermée, la fenêtre est seulement masquée : les filtres gardent leurs valeurs. Voir Filtres déclaratifs.
  • Nouvelle présentation des filtres, quelle que soit leur position : grille sur toute la largeur (bandeau teinté au-dessus des éléments), boîtes en forme de liste déroulante (chevron), filtre actif mis en évidence avec la couleur d'accent du thème, et une croix sur chaque filtre actif pour le vider seul. Variables SCSS $list-view-filter-* et $list-view-filters-bg / -border-color / -border-radius pour l'adapter.
  • Le pager et le panneau d'action de la sélection sont présentés comme ceux de LgGridView : sélecteur de taille, pages et champ « aller à la page » regroupés à droite (avec l'alignement du pied de grille de chaque thème, sans bordure), panneau d'action entre les éléments et le pager. Le pager n'affiche plus le nombre total d'éléments (la grille ne l'affiche pas) : liez ListViewDataCounter pour l'afficher.
  • Pagers de LgGridView et de LgListView : le sélecteur de taille de page et le champ « aller à la page » ont désormais la même hauteur que les boutons de page (ils étaient plus hauts). Cette hauteur commune est calculée à partir des variables $pager-item-* du thème ; la nouvelle variable $pager-control-height permet de la fixer.
  • Hauteur de la liste : comme pour LgGridView, une hauteur donnée par l'attribut style (height ou max-height) fait défiler les éléments dans la liste, l'en-tête, les filtres, le panneau d'action et le pager restant visibles ; la zone des éléments revient en haut à chaque changement de page, de tri ou de filtres. Une liste virtualisée défile ainsi dans la liste sans CSS supplémentaire. Voir Hauteur et défilement dans la liste.
  • Regroupement : les en-têtes de groupe deviennent des bandeaux arrondis (nombre d'éléments dans une pastille), teintés de la couleur du groupe avec GroupColorSelector (couleur exposée en variable CSS --list-view-group-color), et restent collés en haut de la zone qui défile tant qu'on parcourt leur groupe (hors virtualisation). Les éléments d'un groupe sont en retrait sous son en-tête ($list-view-group-indent).
  • LgCard active (Active, élément actif d'une LgListView) : la carte est mise en évidence par un fond teinté de la couleur d'accent du thème, un contour fin de la même couleur (sa taille ne change plus) et des textes en gras ; la teinte est conservée au survol, et l'état actif reste visible en dehors du survol (il ne s'affichait auparavant qu'au survol). Les variables SCSS $card-active-bg et $card-active-border sont remplacées par $card-active-color (couleur d'accent) et $card-active-tint (intensité de la teinte).
  • « Tout sélectionner » devient une case à cocher dans l'en-tête, devant le titre et alignée sur les cases des éléments : cochée quand tous les éléments chargés sont sélectionnés, indéterminée pour une sélection partielle ; un clic sur la case cochée désélectionne tout.
  • Les cases de sélection sont nommées par le contenu de leur élément pour les lecteurs d'écran.
  • Les éléments occupent toute la largeur de la liste (thèmes Custom, PF, TAS : ils s'arrêtaient 2rem avant le bord droit, même sans cases à cocher).

Action

  • Retirez le LgFrame qui entourait éventuellement une LgListView : la liste est déjà un cadre (sinon cadre dans un cadre). Reportez son titre dans Title de la liste.
  • Les classes CSS de l'ancien en-tête n'existent plus (list-view-header, list-view-header-leftside, list-view-header-rightside, list-view-title, list-view-filter-action, list-view-sort-action, listview-sortprop, list-view-button-order, list-view-toolbar-action, list-view-filters-actions) : adaptez les règles CSS de l'application qui les ciblaient. CssClass et AdditionalAttributes s'appliquent désormais à la racine du cadre.
  • Les variables SCSS $list-view-header-padding, $list-view-header-margin et $list-view-item-width sont supprimées : retirez-les du main.scss de l'application si elle les redéfinissait.
  • Si le main.scss de l'application redéfinissait $card-active-bg ou $card-active-border, reportez la couleur voulue dans $card-active-color.

LgListView : glisser-déposer​

LgListView permet de réordonner ses éléments et d'en échanger entre plusieurs listes, à la souris, au toucher ou au clavier. Voir LgListView — Glisser-déposer.

  • DragMode (drapeaux ListViewDragMode) : Reorder (dans la liste, sans effet quand la liste est triée), DragOut / DropIn (vers / depuis les autres listes du même DragGroup), Copy (la liste source garde ses éléments ; copie créée par ItemCopyFactory), All.
  • Les éléments se déplacent par une poignée affichée à gauche de leur carte, avant la bande de couleur ; un ItemTemplate qui n'utilise pas RenderCard la place avec ListViewItemContext.RenderDragHandle(). Sur un écran tactile, un appui long n'importe où sur la carte (hors champs, liens et boutons) démarre aussi le glisser ; un appui bref reste un clic et un glissement rapide fait défiler la page. Avec DragFromItem, la souris (ou le stylet) déplace aussi l'élément depuis n'importe quel point de sa carte, avec les mêmes exclusions : un clic sans déplacement reste un clic ; la poignée reste affichée pour le clavier.
  • Une ligne d'insertion indique l'emplacement de dépôt (verticale dans une liste en grille) ; une liste sans Reorder, triée ou vide est surlignée et reçoit l'élément à la fin.
  • Acceptation d'un élément : les modes et le groupe des deux listes, puis le prédicat CanDrop (ListViewDropContext<TItem>) de la liste cible, évalué pendant le survol (position refusée : pas de ligne, carte encadrée en rouge), puis OnItemDrop (ListViewDropEventArgs<TItem>) au relâchement : Cancel refuse le dépôt, Handled indique que le gestionnaire a mis à jour les données.
  • Sans Cancel ni Handled, la liste met à jour elle-même ses Items locaux (déplacement, retrait de la liste source, copie) ; les données chargées par ControllerUri ne sont pas modifiées (le gestionnaire met à jour la source et appelle RefreshAsync()). Une liste qui ne peut pas modifier ses éléments (distante, tableau…) n'accepte les éléments des autres listes qu'avec un gestionnaire OnItemDrop. OnItemDropped est déclenché une fois l'élément en place.
  • Listes groupées : un élément peut changer de groupe quand OnItemDrop est branché ; le gestionnaire met à jour la propriété groupée à partir de TargetGroupKey.
  • Clavier : Espace ou Entrée sur la poignée saisit l'élément, les flèches choisissent la position, Espace ou Entrée dépose, Échap annule, Ctrl + flèche déplace directement d'une position ; les positions sont annoncées aux lecteurs d'écran.

Une LgListView alimentée par des éléments locaux n'affiche plus l'indicateur de chargement quand elle se recharge (RefreshAsync(), changement du nombre d'éléments, tri, filtre, page) : les éléments affichés sont mis à jour sur place, ce qui conserve le focus et évite un clignotement. Le chargement initial et les données distantes (ControllerUri) gardent l'indicateur. Les lignes sont identifiées par KeyField (par Id pour une liste distante sans KeyField, sinon par l'élément) : un élément déplacé ou rechargé garde les composants de son template au lieu de reprendre ceux de l'élément affiché avant à sa place.

LgColorBar devient LgCardStripe ⚠​

La bande de couleur de LgCard est renommée LgCardStripe (voir LgCard), avec ses classes CSS (color-bar, color-bar-icon, color-bar-content deviennent card-stripe, card-stripe-icon, card-stripe-content) et ses variables SCSS ($card-colorbar-* deviennent $card-stripe-*, $notif-item-color-bar-bg-color et $notif-item-hover-color-bar-bg-color deviennent $notif-item-card-stripe-bg-color et $notif-item-hover-card-stripe-bg-color). Elle accepte un contenu enfant (ChildContent) : elle prend alors la largeur de son contenu, avec la marge interne $card-stripe-content-padding.

La mise à niveau automatique renomme le composant (.razor, .cs) et les classes CSS dans les feuilles de style (.scss, .css).

Action

  • Renommez les variables SCSS surchargées par l'application ($card-colorbar-*, $notif-item-*color-bar-bg-color) : elles ne sont pas renommées par la mise à niveau automatique.

LgGridView : événement de tri et rechargement des données ⚠​

  • Nouvel événement OnSortChanged : il fournit le tri complet après un tri par l'utilisateur ou l'application d'un profil (Sorts : colonnes triées par ordre de tri, avec leur sens ; Key : colonne du tri principal). Il n'est pas déclenché au chargement initial.
  • GridViewSortEventArgs décrit désormais ce tri complet : son constructeur reçoit la liste des tris ; Key et SortDirection sont conservées et donnent la colonne et le sens du tri principal.
  • La grille se recharge désormais quand un élément est ajouté à la même liste Items (List.Add, sans ObservableCollection ni RefreshAsync()), au rendu suivant du parent.
  • Un changement de ControllerUri seul recharge désormais la grille (seul ControllerQueryArg était surveillé).
  • OnFilterChanged n'est plus déclenché de nouveau quand les filtres actifs n'ont pas changé (même valeur saisie de nouveau, profil rechargé avec les mêmes filtres) : dès qu'un filtre était actif, chaque rafraîchissement des filtres le déclenchait.
  • GridViewDataCounterChanged n'est plus notifié qu'à un changement des compteurs (et non plus à chaque chargement).
  • Le champ « aller à la page » du pager ramène un numéro hors limites (0, négatif, au-delà de la dernière page) à la première ou à la dernière page.

LgGridView : clé des lignes, KeyField et Id ⚠​

KeyField est désormais la clé de la sélection et du rechargement d'une ligne (ReloadRow). Sans KeyField, une grille locale compare les éléments par égalité, une grille distante par la propriété Id du modèle. Voir LgGridView.

  • Grille locale (Items) sans KeyField : ReloadRow retrouve l'élément dans Items avec l'égalité du type (Equals / IEquatable<T>) au lieu de la propriété Id, qui n'est plus nécessaire. Si le modèle ne redéfinit pas l'égalité, seule la même instance est retrouvée : un élément remplacé dans Items par une copie n'est plus rechargé, sans erreur (la ligne garde l'ancien élément).
  • Grille distante (ControllerUri) sans KeyField : la propriété Id du modèle sert de clé, pour le rechargement d'une ligne (ReloadRow, OnAfterUpdate avec EditRefreshMode="ReloadGrid") comme avant, et désormais aussi pour la sélection : une ligne sélectionnée le reste après un rechargement ou un changement de page. Id prime sur l'égalité du modèle (Equals / IEquatable<T>) et doit identifier l'élément de façon unique : des lignes partageant un Id, ou dont l'Id n'est pas renseigné, sont sélectionnées ensemble. La clé suit un changement de KeyField ou de la source de données (une grille qui reçoit son ControllerUri après le premier rendu passe sur Id).
  • Grille distante sans KeyField dont le modèle n'a pas de propriété Id : ReloadRow lève une erreur explicite (traduite) avant l'envoi du PATCH / PUT au lieu d'une erreur technique après l'envoi, et OnAfterUpdate (ReloadGrid) retrouve l'élément rechargé avec l'égalité du type (Equals / IEquatable<T>).
  • Une ligne dont l'élément n'est plus trouvé (retiré d'Items ou absent de la réponse du serveur) n'est plus vidée : elle garde son élément, et OnAfterUpdate reçoit l'élément édité au lieu de null.

Action

  • Ajoutez KeyField aux grilles locales sans KeyField dont le modèle n'implémente ni Equals ni IEquatable<T>, quand un OnCommandClick renvoie RefreshMode = ReloadRow après avoir remplacé l'élément dans Items par une copie.
  • Renseignez KeyField sur les grilles distantes dont le modèle a une propriété Id qui n'identifie pas l'élément de façon unique : Id d'un parent dans une vue jointe, projection qui ne renseigne pas Id, ou modèle dont l'égalité (Equals / IEquatable<T>) repose sur d'autres propriétés.

LgGridView : validation des lignes en mode Cell ⚠​

  • Nouvel événement OnCellValueChanged, déclenché à chaque changement de valeur d'une cellule, que la valeur soit valide ou non (args.IsValid) ; une valeur valide est enregistrée avant le déclenchement. Quand plusieurs lignes partagent la même donnée, le gestionnaire désigne les lignes liées avec args.RevalidateItems(i => ...), ou toutes les lignes avec args.RevalidateItems() : la grille les valide à nouveau et signale leurs cellules en erreur, y compris pour les erreurs de IValidatableObject et des attributs de validation. Les lignes non affichées (autre page, filtre, virtualisation, lignes recréées par un rechargement) sont validées à leur affichage. Sans gestionnaire, aucune autre ligne n'est revalidée. Voir LgGridView.

    private void OnCellValueChanged(GridViewCellValueChangedEventArgs<MyItem> args)
    {
    args.RevalidateItems(i => i.GroupId == args.Item.GroupId);
    }
  • L'indicateur d'erreur suit désormais la validation de la ligne entière. Une erreur sur un autre champ de la ligne (par exemple levée par IValidatableObject) est signalée sur la cellule concernée, y compris une cellule en lecture seule, sans attendre qu'elle soit éditée. Une ligne en erreur garde son indicateur quand elle quitte l'affichage puis y revient (page, défilement, rechargement).

  • La modification d'une colonne liée par un indexeur ou un chemin imbriqué met à jour les erreurs des autres cellules de la ligne, et l'annulation d'une saisie (Échap) ne fait plus disparaître les erreurs encore présentes sur la ligne.

  • La touche Entrée dans une cellule valide la saisie sans soumettre le formulaire de la ligne. Elle ne lance plus une seconde validation de toute la ligne, qui affichait les messages d'erreur deux fois et contrôlait aussi les champs jamais saisis.

  • Quand le serveur refuse l'enregistrement d'une cellule avec des erreurs de validation (ValidationProblemDetails ou ModelState, clés Data.<Champ>), les cellules des champs refusés sont signalées sur la ligne, y compris après ReloadRow, et la ligne n'est plus enregistrée tant qu'ils ne sont pas modifiés. L'exception LgRemoteModelStateException (erreurs de validation renvoyées par le serveur, propriété Errors) devient publique.

  • Chaque changement de valeur vérifie les attributs de validation de tous les membres de l'élément de la ligne éditée, comme la validation serveur de l'élément enregistré : un champ obligatoire jamais saisi est signalé (et la ligne n'est pas enregistrée) dès la première modification de la ligne, au lieu d'être refusé par le serveur. Les lignes liées ne vérifient que le champ modifié et les champs déjà en erreur.

  • Quand une saisie n'est pas enregistrée parce que la ligne a une erreur sur un autre champ, le message d'erreur liste aussi les erreurs de ces champs (attributs de validation, erreurs du serveur), et plus seulement le message générique « Il y a une ou plusieurs erreurs sur la ligne ».

  • LgEditForm : nouvelle méthode RevalidateFields(fieldNames), qui valide à nouveau les champs donnés comme s'ils avaient changé, sans afficher leurs erreurs dans le toastr ni changer leur état « modifié » (utile quand la donnée du formulaire a été modifiée ailleurs).

  • La classe CellValidationState et le champ protégé _cellValidationState de LgGridCell sont supprimés : l'état de validation est désormais porté par la ligne.

Action

  • Une cellule dérivée de LgGridCell qui utilisait CellValidationState ou _cellValidationState doit lire l'état de la ligne à la place.

Configuration : secrets au design time et placeholders ${Clé} ⚠​

  • Au design time (dotnet ef), DesignTimeApplicationDbContextFactory<T> charge maintenant, après appsettings.json et appsettings.Development.json, les secrets utilisateur puis les variables d'environnement, dans le même ordre de priorité qu'à l'exécution. Une chaîne de connexion stockée avec dotnet user-secrets (ou passée par ConnectionStrings__<Nom>) est donc utilisée par les commandes de migration. Les secrets sont lus avec le UserSecretsId du projet Model : donnez-lui le même que le projet Server, par exemple dans un Directory.Build.props placé dans le dossier de l'application.

    Directory.Build.props (dossier de l'application)
    <Project>
    <PropertyGroup>
    <UserSecretsId>00000000-0000-0000-0000-000000000000</UserSecretsId>
    </PropertyGroup>
    </Project>

    Le nouveau point d'extension virtuel OnConfigureConfiguration(IConfigurationBuilder) permet d'ajouter ou de retirer des sources de configuration. Voir Secrets et placeholders de configuration.

  • ⚠ Placeholders ${Clé} dans la configuration. Sur le serveur, le batch et au design time, une valeur de configuration peut référencer une autre clé, quelle que soit sa source (secrets utilisateur, variable d'environnement, coffre…). La chaîne de connexion reste versionnée et seul le mot de passe est stocké ailleurs :

    appsettings.json
    "ConnectionStrings": {
    "MainDb": "Server=db01;Database=App;User Id=app;Password=${DB_PASSWORD}"
    }

    Le remplacement est automatique (GetConnectionString, options, bibliothèques tierces). ${Clé ?? défaut} donne une valeur par défaut et $${ un ${ littéral. Une clé absente sans défaut lève une exception à la lecture. Dans ConnectionStrings, une valeur insérée contenant ;, ' ou " est mise entre guillemets. Le client Blazor ne remplace pas les placeholders. "Lagoon": { "ExpandPlaceholders": false } désactive le remplacement.

    Mise à niveau : lgn update échappe en $${ les ${ déjà présents dans les appsettings*.json (hors client). Une valeur venant d'une autre source (variable d'environnement, coffre…) et contenant ${ doit être échappée à la main. DbContextHelper.GetExpandedConnectionString (syntaxe %VAR%) est retirée (erreur de compilation) : utilisez GetConnectionString. Voir Placeholders dans la configuration.

Lien de réinitialisation du mot de passe envoyé à l'adresse enregistrée ⚠​

La page « Mot de passe oublié » envoyait le lien de réinitialisation à l'adresse saisie par le visiteur, alors que le compte est retrouvé par une comparaison sur l'adresse normalisée : une adresse différente (par exemple ſam@corp.com, avec le caractère U+017F, ou le signe Kelvin à la place d'un K) retrouvait le compte sam@corp.com et recevait son lien. Le lien est désormais envoyé à l'adresse enregistrée du compte. Même schéma que CVE-2026-70175, que le runtime .NET 10.0.13 ne corrige pas pour Lagoon (page propre au framework). La variante PF (LgPFLogin) est corrigée de la même façon.

ILgAuthApplication.GenerateResetPasswordLinkAsync renvoie maintenant un ResetPasswordLink (propriétés Url et Email, l'adresse enregistrée du compte) au lieu d'une string. Il renvoie toujours null quand aucun compte ne correspond.

ResetPasswordLink resetLink = await App.GenerateResetPasswordLinkAsync(typedEmail);
if (resetLink is not null)
{
await smtp.SendEmailAsync(resetLink.Email, subject, body.Replace("{0}", resetLink.Url));
}

Action : la mise à niveau automatique signale chaque appel ou surcharge de GenerateResetPasswordLinkAsync dans l'application (marqueur <-- LAGOON UNSUPPORTED) sans le modifier. Envoyer le courriel à resetLink.Email, jamais à l'adresse saisie, et utiliser resetLink.Url comme lien ; une surcharge renvoie new ResetPasswordLink(url, user.Email).

LgTextBox et LgNumericBox : OnInput à la réinitialisation ⚠​

  • La réinitialisation par le bouton d'effacement (✕) déclenche maintenant OnInput avec une valeur vide (""), comme le navigateur pour un <input type="search"> vidé par sa croix. Un champ qui n'écoutait que OnInput avec un Value en liaison simple (champ de recherche, filtre) gardait jusqu'ici son ancien texte après une réinitialisation : champ vide, filtre toujours appliqué. OnInput n'est pas déclenché si la réinitialisation est annulée (OnChangingAsync, refus de la confirmation de ConfirmationMessage). Le comportement est hérité par LgSuggestBox et LgColorPickerBox. Voir LgTextBox et LgNumericBox.
  • Corrigé par la même occasion : la recherche de la fenêtre « Colonnes... » de LgGridView et la recherche dans la liste des valeurs du filtre d'une colonne date gardaient leur filtre après un clic sur ✕.

Action

  • Un composant qui branchait OnReset sur le même gestionnaire que OnInput pour contourner l'ancien comportement reçoit désormais deux appels à chaque réinitialisation : retirez le OnReset.

Utilisateurs sans stockage en base​

Nouvelle option AuthenticationOptions.UserStorage. Avec UserStorageMode.None, une connexion SSO ou Windows n'écrit plus rien dans les tables ASP.NET Core Identity (AspNetUsers, AspNetUserClaims, AspNetUserRoles, AspNetUserLogins, AspNetUserTokens), et OpenIddict n'enregistre plus les codes et jetons (OpenIddictTokens). L'identité de l'utilisateur (login, e-mail, rôles, claims personnalisés) ne vit que dans le cookie d'authentification et dans les jetons. Destinée aux applications qui ne doivent enregistrer aucune donnée personnelle.

Main.cs
protected override void OnConfigureAuthentification(AuthenticationOptions options)
{
options.UserStorage = UserStorageMode.None;
}
  • Les rôles sont donnés à chaque connexion avec context.AddCustomRole(...) dans OnSignIn / OnSignInAsync.
  • L'identifiant de l'utilisateur (sub) est un Guid aléatoire à chaque connexion.
  • Le cookie a une expiration absolue (SsoAuthenticationCookieLifeTime, 1 jour par défaut) : un changement de rôle chez le fournisseur d'identité s'applique au plus tard à cette échéance.
  • Aucun jeton de rafraîchissement n'est émis (offline_access est retiré) et un jeton ne peut pas être révoqué.
  • Incompatible avec l'authentification Forms et la MFA (exception au démarrage), les notifications, les profils de grilles Remote personnels et toute entité liée à la table des utilisateurs.

Le mode par défaut (UserStorageMode.Database) ne change pas. Voir Utilisateurs sans stockage en base.

Claims personnalisés dans les jetons​

  • Nouvelle option AuthenticationOptions.AccessTokenClaims : elle déclare les claims émis dans le jeton d'accès, par exemple les claims personnalisés (ajoutés avec context.AddCustomClaim(...)) dont le serveur a besoin pour ses politiques d'autorisation. Un claim déclaré (comparaison insensible à la casse) est émis dans le jeton d'accès et dans le jeton d'identité, un claim par valeur, à la connexion comme à chaque renouvellement du jeton d'accès ; il est aussi renvoyé par /connect/userinfo. Une API peut donc être protégée par une politique RequireClaim sur ce claim ; la même politique, déclarée aussi dans le OnConfigureAuthorization du client, fonctionne avec AuthorizeView Policy="...".

    Main.cs (Server)
    protected override void OnConfigureAuthentification(AuthenticationOptions options)
    {
    options.AccessTokenClaims = new[] { "department" };
    }

    protected override void OnConfigureAuthorization(AuthorizationOptions options)
    {
    options.AddPolicy("Sales", policy => policy.RequireClaim("department", "Sales"));
    }

    Quand la liste est renseignée, le jeton d'accès ne contient plus que les claims role, le claim displayname (utilisé par le suivi des modifications pour nommer l'auteur) et les claims déclarés : les claims dont le type se termine par id doivent alors y figurer pour rester dans le jeton d'accès. Sans AccessTokenClaims, rien ne change : le jeton d'accès contient les claims role, displayname et ceux dont le type se termine par id. Le jeton d'identité n'est pas concerné : tous les claims de l'utilisateur y restent. Chaque claim déclaré grossit l'en-tête Authorization: Bearer de tous les appels d'API : ne déclarez que ceux utilisés côté serveur. Le security stamp ASP.NET Core Identity n'est jamais émis, même déclaré. Voir Policy fondée sur un claim personnalisé.

  • AuthenticationOptions.CustomClaims, sans effet depuis la 2.0, est marquée obsolète : un claim ajouté avec context.AddCustomClaim(...) dans OnSignIn / OnSignInAsync est disponible pour l'application cliente sans être déclaré.

  • Nouvelle option AuthenticationOptions.PreserveStoredClaims (false par défaut) : avec des utilisateurs stockés en base, la connexion remplace les claims de AspNetUserClaims par ceux ajoutés avec context.AddCustomClaim(...) ; un claim enregistré autrement (écran d'administration, seeder…) est donc supprimé, et tous le sont si la connexion n'en ajoute aucun (comportement depuis la 3.32.1). Avec PreserveStoredClaims = true, seuls les claims stockés dont le type est ajouté à la connexion sont remplacés : les autres sont conservés. Voir Claims stockés en base.

    Main.cs (Server)
    protected override void OnConfigureAuthentification(AuthenticationOptions options)
    {
    options.PreserveStoredClaims = true;
    }
  • Documentation : nouvelle section Afficher le nom de l'utilisateur dans l'interface (claim displayname ajouté à la connexion, lu côté client avec GetUser().Claims.GetDisplayName()).

Fenêtres « Colonnes » et « Regroupements » de LgGridView​

  • La fenêtre « Colonnes... » présente deux listes LgListView : les colonnes affichées (réordonnables par glisser-déposer ou avec Ctrl+Haut / Ctrl+Bas, épinglables, supprimables) et les colonnes disponibles, triées par titre avec un champ de recherche. Une colonne s'ajoute par glisser-déposer vers la liste affichée ou avec l'icône « + », et se retire avec l'icône « × » ou en la déposant sur la liste des disponibles ; elle se glisse depuis n'importe quel point de sa ligne, et au clavier depuis sa poignée. Relâcher une colonne hors de ces zones annule le déplacement.
  • Les colonnes sans titre de sélection et de commandes apparaissent sous les libellés « [Sélection] » et « [Action] ».
  • La fenêtre Regroupements est revue : les colonnes regroupées sont affichées par niveau (« Niveau 1 », « Niveau 2 »…) dans une liste, sans liste déroulante. Une zone sous la liste permet de choisir une colonne groupable (dans l'ordre d'affichage des colonnes de la grille) et de l'ajouter sur un nouveau niveau (5 niveaux au plus, compteur affiché). Un niveau peut être fusionné avec le niveau précédent, monté, descendu ou supprimé ; une colonne peut être retirée, placée sur un nouveau niveau ou glissée (souris ou clavier) sur un autre niveau pour être combinée à ses colonnes. Un bouton « Tout retirer » vide les regroupements.
  • Si l'enregistrement du profil échoue (serveur injoignable, accès refusé...), les changements de colonnes et de regroupement restent appliqués à la grille.
  • Les modales d'options de colonnes et de regroupements (LgColumnOptions, LgGroupOptions et leurs types ColumnOption, GroupOption, SaveColumnOptionsEventArgs, SaveGroupOptionsEventArgs) passent du module Lagoon.UI au module Lagoon.UI.GridView, dans le namespace Lagoon.UI.Components.Internal. Ce sont des composants internes de la grille : une application qui les référençait directement doit ajouter @using Lagoon.UI.Components.Internal (ou using) et une référence à Lagoon.UI.GridView.

Nouveautés​

  • Chargement différé des bibliothèques Razor (RCL) : l'assembly d'une RCL déclarée avec l'élément BlazorWebAssemblyLazyLoad n'est téléchargée qu'à la première navigation vers une de ses pages. Dans OnConfigureBehavior, app.LazyAssemblies.Add("orders", "MyCompany.Orders.wasm") associe un préfixe de route aux assemblies à charger ; pour les cas avancés, la nouvelle méthode OnNavigateAsync(LgNavigationContext context) de Main.cs est appelée avant chaque navigation (context.LoadAssemblies(...), context.AddAssemblies(...)). LoadLazyAssembliesAsync précharge des assemblies sans navigation, et WindowManager.OpenModalAsync ouvre une page différée dans une modale. Voir RCL et chargement différé.

    protected override void OnConfigureBehavior(ApplicationBehavior app)
    {
    app.LazyAssemblies.Add("orders", "MyCompany.Orders.wasm");
    }
  • Certificat OpenIddict en base64 : le certificat de signature et de chiffrement d'OpenIddict peut être fourni sous forme de chaîne, sans fichier, par la nouvelle clé Lagoon:Authentication:CertificateBase64 : pratique quand les secrets viennent de variables d'environnement ou d'un coffre (conteneurs). Elle accepte la forme compacte <certificat>.<clé privée> (base64 du certificat DER et de la clé PKCS#8, la représentation la plus courte) ou le base64 d'un pfx (mot de passe dans Lagoon:Authentication:OpendIdCertificatePassword). Elle passe avant CertificateFilePath et OpendIdCertificate, après CertificateThumbprint. CertificateLoader.FromBase64 / ToCompactBase64 (Lagoon.Server.Helpers) lisent et produisent ces chaînes pour les certificats propres à une application. Voir Certificat.

  • LgGridView : le nombre maximal de colonnes triées en même temps, jusqu'ici fixé à 4, se règle avec le nouveau paramètre MaxSortedColumns, ou pour toute l'application avec l'option globale MaxSortedColumns de app.GridView(...) (4 par défaut, comportement inchangé). Si l'utilisateur trie une colonne de plus, le tri de plus faible priorité est retiré. 1 limite la grille à une seule colonne triée, 0 supprime la limite, une valeur négative lève une exception. Voir LgGridView et les options globales.

  • LgGridView : le nouveau paramètre ProfileFeatures (ou l'option globale ProfileFeatures de app.GridView(...)) limite les éléments enregistrés dans les profils et rechargés depuis eux : filtres, tris, position, visibilité, gel, largeur des colonnes, regroupements et taille de page (flags GridFeature, tous par défaut ; la position et le gel vont ensemble). Contrairement à Features, un élément exclu reste utilisable : il n'est pas écrit dans les profils et garde son état courant quand un profil est chargé.

  • LgGridView : le nouveau rappel OnProfileLoadingAsync (Func<GridViewProfileLoadingEventArgs, Task>) est appelé et attendu juste avant l'application d'un profil, avec le profil et la raison du chargement (GridViewProfileLoadReason : initialisation, sélection par l'utilisateur, suppression du profil courant). Il peut retirer des éléments du chargement (LoadedFeatures) et choisir si les éléments non chargés gardent leur état courant (par défaut) ou reprennent leur valeur par défaut (ExclusionMode). S'il lève une exception, elle est affichée et le profil est chargé en entier. Quand les tris courants sont conservés, OnSortChanged n'est pas déclenché ; quand les filtres le sont, la ligne de filtres garde l'état choisi par l'utilisateur. Voir Contenu des profils et chargement partiel.

  • LgGridSelectionColumn : nouveau paramètre CanSelectSelector (Func<TItem, bool>), qui indique pour chaque ligne si l'utilisateur peut la sélectionner. La case à cocher d'une ligne refusée est désactivée et, au survol de la ligne, la coche est remplacée par un cercle barré. Le clic sur la ligne (SelectableRows) ne la sélectionne pas et ne lève pas OnRowSelection (en SingleSelection, la sélection précédente est conservée), et « tout sélectionner » l'ignore. Une ligne refusée déjà sélectionnée reste désélectionnable. La condition ne restreint pas le code : SelectItem(), la collection liée à Selection et celle des arguments de OnRowSelection peuvent ajouter n'importe quel élément. Voir LgGridSelectionColumn. L'icône, la couleur et la taille de survol se règlent dans les thèmes avec $gridview-cell-selection-locked-hover-icon-name ('NoEntry', nouvel alias IconNames.NoEntry), $gridview-cell-selection-locked-hover-icon-color et $gridview-cell-selection-locked-hover-icon-size (1rem).

    <LgGridSelectionColumn CanSelectSelector="@((OrderRow order) => order.Status != OrderStatus.Closed)" />
  • LgGridView : une sélection modifiée dans OnRowSelection est désormais affichée. Modifier args.Selection (Add, Remove, Clear) rafraîchit les lignes concernées (classe de la ligne et case à cocher), par exemple pour désélectionner l'élément sélectionné précédemment ou pour refuser l'élément cliqué. Les éléments y sont comparés sur la clé de la grille (KeyField, ou Id sur une grille distante) : Remove retire aussi une autre instance du même élément, par exemple après un rechargement. args.Selection n'est plus l'instance de la collection liée à Selection, mais une vue de cette collection : ne la comparez pas à votre collection et ne la convertissez pas en HashSet<T> / List<T>. Voir LgGridView.

  • LgGridView : nouvelles méthodes SelectItem(item), UnselectItem(item) et ClearSelection(), qui modifient la sélection et son affichage sans déclencher OnRowSelection, et RefreshSelection(), qui met l'affichage à jour après une modification directe de la collection liée à Selection.

  • LgGridCommandColumn : le nouveau template CommandsContent déclare des commandes du menu pour chaque ligne. Son contexte expose l'élément de la ligne (context.Item) : une condition @if fait apparaître une commande seulement sur les lignes concernées. Ces commandes s'ajoutent après celles de Commands et de ChildContent, qui restent communes à toutes les lignes. Le menu d'une ligne sans commande n'est pas affiché. Seules des déclarations LgGridCommand sont acceptées dans le template (un LgAuthorizeView lève une NotSupportedException). Voir LgGridCommandColumn.

  • Les paramètres de colonne SortExpression, GroupExpression, FilterExpression, LambdaExpression et WeightExpression de LgGridView sont maintenant marqués [Obsolete] et masqués de l'IntelliSense. Ils fonctionnent toujours, mais leur usage produit l'avertissement CS0618. Utilisez à la place SortSelector, GroupSelector, FilterSelector, LambdaSelector et WeightSelector, par exemple SortExpression="GetLambdaExpression((Person p) => p.Name)" → SortSelector="@((Person p) => p.Name)". Voir LgGridTextColumn.

  • Le filtrage distant « valeur dans une liste » d'un contrôleur [Queryable] (celui d'un RemoteListDataSource, ou d'un ModelFilter.AddIncludedInList / AddEquals) accepte désormais les types char, DateOnly, TimeOnly et DateTimeOffset (et leurs versions nullables), en plus des chaînes, booléens, nombres, DateTime, enums, Guid et TimeSpan. Pour DateTimeOffset, l'égalité porte sur l'instant : 08:00 +02:00 est égal à 06:00 +00:00. La nouvelle méthode Filter.SupportsValueType(Type) indique si les filtres, et donc le filtrage distant d'un contrôleur [Queryable], supportent un type de valeur.

  • lgn update corrige le .editorconfig et le .gitattributes des applications : fins de ligne LF pour le C#, scripts .bat/.cmd sans BOM, indentation par défaut (2 espaces pour les fichiers MSBuild, XML, JSON et YAML), réglages obsolètes ou en double retirés. Les personnalisations sont conservées. Un .gitattributes est créé à la racine de la solution s'il manque.

Corrections​

  • ⚠ Le filtre des colonnes date de LgGridView (LgGridDateColumn et colonnes mois / année / semaine) fonctionne désormais sur une valeur DateOnly ou DateTimeOffset (ou nullable), en local comme sur une grille distante : la sélection par années / mois / jours ne présentait que des valeurs vides ou plantait, et la règle « Du / Au » produisait des dates erronées. Pour un DateTimeOffset, le filtre porte sur la date affichée, dans le décalage de la valeur (comme LgDateBox), et non sur l'instant. Changement de comportement : une plage d'un DateFilter<DateTimeOffset> construite par code (AddBetween, AddGreaterThan, AddLessThan) comparait les instants ; elle compare désormais les dates affichées, et seule la date des bornes compte (l'heure et le décalage des bornes sont ignorés). Pour comparer des instants, utilisez un NumericFilter<DateTimeOffset> (ce que crée ModelFilter.AddBetween) : il n'est utilisable qu'en mémoire, le filtrage distant le refuse.

  • LgSelect / LgSelectMultiple alimentés par un RemoteListDataSource<TItem, TValue> dont le type de valeur n'est pas scalaire (un record, une classe ou une structure, par exemple vm => new MyKey(vm.Code)) affichaient « Err! » au lieu du libellé des valeurs initiales : le serveur ne sait pas filtrer sur ce type de valeur. Les éléments sont désormais chargés sans filtre puis retenus en mémoire selon l'égalité de valeur (ValueEquals de la source). Les types scalaires restent filtrés côté serveur.

  • LgSelect / LgSelectMultiple alimentés par un RemoteListDataSource<TItem, TValue> dont le type d'élément distant diffère du type lié (cas habituel : un view model VmFirm pour un LgSelect<Guid?>) rechargent désormais leur liste quand ControllerQueryArg change (selects en cascade) ; ils gardaient jusqu'ici la liste du premier chargement. Un changement d'argument pendant un chargement annule la requête en cours et relance la requête avec le nouvel argument : la réponse obsolète n'est plus affichée. Après un changement de ControllerQueryArg ou un appel à ResetDataSource(), la requête n'est envoyée tout de suite que si le select a besoin des données (liste ouverte, chargement en cours, valeurs sélectionnées dont le libellé reste à résoudre) ; sinon elle est différée à la prochaine ouverture de la liste. La valeur sélectionnée n'est pas effacée quand l'argument change : si elle n'appartient plus à la nouvelle liste, remettez-la à null dans le gestionnaire qui change l'argument (modifier ControllerQueryArg suffit, sans appeler ResetDataSource()).

    private Task OnSelectedCountryChanged()
    {
    _myCar.FirmId = null;
    _firmsByCountryDataSource.ControllerQueryArg = _myCar.CountryId;
    return Task.CompletedTask;
    }
  • Un select à chargement asynchrone ouvert pendant la résolution du libellé de sa valeur sélectionnée restait bloqué sur l'indicateur de chargement jusqu'à sa fermeture : la liste est désormais chargée dès la fin de la résolution.

  • LgGridView : quand un gestionnaire OnCommandClick modifie args.Item sur place (même instance) en gardant le mode de rafraîchissement par défaut (GridEventRefreshMode.StateHasChangeRow), ou quand GridEventRefreshMode.ReloadRow recharge une grille locale (qui retrouve la même instance), la ligne affiche désormais les nouvelles valeurs. Depuis la 4.0.0, les cellules en lecture seule gardaient le texte affiché avant la commande jusqu'au prochain RefreshAsync(). Seule la ligne de la commande est recalculée.

  • LgGridView : une colonne affichée depuis les options de colonnes (par exemple une colonne DefaultVisible="false") garde sa largeur au rechargement de la page. Une colonne rendue visible par un profil, notamment un profil enregistré sur le serveur (StorageMode="GridViewProfileStorage.Remote"), ne s'affiche plus avec une largeur de 1 pixel. Après un redimensionnement manuel, la largeur d'une colonne ajoutée est aussi enregistrée dans le profil.

  • LgGridView : un filtre par défaut (DefaultFilter) supprimé par l'utilisateur ne réapparaît plus au rechargement du profil ; le DefaultFilter ne s'applique plus qu'aux colonnes absentes du profil. En conséquence, un DefaultFilter ajouté plus tard à une colonne existante ne s'applique pas aux utilisateurs dont le profil enregistre déjà cette colonne. Le bouton « Réinitialiser » du menu des colonnes remet désormais les DefaultFilter au lieu de vider tous les filtres.

  • LgGridView : un élément remplacé dans une ObservableCollection liée à Items (même collection, même nombre d'éléments) s'affiche de nouveau au rendu suivant ; la grille rechargeait ses données mais réaffichait l'ancien élément, conservé par son cache local.

  • LgGridView : la fenêtre Regroupements modifiait directement les regroupements de la grille pendant l'édition (« Annuler » ne les rétablissait pas), et un nouveau rendu de la grille pendant l'édition pouvait réinitialiser la fenêtre. L'édition se fait désormais sur une copie, appliquée seulement à la validation.

  • LgGridView : une colonne de type liste (LgGridEnumListColumn, LgGridSelectMultipleColumn, LgGridTagBoxColumn…) n'est plus proposée au regroupement. Regrouper sur une telle colonne faisait échouer le chargement de la grille (InvalidOperation_IComparerFailed), y compris au rechargement d'un profil ou avec le paramètre GroupBy : ces colonnes y sont maintenant ignorées et les niveaux suivants renumérotés.

  • LgGridView : un modèle d'élément (ItemModel, ou le type des éléments) dont la classe déclare un indexeur ne fait plus échouer l'affichage de la grille.

  • LgGridView : le message « La ligne d'ajout ou de mise à jour n'existe pas. » (enregistrement d'une ligne sans ligne en édition) est désormais traduit ; il s'affichait sous la forme ##EditFormNullException(fr?).

  • La liste déroulante des colonnes de la fenêtre de regroupement de LgGridView occupe de nouveau toute la largeur.

  • Filtre de date (LgDateFilterBox, filtre des colonnes date de LgGridView) : choisir une date dans le calendrier de l'onglet Condition (champs From / To) fermait le filtre sans appliquer la date. Le calendrier d'un LgDateBox garde désormais le focus sur le champ quand on clique dedans, ce qui corrige aussi un LgDateBox placé dans n'importe quelle liste déroulante ; un clic sur le champ rouvre le calendrier.

  • LgFrame : le paramètre ButtonSize est désormais appliqué aux boutons de la toolbar du cadre ; quand il était renseigné, la toolbar restait en taille Medium. Sans ButtonSize, FrameToolbarButtonSize s'applique comme avant.

  • Navigation au clavier : la touche Tab ne lève plus d'erreur JavaScript (e is not defined), et le focus reste de nouveau dans la fenêtre modale ouverte (l'erreur interrompait ce maintien du focus depuis la version 4.0.0).

  • Mode onglets : un onglet restauré au démarrage dont la page n'existe plus affiche la page « introuvable » à son activation, au lieu de rester vide.

  • Thèmes : au rechargement d'une page dont l'URL compte plusieurs segments (par exemple /lg/icon-library), le préchargement de la feuille de style du thème ne part plus en 404 sous la route courante (/lg/theme-xxx.css) et la console n'affiche plus l'erreur « Refused to apply style … MIME type ». Action : dans le index.html de l'application, placer tout <link rel="preload"> (ou autre URL relative) après l'élément <base href="{BASE_HREF}" />, sinon il est résolu sous la route courante.

Sécurité​

Cette version met à jour les dépendances suivantes :

  • .NET 10.0.13 (Patch Tuesday d'octobre 2026, 44 CVE). Les correctifs sont dans le runtime et le SDK, pas dans les paquets : installez l'ASP.NET Core Runtime / Hosting Bundle 10.0.13 sur tous les serveurs. Il corrige notamment deux failles critiques de request smuggling de Kestrel (CVE-2026-88092, CVE-2026-70188), des dénis de service non authentifiés sur les formulaires et le multipart (CVE-2026-70181, CVE-2026-70196) et dans la validation des signatures XML utilisée par l'authentification SAML (CVE-2026-58280, CVE-2026-69651, CVE-2026-96958, CVE-2026-96959, CVE-2026-96960). Installez le SDK 10.0.113 / 10.0.402 sur les postes et la CI (CVE du SDK, et runtime embarqué dans les clients Blazor WebAssembly, qui doivent être republiés) ; les applications qui activent HTTP/3 sous Windows sont exposées à CVE-2026-105016 (exécution de code, critique) tant que le runtime n'est pas à jour.
  • Microsoft.AspNetCore.Components.WebAssembly.Server 10.0.13 : corrige CVE-2026-102870 et CVE-2026-96230 (outils de débogage, postes de développement). Ce correctif est porté par le paquet : montez la référence du projet Server de vos applications.
  • PuppeteerSharp 25.14.0 (Chrome 155, plus de 300 correctifs de sécurité du navigateur) : les applications utilisant Lagoon.PDF avec 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.12 : corrige GHSA-7rj2-5w9w-r9jx (arrêt du processus par un flux LZ4 forgé). Lagoon n'est pas directement exposé, mais une application qui désérialise du MessagePack reçu de ses clients avec la compression LZ4 l'était.
  • System.Security.Cryptography.Xml / System.Security.Cryptography.Pkcs 10.0.13 : Lagoon.Server et Lagoon.Services les référencent désormais directement, ce qui remplace les versions vulnérables tirées par DataProtection, OpenIddict et MimeKit et fait disparaître les avertissements NU1902 / NU1903 de l'audit NuGet dans vos projets. Le correctif effectif reste celui du runtime 10.0.13. Si votre projet Model référence Microsoft.Data.SqlClient, ajoutez-y System.Security.Cryptography.Pkcs 10.0.13 pour lever le dernier avertissement.
  • Éditeur HTML (LgHtmlEditor) : Jodit 4.18.2 : corrige trois failles XSS de l'éditeur publiées en septembre et octobre 2026 (GHSA-gr86-hxq8-g84q, critique : contournement du filtre par <noscript> ; GHSA-7pxg-rf9q-fg33 : HTML stocké exécuté avant son nettoyage ; GHSA-jhhp-r3r7-v2cg : HTML déposé par glisser-déposer). Le contenu déposé par glisser-déposer depuis l'extérieur de l'éditeur passe désormais par le traitement du collage : il est assaini comme un contenu collé, la boîte de dialogue de collage HTML s'affiche le cas échéant, et OnPasteAsync est aussi appelé pour ce contenu. L'assainissement de Jodit reste côté navigateur : assainissez côté serveur (LgHtmlSanitizer) les contenus HTML partagés entre utilisateurs.
  • Page « Mot de passe oublié » : voir la section Lien de réinitialisation du mot de passe envoyé à l'adresse enregistrée ci-dessus.

Dépendances​

  • Paquets .NET (Microsoft.AspNetCore.*, Microsoft.Extensions.*, Microsoft.EntityFrameworkCore.*, Microsoft.JSInterop.WebAssembly, System.DirectoryServices.AccountManagement) alignés sur 10.0.13 (Patch Tuesday d'octobre 2026). L'action sur les serveurs et les postes (runtime / SDK) est décrite dans la section Sécurité.
  • Mises à jour : AngleSharp 1.8.5, MessagePack 3.1.12, OpenIddict 7.7.2, PuppeteerSharp 25.14.0 (Chrome 155), Swashbuckle.AspNetCore 10.3.0, AWSSDK.S3 4.0.104.4, Azure.Storage.Blobs 12.30.1, Jodit 4.18.2.
  • OpenIddict 7.7.2 : sur .NET 10, l'enregistrement de la tâche de purge OpenIddict n'écrase plus les options Quartz.NET configurées par l'application.
  • Swashbuckle 10.3.0 change la représentation de la nullabilité (anyOf / allOf / oneOf, type: null) : les documents OpenAPI générés peuvent différer, à vérifier si des clients sont générés à partir de votre Swagger.
  • La copie Lagoon du script d'authentification Blazor (AuthenticationService.js) est alignée sur la version 10.0.13 de Microsoft.AspNetCore.Components.WebAssembly.Authentication (script inchangé, nouveau numéro de cache ?v=10.0.13).

Action : les applications dont les références Microsoft.AspNetCore.* / EF Core suivent 10.* (posé par la migration 4.0) récupèrent les versions 10.0.13 à la restauration ; celles qui épinglent une version la montent en 10.0.13, en particulier Microsoft.AspNetCore.Components.WebAssembly.Server du projet Server, dont le correctif de sécurité est porté par le paquet. Mettre aussi à jour l'outil local dotnet-ef (dotnet tool update dotnet-ef).