Fermer

février 7, 2019

Traitement des métadonnées de site avec des modules SXA: Partie 1


Contexte

Si vous utilisez Sitecore 9.0+, il y a de grandes chances que vous ayez déjà entendu parler de Sitecore Experience Accelerator ou que vous utilisiez actuellement ce dernier pour personnaliser votre instance. À mon avis, l'un des avantages de SXA est l'utilisation de modules pour ajouter des quantités discrètes de fonctionnalités à un locataire ou à un site en tirant parti des actions d'échafaudage . L'un des premiers cas d'utilisation d'un module personnalisé que j'ai rencontré a été l'examen des résultats de page de Google Lighthouse dans notre page Google Lighthouse.

 Résultat d'image pour le rapport exemple Google Lighthouse

Il est évident que ce n'est pas exactement le rapport Lighthouse. généré, mais dans le cadre de la vérification des meilleures pratiques, Google recherche le fichier manifest.json qui contient des informations sur un site lui indiquant comment se comporter pour les appareils mobiles et les ordinateurs de bureau. utilisateurs. Voici le contenu d'un fichier typique:

Comme vous pouvez probablement le constater, il est principalement destiné aux utilisateurs mobiles qui installent l'application Web sur leur écran d'accueil. Normalement, il s’affiche sous la forme d’un fichier statique dans le répertoire racine de l’application et est lié à la page à l’aide d’un extrait de code dans l’élément comme suit:

Si rien ne change jamais avec votre application, c'est toute l'étendue de vos efforts de mise en œuvre. Hourra! Vous avez maintenant un manifeste d'applications Web, vos utilisateurs de mobile sont satisfaits et le score "Meilleures pratiques" de votre rapport Phare a augmenté.

… malheureusement, les choses ne sont jamais aussi simples. Plusieurs variables n'ont pas été prises en compte, en particulier lorsque vous ajoutez Sitecore à l'équation:

  1. Que se passe-t-il s'il y a plusieurs sites / locataires?
  2. Et si la couleur de mon thème / les icônes / la couleur de l'arrière-plan changent?
  3. Que faire si le fichier manifeste se trouve dans le contrôle de source et est remplacé lors du déploiement sur mon site?
  4. Puis-je définir ces variables à partir de Sitecore?

Heureusement pour vous, si l'une de ces réponses vous était familière, vous êtes un bon candidat pour les informations suivantes. Dans la section suivante, nous verrons comment implémenter un fichier manifeste dynamique et mis en cache qui peut être contrôlé indépendamment par les locataires et qui ne sera pas remplacé lors des déploiements. En prime, ce module gérera l'ajout de deux balises personnalisées supplémentaires pour la couleur du thème et la méta-description, deux éléments utiles pour l'amélioration des scores du rapport Lighthouse.

Implémentation

Cette partie couvre le code. derrière cela, nous devons faire en sorte que cela fonctionne, et la partie 2 couvrira la configuration de Sitecore. Avant de commencer, vous devez disposer des éléments suivants (ou d’une configuration très similaire):

  • Sitecore Experience Accelerator 1.7
  • Les extensions Sitecore Powershell 4.7.2
  • Unicorn 4.0+

Vous pouvez créer votre projet propre, ou référencez l’un des extraits de code présentés, en les visionnant sur github . N'oubliez pas que cette structure est conforme aux conventions Helix, comme tout bon projet Sitecore. Étant donné que cette fonctionnalité sera ajoutée en tant que module SXA, j'ai décidé de la placer dans la couche d'entités.

Le secret de ce module réside dans le pipeline HttpRequestBegin. Ce pipeline s'exécute à chaque requête et constitue un excellent candidat pour créer ou récupérer un fichier manifeste généré dynamiquement. Nous allons ajouter un processeur personnalisé appelé WebAppManifestHandler à notre projet et l'ajouter au pipeline à l'aide d'un correctif de configuration.


    
         720 
    

Ceci transmettra également une valeur CacheExpiration, qui sera utilisée ultérieurement lors de la mise en cache du fichier manifeste. . Passons maintenant aux bits importants du gestionnaire.

 public class WebAppManifestHandler: HttpRequestProcessor
{
    lecture seule privée IContext _context;
    lecture seule privée BaseCorePipelineManager _pipelineManager;

    public int CacheExpiration {get; ensemble; }

    public WebAppManifestHandler (contexte IContext, BaseCorePipelineManager pipelineManager)
    {
        _context = context ?? jette new ArgumentNullException (nameof (context));
        _pipelineManager = pipelineManager? lance la nouvelle ArgumentNullException (nameof (pipelineManager));
    }

