Skip to content

Patterns - comment ajouter / modifier

Ajouter un widget

Un widget = ViewComponent + vue, plus (optionnel selon style/interactivite) SCSS, TS, bundle WebOptimizer. Voir l'anatomie detaillee dans ../modules/widgets.md.

  1. ViewComponent : creer Widget<Nom>ViewComponent.cs dans App.Core/ViewComponents/, heriter de ViewComponent, implementer InvokeAsync(...), injecter ce qu'il faut (IDomainService pour le skin, Ii18nService, ...), retourner View("Default", model).
  2. Vue : src/Nanoxi/.../App.Core/Views/Components/Widget<Nom>/Default.cshtml. Si interactif, emettre l'init JS.
  3. View model : dans App.Model/ViewModel/Component/.
  4. Document type : ajouter le widget<nom> (+ widget<nom>Settings / item si necessaire) dans uSync (src/Umbraco/uSync/v9/ContentTypes/), et l'autoriser dans le BlockGrid.
  5. SCSS (si style) : src/Nanoxi/Design/default/css/component/widget<nom>/ + declarer le bundle CSS correspondant dans src/Umbraco/Program.cs (pipeline.AddCssBundle($"/css/{skin}/bundle-widget<nom>.css", ...)).
  6. TypeScript (si interactif) : src/Nanoxi/TypeScript/ts/component/widget<nom>/ ; le JS compile (dist/) est copie vers wwwroot/scripts/ et reference par un AddJavaScriptBundle(...) dans Program.cs.

Note : contrairement a d'autres variantes Nanoxi (Vite), ici le bundling CSS/JS est gere par WebOptimizer dans Program.cs. Un nouveau bundle doit donc y etre declare, pas dans un vite.config.ts.

Ajouter un service

  1. Definir l'interface dans App.Interface (dossier thematique : Domain, Email, Google, Media, Navigation, ...).
  2. Implementer dans le projet adequat (Services/XxxService.cs).
  3. Enregistrer dans src/Umbraco/Program.cs (services.AddScoped<IXxxService, XxxService>()).
  4. Injecter par constructeur la ou c'est utilise.

Ajouter un repository (donnees custom NPoco)

  1. Modele/mapping avec [TableName("nanoxi_...")] + [PrimaryKey("Id")].
  2. Interface dans App.Interface, implementation dans App.Repository/Repositories/ injectant IScopeProvider.
  3. Toujours encapsuler dans using (var scope = _scopeProvider.CreateScope()) { ... scope.Complete(); }.
  4. Enregistrer dans Program.cs.
  5. La table doit exister : ajouter le script de creation dans data/01.Scratch/ (prefixe ordonne, ex. 050.NanoxiTable).

Ajouter une validation de formulaire

  1. Creer AbstractValidator<TViewModel> dans App.Validation.
  2. Enregistrer services.AddTransient<IValidator<TViewModel>, TValidator>() dans Program.cs.
  3. Cote vue, FormHelper traduit en validation client (jQuery Validation).

Ajouter un document type / une composition

Editer via le backoffice Umbraco puis exporter avec uSync (les .config apparaissent sous src/Umbraco/uSync/v9/ContentTypes/), OU ecrire directement le .config. Attention aux alias reserves (voir business-rules.md). Reutiliser les compositions existantes (SEO, Sitemap, Widget).

Ajouter une route custom

Dans src/Umbraco/Program.cs, apres le mapping des endpoints Umbraco (MapUmbracoRoute), via MapControllerRoute(...) (le sitemap y est declare comme exemple).

Email

IEmailService (App.Mail) rend un template Razor via IViewRender puis envoie en SMTP. Les templates vivent dans App.Mail/Templates/ (copies au build vers Views/Partials/Templates). SMTP configurable par domaine d'envoi (IOptions<SmtpOptions>, section Nanoxi:CMS:MailSettings:Domains).

Cache applicatif (RuntimeCache) et invalidation

Pour les donnees derivees du contenu qui se rendent sur chaque page mais ne changent qu'a l'edition (navigation de premier niveau, sitemap), on cache dans le RuntimeCache partage Umbraco (AppCaches.RuntimeCache, injecte via AppCaches), pas dans un champ de service.

  • Cle : constante dans App.Definition/AppDefs_Cache.cs (AppDefs.Cache.Key.*). Pour les donnees variant par langue, suffixer la cle par culture : $"{NavigationCacheKey}.{culture}" (culture = Thread.CurrentThread.CurrentCulture.Name).
  • Lecture : appCaches.RuntimeCache.GetCacheItem(cacheKey, BuildXxx).
  • Invalidation par notifications (App.Notification/Content) : ContentPublished (publication) + ContentStructureChanged (unpublish / move / trash / delete). Les deux appellent _runtimeCache.ClearByKey(AppDefs.Cache.Key.XxxKey). ClearByKey matche par prefixe, donc ClearByKey("NavigationCacheKey") purge toutes les variantes de culture d'un coup. Handlers enregistres dans Program.cs via .AddNotificationHandler<...Notification, ...>().
  • Pieges :
    • Copie defensive : si un consommateur mute la liste (ex. MenuViewComponent -> Pages.RemoveAll), exposer une copie par requete (new List<>(entry.Items)), jamais la reference cachee, sinon corruption inter-requetes.
    • Ne jamais accumuler dans un champ d'instance dans la factory de cache : construire une liste locale (sinon doublons a chaque cache-miss, cf. ancien bug SitemapCacheService).
    • Cacher des IPublishedContent epingle le snapshot NuCache courant : acceptable tant que l'invalidation couvre tout changement de contenu (publish + structure), ce qui relache la reference a la generation suivante.

Memoisation par requete (service scoped)

Un service scoped (une instance par requete) peut memoiser des lookups repetes dans des champs d'instance sans risque de fuite (libere en fin de requete). Ex. DomainService : le noeud domaine est resolu une seule fois (GetDomainPage() + flag _domainPageResolved) et les valeurs string sont mises en cache dans un Dictionary (_stringPropertyCache), au lieu d'un GetById par appel de propriete (~6x/page). Ne PAS faire ca dans un singleton (le champ retiendrait un IPublishedContent et epinglerait un snapshot NuCache durablement).

Contributors

No contributors

Changelog

No recent changes