Building Sitemap as headless service for Sitecore MVP Site - Take 2

In one of my recent posts, I had built sitemap and robots.txt files for the Sitecore MVP Site but, the approach I followed wasn't feasible for a distributed topology since I used a pipeline processor and serialized the file using shared docker volume assuming that the cm and cd instances will run on the same server and my pull request was hence rejected. 

For some context, this is how sitemap usually gets generated in the conventional Sitecore ASP.NET MVC world:


Here is my old implementation that is inspired by the above Sitecore MVC implementation:



So today, I started revamping my work to fit the headless world, in a new branch wherein I expose the contents of sitemap as json and that json can be consumed by the rendering host or any other front-end! 

So, based on the above diagram,  I just cover the top part of exposing the sitemap as json in this blog article. My next post will have details about consuming the sitemap from the rendering host!

This is one-level down design overview of how calls happen within the so-called headless service:


Based on the above diagram, we need a service named SitemapBuilder.cs that does the actual job of getting data from Sitecore and this class must implement an interface. Both the files reside under Mvp.Feature.Navigation.Platform.csproj

/////////////////////////////////////////////////////////////////////////////////////////

//ISitemapBuilder.cs

using X.Web.Sitemap;


namespace Mvp.Feature.Navigation.Services

{

    public interface ISitemapBuilder

    {

        Sitemap GetSitemapData();

    }

}


Note that a reference to XSitemap nuget package is needed for this project since Sitemap is a data type that  comes OOB with that package!


Next, here is the SitemapBuilder that implements the above ISitemapBuilder interface:

//

/////////////////////////////////////////////////////////////////////////////////////////

//SitemapBuilder.cs

using Sitecore;

using Sitecore.Data.Items;

using Sitecore.Links;

using Sitecore.Links.UrlBuilders;

using System.Linq;

using X.Web.Sitemap;


namespace Mvp.Feature.Navigation.Services

{

    public class SitemapBuilder

    {

        public Sitemap GetSitemapData()

        {

            var homepage = Context.Database.GetItem(Context.Site.StartPath);

            if (homepage == null) return null;


            var sitemap = new Sitemap();


            //Create node of Homepage in Sitemap.

            var config = AppendLanguage();


            if (!ExcludeItemFromSitemap(homepage))

            {

                sitemap.Add(new Url

                {

                    ChangeFrequency = ChangeFrequency.Daily,

                    Location = GetAbsoluteLink(LinkManager.GetItemUrl(homepage, new ItemUrlBuilderOptions() { LanguageEmbedding = config == 2 ? LanguageEmbedding.Always : (config == 1 ? LanguageEmbedding.AsNeeded : LanguageEmbedding.Never) })),

                    Priority = 0.5,

                    TimeStamp = homepage.Statistics.Updated

                });

            }


            // Get all decendants of Homepage to create full Sitemap.

            var childrens = homepage.Axes.GetDescendants();

            //Remove the items whose templateid is in exclude list

            var finalcollection = childrens.Where(x => !ExcludeItemFromSitemap(x)).ToList();


            sitemap.AddRange(finalcollection.Select(childItem => new Url

            {

                Location = GetAbsoluteLink(LinkManager.GetItemUrl(childItem, new ItemUrlBuilderOptions() { LanguageEmbedding = (config == 2 ? LanguageEmbedding.Always : (config == 1 ? LanguageEmbedding.AsNeeded : LanguageEmbedding.Never)) })),

                TimeStamp = childItem.Statistics.Updated

            }));


            return sitemap;

        }


        private static string GetAbsoluteLink(string relativeUrl)

        {

            var site = Sitecore.Configuration.Factory.GetSite("mvp-site");

            return site.SiteInfo.Scheme + "://" + site.SiteInfo.HostName.Replace("-cd", string.Empty) + relativeUrl;

        }


        ///

        /// Append language or not in URL to return language specific sitemap.xml

        /// 

        private static int AppendLanguage()

        {

            return string.IsNullOrWhiteSpace(Sitecore.Configuration.Settings.GetSetting("Mvp.LanguageEmbedForSitemap")) ? 0 : System.Convert.ToInt32((Sitecore.Configuration.Settings.GetSetting("Mvp.LanguageEmbedForSitemap")));

        }


        ///

        /// This method will get a list of excluding template ids and will check if the passed item is in

        /// 

        private static bool ExcludeItemFromSitemap(Item objItem)

        {

            //Check if the item is having any version

            if (objItem.Versions.Count > 0)

            {

                var excludeItems = Sitecore.Configuration.Settings.GetSetting("Mvp.ExcludeSitecoreItemsByTemplatesInSitemap");

                if (string.IsNullOrWhiteSpace(excludeItems)) return false;


                var collection = excludeItems.Split(',').ToList();

                return collection.Contains(objItem.TemplateID.ToString());

            }

            return true;

        }

    }

}

/////////////////////////////////////////////////////////////////////////////////////////

Shared model that resides under Mvp.Feature.Navigation.Shared.csproj

/////////////////////////////////////////////////////////////////////////////////////////

//SitemapData.cs 

using X.Web.Sitemap;


namespace Mvp.Feature.Forms.Shared.Models

{

    public class SitemapData

    {

        public Sitemap Sitemap { get; set; }

    }

}

/////////////////////////////////////////////////////////////////////////////////////////

Note that a reference to XSitemap nuget package is needed since Sitemap is a data type that  comes OOB with that package!

I  also add the controller named NavigationController in the existing Mvp.Feature.Navigation project and here is the code that resides within the controller:



/////////////////////////////////////////////////////////////////////////////////////////

//NavigationController.cs

using Mvp.Feature.Forms.Shared.Models;

using Mvp.Feature.Navigation.Services;

using System.Web.Mvc;


namespace Mvp.Feature.Navigation.Controllers

{

    public class NavigationController: Controller

    {

SitemapBuilder _service;


public NavigationController()

{

_service = new SitemapBuilder();

}


[HttpGet]

public JsonResult GetSitemapData()

{

var sitemapModel = new SitemapData

{

Sitemap = _service.GetSitemapData()

};


return Json(sitemapModel, JsonRequestBehavior.AllowGet);

}

}

}


/////////////////////////////////////////////////////////////////////////////////////////

Build the Mvp.Feature.Navigation.csproj. Then, fire up postman and access the controller as follows and no wonder the sitemap json response appears


Code here: https://github.com/navancommits/MVP-Site/tree/feature/Sitemap-and-robots-txt

My favourite Sitemap xml blog by Vinayak Chauhan: https://vinayakchauhandotcom.wordpress.com/2017/04/15/sitemap-xml-with-sitecore/

Comments

Popular Posts