    processus de substitution publique public (arguments HttpRequestArgs)
    {
        var url = args.HttpContext.Request.Url;
        if (AbortProcessor (url)) return;

        var manifeste = GetManifestFromCache (url? .ToString ());
        si (manifeste! = null)
        {
            RenderManifest (arguments, manifeste);
            revenir;
        }

        var webAppManifestArgs = new GetWebAppManifestContentArgs ();
        _pipelineManager.Run ("getWebAppManifestContent", webAppManifestArgs);

        manifest = JsonConvert.SerializeObject (webAppManifestArgs.Result, Formatting.Indented);
        CacheManifestContent (url? .ToString (), manifeste);
        RenderManifest (arguments, manifeste);
    }

    bool privé AbortProcessor (URL URI)
    {
        if (! url.PathAndQuery.EndsWith ("/ manifest.json", StringComparison.OrdinalIgnoreCase))
            retourne vrai;
        var siteInfo = _context.Site.SiteInfo;
        return siteInfo == null || ! UrlUtils.IsUrlValidForFile (url, siteInfo, "/manifest.json");
    }

    chaîne privée GetManifestFromCache (chaîne requestUrl)
    {
        var cacheKey = GetCacheKey (requestUrl);
        var manifest = HttpRuntime.Cache [cacheKey] en tant que chaîne;
        retourner le manifeste;
    }

    void statique privé RenderManifest (arguments HttpRequestArgs, manifeste de chaîne)
    {
        args.HttpContext.Response.ContentType = "application / json";
        args.HttpContext.Response.ContentEncoding = Encodage.UTF8;
        args.HttpContext.Response.Write (manifeste);
        args.HttpContext.Response.End ();
        args.AbortPipeline ();
    }

    void virtuel protégé CacheManifestContent (string requestUrl, manifeste de chaîne)
    {
        var cacheKey = GetCacheKey (requestUrl);
        HttpRuntime.Cache.Insert (cacheKey, manifeste, null, DateTime.UtcNow.AddMinutes (CacheExpiration), Cache.NoSlidingExpiration);
    }

    chaîne privée GetCacheKey (chaîne requestUrl)
    {
        return $ @ "{Constants.ManifestCacheKey} / {_ context.Database? .Name} / {_ context.Site?.NameName/{requestUrl}";
    }
} 

