Recherche & generation PDF
Recherche (Services/SearchService.cs)
ISearchService/SearchService(scoped) : recherche full-text sur les articles, qui proviennent du CMS headless.- Page de resultats :
searchPage(vueSearchPage.cshtml). - Cote frontend : module JS
scripts/modules/search.js.
Filtre par type & SEO de la page de resultats
- Chaque
SearchResultItemporte unElementType(LINK= page locale,ARTICLE= article headless,MEDIA). La vueSearchPage.cshtmlrend deux dropdowns Bootstrap (form-select, pattern repris de pully-website) : type (?type=article|link|media, option affichee seulement si le type a des resultats) et annee (?year=, affiche seulement si 2+ annees distinctes). Filtrage server-side dans la vue (leSearchWithPageest cache 2 min, donc rechargement bon marche) ; les selects auto-soumettent le form GET (search.js→initResultFilters, classe.js-search-filter). Libelles via dictionnaireSearchPage.Filter.*(fallback FR en dur). - URL des resultats articles : les articles headless n'ont pas de nœud local — leur URL doit etre construite
{url du BlogPage du site}/{slug}(routee parBlogContentFinder).SearchInArticlesprend unsiteRoot(leRoot()de la search page) pour resoudre le BlogPage ; un slug brut serait resolu relativement a/recherche/→ 404 (bug corrige le 2026-07-16, idem dansmapBlock.cshtml;favoriteBlock.cshtmlavait deja le bon pattern). - Piege de config contenu : la propriete
includeArticles(« Articles inclus », dropdown multiple) du nœud Recherche doit contenir les skins voulus (accm,icogne,linfo) — si elle est vide ou pointe le mauvais skin,SearchWithPagen'interroge pas du tout les articles headless (constate sur staging le 2026-07-15 : valeur["icogne"]→ aucun article ACCM dans la recherche). - SEO : les resultats de recherche ne doivent jamais etre indexes. Verrouille cote code a deux endroits :
SearchPage.cshtmlposeViewBag.HideFromSitemap = true(les 4 layouts rendent alors<meta name="robots" content="noindex">) etPublishedContentExtensions.HideFromSiteMap()forcetruepour le doctypesearchPage(exclusion du sitemap XML), independamment de ce que coche l'editeur.
Exclusion des conteneurs sans template
SearchInContent ne retourne que les nœuds qui possedent un template rendu (TemplateId > 0). Les doctypes de type conteneur (ex. pageContainer, « Conteneur de pages ») n'ont pas de template : ils servent uniquement a regrouper des enfants dans l'arbre. Leur Url() renvoie pourtant une URL valide via l'UrlProvider, mais la navigation aboutit a un 404 (pas de template a rendre). Sans le filtre TemplateId, ces conteneurs apparaissaient dans les resultats et generaient des 404 au clic. Garder ce filtre pour tout nouveau doctype non rendu.
Recherche full-text dans le corps des pages (fullTextContent)
Par defaut l'ExternalIndex n'indexe que nodeName, metaTitle, metaDescription : un mot present dans le corps d'une page (BlockGrid/BlockList/RTE) ne remontait pas. Examine/FullTextIndexHandler (branche sur TransformingIndexValues de l'ExternalIndex, enregistre dans SiteComposer) calcule un champ fullTextContent agregeant tout le texte extractible via Examine/FullTextExtractor (parcours du JSON des blocks, strip HTML, saut des GUID/Udi et des champs de plomberie). SearchInContent interroge ce champ avec un boost faible (^1 exact / ^0.5 fuzzy) pour ne jamais devancer un hit titre/meta.
IMPORTANT (operationnel) : le champ n'est peuple qu'a l'(re)indexation. Apres deploiement, faire un rebuild de l'ExternalIndex (backoffice → Settings → Examine Management → ExternalIndex → Rebuild), sinon
fullTextContentreste vide et la recherche dans le corps ne remonte rien.
Analyzer : accents, apostrophes, separateurs de milliers
Examine/AccentAndSeparatorAnalyzer (enregistre via Examine/ExternalIndexAnalyzerConfigurator en IPostConfigureOptions<LuceneDirectoryIndexOptions> dans Program.cs) enveloppe l'analyzer de l'ExternalIndex. Comme Examine utilise le meme analyzer a l'indexation ET a la requete, la normalisation s'applique des deux cotes :
- Char filters : variantes d'apostrophe (
’‘ʼ) →'; separateur de milliers entre deux chiffres (apostrophe + espaces insecables/fines) supprime →19'702=19 702=19702. - Token filter :
ASCIIFoldingFilter→ repli des diacritiques, doncfête=feteau niveau de l'index (insensibilite reelle aux accents, au-dela du simple fuzzy~1).
Necessite aussi un rebuild de l'ExternalIndex pour s'appliquer au contenu existant.
Fuzzy et suggestion « vouliez-vous dire »
La requete principale utilise un fuzzy ~1 (mots ≥ 4 car.) pour tolerer une faute legere sans polluer les resultats. Quand une recherche ne renvoie aucun resultat, SearchWithPage relance une passe fuzzy agressif (~2 pour 5+ car., ~1 pour 4 car., via BuildAggressiveFuzzyTerm) sur le contenu + les medias — uniquement pour constituer un pool de candidats et proposer une correction (BuildSuggestion / SearchTextHelper.BuildSuggestedQuery). Ces hits agressifs ne sont pas affiches comme resultats. Les articles headless sont exclus de cette passe (pas d'index Examine a « fuzzer »).
Portage inspire de
pully-website(SearchService,FullTextIndexHandler,ThousandsSeparatorAnalyzer), adapte a ACCM (non culture-variant, ajout du folding d'accents).
Generation PDF (Services/PdfGeneratorService.cs)
IPdfGeneratorService/PdfGeneratorService(scoped).- Stack : itext 9.3 pour le rendu PDF + Fluid (templates Liquid) pour le contenu.
- Usage : generation de documents PDF a partir de contenu/templates.
Ces deux services sont enregistres dans Program.cs.