Ce gestionnaire héritant de HttpRequestProcessor, nous pouvons remplacer la méthode Process pour récupérer le manifeste. Il vérifie d’abord le manifeste dans le cache et, s’il n’est pas trouvé, exécute le pipeline GetWebAppManifestContent et met en cache le résultat d’un objet JSON sérialisé à l’aide d’une clé de cache personnalisée et de la variable CacheExpiration transmise à partir du correctif de configuration. Une fois que nous avons le manifeste et que nous l'avons mis en cache, nous pouvons restituer le contenu avec la méthode RenderManifest. Cela signifie que toute demande adressée à https://sitecore.site/manifest.json restituera le manifeste généré, y compris les références via l'étiquette de lien mentionnée ci-dessus (c'est-à-dire ).

Maintenant nous devons associer le contenu manifeste à Sitecore. Pour cela, nous allons créer un pipeline personnalisé appelé getWebAppManifestContent et l'ajouter à notre fichier de correctif de configuration.

 
    
 

Comme indiqué dans la documentation, nous avons besoin d'un processeur personnalisé et de ses arguments pour pouvoir remplacer la méthode Process dans la classe de pipeline proprement dite. Cela est aussi simple que de faire hériter de PipelineArgs une classe d'arguments et d'ajouter les propriétés dont nous avons besoin (dans ce cas, un modèle JSON pour le rendu et la mise en cache du contenu du manifeste). Cette classe est ensuite utilisée dans une classe de processeur abstraite, qui est ensuite remplacée.

 public class GetWebAppManifestContent: GetWebAppManifestContentProcessor
{
    privé en lecture seule IMultisiteContext _multisiteContext;
    lecture seule privée IContext _context;
    lecture seule privée BaseMediaManager _mediaManager;
    lecture seule privée BaseLinkManager _linkManager;

    public GetWebAppManifestContent (IMultisiteContext multisiteContext, contexte IContext, BaseMediaManager mediaManager, BaseLinkManager linkManager).
    {
        _multisiteContext = multisiteContext ?? jette new ArgumentNullException (nameof (multisiteContext));
        _context = context ?? jette new ArgumentNullException (nameof (context));
        _mediaManager = mediaManager ?? lance la nouvelle ArgumentNullException (nameof (mediaManager));
        _linkManager = linkManager ?? lance la nouvelle ArgumentNullException (nameof (linkManager));
    }

    Processus d'annulation de remplacement public (arguments GetWebAppManifestContentArgs)
    {
        if (args.Result! = null) return;
        var settingsItem = GetSiteSettings ();
        if (settingsItem == null) renvoie;
        if (settingsItem.InheritsFrom (nouvel ID (Templates.WebAppManifest.Id)))
        {
            args.Result = GetManifestContent (settingsItem);
        }
    }

    article privé GetSiteSettings ()
    {
        return _multisiteContext.GetSettingsItem (_context.Database.GetItem (_context.Site.StartPath));
    }

    private WebAppManifestJsonModel GetManifestContent (Paramètres d'objetItem)
    {
        var manifest = new WebAppManifestJsonModel
        {
            ShortName = settingsItem.Fields [Templates.WebAppManifest.Fields.ShortName] ?. Valeur ?? string.Empty,
            Nom = settingsItem.Fields [Templates.WebAppManifest.Fields.FullName] ?. Valeur ?? string.Empty,
            Description = settingsItem.Fields [Templates.WebAppManifest.Fields.Description] ?. Valeur ?? string.Empty,
            Icones = new []
            {
                GetIcon (settingsItem, Templates.WebAppManifest.Fields.LargeIcon, "512x512"),
                GetIcon (settingsItem, Templates.WebAppManifest.Fields.SmallIcon, "192 x 192")
            },
            ThemeColor = settingsItem.Fields [Templates.WebAppManifest.Fields.ThemeColor] ?. Valeur ?? "#FFFFFF",
            BackgroundColor = settingsItem.Fields [Templates.WebAppManifest.Fields.BackgroundColor] ?. Valeur ?? "#FFFFFF",
            StartUrl = GetRelativeUrl (settingsItem.Fields [Templates.WebAppManifest.Fields.StartUrl]),
            Display = GetDisplayType (settingsItem.Fields [Templates.WebAppManifest.Fields.Display]) ?? "autonome",
            Scope = GetRelativeUrl (settingsItem.Fields [Templates.WebAppManifest.Fields.Scope])
        };

        retourner le manifeste;
    }

    objet privé GetIcon (Item settingsItem, string fieldId, taille de la chaîne)
    {
        var iconImage = (ImageField) settingsItem.Fields [fieldId];
        var iconMedia = iconImage? .MediaItem;
        if (iconMedia == null)
        {
            retourner nouveau
            {
                src = string.Empty,
                type = string.Empty,
                tailles = taille
            };
        }

        retourner nouveau
        {
            src = _mediaManager.GetMediaUrl (iconMedia),
            type = _mediaManager.MimeResolver.GetMimeType (iconMedia) ?? string.Empty,
            tailles = taille
        };
    }

    chaîne privée GetRelativeUrl (champ)
    {
        if (champ == null) renvoie null;
        var item = _context.Database.GetItem (field.Value);
        retourner l'article! = null? _linkManager.GetItemUrl (item): _context.Site.VirtualFolder;
    }

    chaîne statique privée GetDisplayType (champ)
    {
        if (champ == null) renvoie null;
        var displayItem = (champ (ReferenceField)) .TargetItem;
        retour displayItem? .Fields [Sitecore.XA.Foundation.Common.Templates.Enum.Fields.Value] ?. Valeur;
    }
} 

Cette classe va d’abord et avant tout vérifier si l’élément paramètres du site contextuel hérite du modèle de manifeste Web App (traité dans la partie 2). Ce modèle partiel conservera le contenu du manifeste dans Sitecore afin que les auteurs de contenu puissent contrôler les différentes valeurs du manifeste. Et comme il est ajouté en tant que modèle partiel à l'élément Paramètres, le contenu du manifeste peut être séparé site par site, ce qui signifie qu'il n'y a aucune interférence avec d'autres sites. Si la classe trouve le modèle partiel, elle essaiera de créer un nouveau modèle JSON à partir des valeurs de champ. Assurez-vous d’avoir défini correctement les valeurs de champ dans le fichier Modèles après avoir créé le modèle partiel, sinon les valeurs ne seront pas résolues correctement. Enfin, il existe quelques méthodes d'assistance pour obtenir les types d'affichage, les icônes et les URL.

À ce stade, nous pouvons générer le contenu du manifeste dans Sitecore, le restituer et le mettre en cache si nécessaire. La prochaine pièce de ce casse-tête implique la publication. Que se passe-t-il si vous décidez de modifier l'une des valeurs de Sitecore? Le manifeste étant mis en cache, les mises à jour ne se propagent pas lors d’une publication. Heureusement, Sitecore inclut plusieurs gestionnaires d'événements que nous pouvons augmenter de la même manière que le pipeline HttpRequestBegin. Dans ce cas, nous devrons vider le cache une fois la publication terminée, ce qui se fait facilement via le correctif de configuration.

 
    
        
    
    
        
    
 

Ceci couvrira les publications locales et distantes d'un site. La classe elle-même est très simple.

 public class WebAppManifestCacheClearer: CacheClearerBase
{
    lecture seule privée BaseLog _baseLog;

    WebAppManifestCacheClearer public (BaseLog baseLog)
    {
        _baseLog = baseLog ?? jette new ArgumentNullException (nameof (baseLog));
    }
    public void ClearManifestCache (expéditeur d'objet, args EventArgs)
    {
        Assert.ArgumentNotNull (expéditeur, nom de (expéditeur));
        Assert.ArgumentNotNull (arguments, nom de (arguments));
        _baseLog.Info ("ManifestCacheClearer efface le cache HTTP pour tous les sites.", this);
        DeleteCachesWithKeyPrefix (Constants.ManifestCacheKey);
    }
} 

La méthode principale utilise la clé ManifestCacheKey pour supprimer le contenu du manifeste mis en cache une fois la publication terminée. Pour cette raison, le contenu devra être à nouveau mis en cache, mais l'opération ne sera ni très coûteuse ni longue, et le contenu mis à jour sera disponible pour toutes les demandes effectuées dans le manifeste. Le pipeline PreProcessRequest de SXA inclut un filtre permettant d’empêcher le chargement de certaines extensions de fichier, à l’exception de fichiers tels que les fichiers sitemap ou robots. Nous pouvons ajouter une nouvelle exception dans notre fichier de correctif de configuration comme ceci:

 
    
        
           manifest.json 
        
    
 

Le fichier manifeste ne sera pas bloqué par le pipeline et pourra être chargé avec succès.

Nous avons presque terminé, et le dernier élément qui reste consiste à générer la balise link pour l'afficher sur une page. , ainsi que les autres balises de bonus que j'ai mentionnées précédemment. En utilisant le modèle de référentiel nous allons générer quelques balises de lien en remplaçant la fonctionnalité de balise méta de SXA. Le référentiel ressemble à ceci:

 public class WebAppManifestRepository: MetadataBaseRepository 
{
    lecture seule privée IContext _context;
    privé en lecture seule IMultisiteContext _multisiteContext;
    privé en lecture seule IPageMode _pageMode;

    public WebAppManifestRepository (contexte IContext, IMultisiteContext multisiteContext, IPageMode pageMode)
    {
        _context = context ?? jette new ArgumentNullException (nameof (context));
        _multisiteContext = multisiteContext ?? jette new ArgumentNullException (nameof (multisiteContext));
        _pageMode = pageMode ?? jette new ArgumentNullException (nameof (pageMode));
        Cartographie = nouveau dictionnaire  ();
    }

    remplacement public WebAppManifestRenderingModel GetModel ()
    {
        var instance = base.GetModel ();
        instance.ManifestPath = GetManifestPath ();
        renvoyer _pageMode.IsExperienceEditorEditing? MockEmptyValues ​​(instance): FilterEmptyValues ​​(instance);
    }

    private WebAppManifestRenderingModel MockEmptyValues ​​(modèle WebAppManifestRenderingModel)
    {
        var metaTags = model as IList ?? model.MetaTags.ToList ();
        foreach (var metaTagModel dans metaTags.Where (tagModel => string.IsNullOrEmpty (tagModel.Content))
        {
            var name = Rendering.Item.Database.GetItem (metaTagModel.Id) .Name;
            metaTagModel.Content = $ @ "[{name} field is empty or is missing on the context item]";
        }
        var source = new WebAppManifestRenderingModel
        {
            MetaTags = metaTags,
            ManifestPath = "~ / manifest.json"
        };
        retourner la source;
    }

    statique privée WebAppManifestRenderingModel FilterEmptyValues ​​(modèle WebAppManifestRenderingModel)
    {
        var obj = new WebAppManifestRenderingModel
        {
            MetaTags = model.MetaTags.Where (tagModel =>! String.IsNullOrEmpty (tagModel.Content)),
            ManifestPath = model.ManifestPath
        };
        retour obj;
    }

    chaîne privée GetManifestPath ()
    {
        if (! _context.Site.VirtualFolder.Equals ("/"))
            return $ @ "{_ ​​context.Site.VirtualFolder} manifest.json";
        renvoyer "/manifest.json";
    }

    protégé remplacer IEnumerable GetMetaTags ()
    {
        var list = BuildModelMapping (). ToList ();
        list.Add (nouveau MetaTagModel
        {
            Name = "thème-color",
            Content = GetThemeColor ()
        });
        list.Add (nouveau MetaTagModel
        {
            Nom = "description",
            Contenu = GetDescription ()
        });
        return list.Where (metaTagModel => metaTagModel! = null);
    }

    chaîne privée GetThemeColor ()
    {
        var settingsItem = GetSiteSettings ();
        var themeColor = settingsItem [Templates.WebAppManifest.Fields.ThemeColor];
        return! string.IsNullOrEmpty (themeColor)? themeColor: "#FFFFFF";
    }

    chaîne privée GetDescription ()
    {
        var settingsItem = GetSiteSettings ();
        var description = settingsItem [Templates.WebAppManifest.Fields.Description];
        return! string.IsNullOrEmpty (description)? description: "description";
    }

    article privé GetSiteSettings ()
    {
        return _multisiteContext.GetSettingsItem (_context.Database.GetItem (_context.Site.StartPath));
    }
} 

Ce référentiel sera utilisé par une classe de contrôleur utilisée dans le rendu d'un contrôleur Sitecore. Dans ce cas, le modèle de rendu hérite du stock SXA MetaTagRenderingModel et ajoute une nouvelle propriété appelée ManifestPath. Cela est nécessaire car certains sites peuvent avoir des valeurs différentes pour leurs dossiers virtuels par rapport à d'autres. Avant que cette propriété ne soit définie, les propriétés de base du modèle sont renseignées et une autre méthode appelée GetMetaTags est remplacée afin que nous puissions ajouter les balises de méta description et de couleur de thème au modèle de base. Ce processus est effectué à l'aide de méthodes d'assistance qui analysent les valeurs des champs d'élément Settings du site contextuel. Le ManifestPath est ensuite défini et les valeurs sont finalement maquillées ou filtrées selon que la page se trouve ou non dans l'éditeur d'expérience. Le modèle est ensuite passé dans la vue et affiché sur une page:

 @ utilisant Sitecore.XA.Feature.SiteMetadata.Models
@model YourProject.Feature.SiteMetadata.Models.WebAppManifestRenderingModel

@ {
    Layout = Sitecore.Configuration.Settings.GetSetting ("XA.Foundation.Presentation.MetaComponentLayoutPath", "~ / Vues / SXA / Meta Component Layout.cshtml");
}

@les fonctions {
    chaîne statique privée BuildAttribute (attribut de chaîne, valeur de chaîne)
    {
        return (! string.IsNullOrWhiteSpace (valeur)? $ @ "{attribut} =" "{valeur}" "": string.Empty);
    }
}



@foreach (balise var dans Model.MetaTags)
{
    if (! Sitecore.Context.PageMode.IsExperienceEditorEditing && string.IsNullOrWhiteSpace (tag.Content))
    {
        continuer;
    }
    @ Html.Raw ($ @ "");
} 

Conclusion

C’est tout! Une fois que vous avez créé le rendu et que vous l'avez ajouté à une page, vous obtenez un fichier manifeste généré et mis en cache de manière dynamique qui est mis à jour lors de la publication, ainsi que des balises bonus pour améliorer les scores de vos rapports Lighthouse. Jetons un coup d'œil aux préoccupations exprimées précédemment et à la façon dont nous les avons traitées.

  • Et s'il y a plusieurs sites / locataires?
    • Le manifeste étant basé sur l'élément de paramétrage, chaque site peut avoir son propre manifeste.
  • Que se passe-t-il si la couleur de mon thème / mes icônes / la couleur de fond changent?
    • Il suffit de publier l'élément de paramètres pour vider le cache et afficher les nouvelles valeurs
  • Que se passe-t-il si le fichier manifeste est dans le contrôle de source et est remplacé lors du déploiement sur mon site?
    • Vous n'avez désormais pas besoin d'un fichier statique car il ne se trouve pas dans le contrôle de source
  • Puis-je définir ces variables à partir de Sitecore?
    • Oui! Utilisation d'un modèle partiel ajouté à l'élément de paramètres

Nous allons maintenant plonger dans la deuxième partie, qui couvre les modules SXA, les échafaudages, PowerShell et bien plus encore!






Source link