Building site map with Kentico MVC

Sitemap is a special .xml file stored in the root directory of the server. Site owners are often interested in why a Sitemap is needed and does the presence and absence of this document affect search engine promotion? Perhaps it will not affect the global results of the future promotion and popularization of the site, but in any case it will make positive changes in the appearance of the page and facilitate the work of the front-end specialists. So why is this part of the design an important element of the page?

A sitemap is a kind of directory consisting of a list of links leading to all sections and pages of the site. If some elements of development are created only to simplify the work of programmers, then such a directory is necessary for future users of the site. This means that quality work can significantly increase the constant attendance of pages. Sitemap file helps search engines more quickly and efficiently index the Internet project. This is especially important if the volume of a web resource is in the thousands or tens of thousands of pages, as in large information portals.


 

Let's look at an example of creating a sitemap on the Kentico MVC platform. First you need to install NuGet package to work with the external part of the site - the SimpleMVCSitemap plugin will generate the necessary XML files for the foundation of your site map for reliable directory operation. This solves many functional tasks for the developer, so you do not need to delve into the complex modeling processes. ASP.NET MVC uses NuGet to work install third party plugins. This tool allows you to simplify the process of their installation, updating and, if necessary, removal. Using a graphical or console interface, you only need to enter the appropriate command.

 

Let’s put together some code that will fetch, process and format the data for site map:



namespace bitsorchestra.Models
{

    public class SitemapNode
    {
        public DateTime? LastModified { get; set; }
        public string Url { get; set; }

        public SitemapNode() { }
        public SitemapNode(string u)
        {
            this.Url = u;
        }
    }

    public class Generator
    {
        public Generator() { }
        public IReadOnlyCollection GetSitemapNodes(UrlHelper urlHelper)
        {
            List nodes = new List();
            foreach (var doc in GetXMLSiteMapDocuments())
            {
                nodes.Add(new SitemapNode(doc.NodeAliasPath)
                {
                    LastModified = doc.DocumentModifiedWhen.Date
                });
            }
            return nodes;
        }

        public string GetSitemapDocument(IEnumerable sitemapNodes)
        {
            XNamespace xmlns = "http://www.sitemaps.org/schemas/sitemap/0.9";
            XElement root = new XElement(xmlns + "urlset");

            foreach (SitemapNode sitemapNode in sitemapNodes)
            {
                XElement urlElement = new XElement(
                    xmlns + "url",
            new XElement(xmlns + "loc", Uri.EscapeUriString(sitemapNode.Url)),
            sitemapNode.LastModified == null ? null : new XElement(
                xmlns + "lastmod",
                sitemapNode.LastModified.Value.ToLocalTime().ToString("yyyy-MM-dd")));
                root.Add(urlElement);
            }

            XDocument document = new XDocument(root);
            return document.ToString();
        }


        private MultiDocumentQuery GetXMLSiteMapDocuments()
        {
            //Define some sitewide parameters
            var culture = "en-us";
            var siteName = SiteContext.CurrentSiteName;

            //Define required parameters for data call
            var defaultPath = "/%";
            var defaultWhere = "(DocumentShowInSiteMap = 1) AND (NodeLinkedNodeID IS NULL) AND (ClassName NOT LIKE '%Section')";
            var defaultOrderBy = "NodeLevel, NodeOrder, DocumentName";
            var defaultColumns = "DocumentModifiedWhen, DocumentUrlPath, NodeAliasPath, NodeID, NodeParentID, NodeSiteID, NodeACLID";

            var classNameList = DocumentTypeHelper.GetClassNames(TreeProvider.ALL_CLASSNAMES);

            Func dataLoadMethod = () => DocumentHelper.GetDocuments()
                                                                        .PublishedVersion(true)
                                                                        .Types(classNameList)
                                                                        .OnSite(SiteContext.CurrentSiteID)
                                                                        .Path(defaultPath)
                                                                        .Culture(culture)
                                                                        .CombineWithDefaultCulture(true)
                                                                        .Where(defaultWhere)
                                                                        .OrderBy(defaultOrderBy)
                                                                        .Published(true)
                                                                        .Columns(defaultColumns);


            //Cache settings set to 4 hours, but with dependency's on if any page changes in the tree
            var cacheSettings = new CacheSettings(240, "data|xmlsitemap", siteName, culture)
            {
                GetCacheDependency = () =>
                {
                    // Creates caches dependencies. This example makes the cache clear data when any node is modified, deleted, or created.
                    string dependencyCacheKey = $"node|{siteName}|/|childnodes";
                    return CacheHelper.GetCacheDependency(dependencyCacheKey);
                }
            };

            return CacheHelper.Cache(dataLoadMethod, cacheSettings);
        }


    }
    public static class UrlHelperExtensions
    {
        public static string AbsoluteRouteUrl(this UrlHelper urlHelper,
            string routeName, object routeValues = null)
        {
            string scheme = urlHelper.RequestContext.HttpContext.Request.Url.Scheme;
            return urlHelper.RouteUrl(routeName, routeValues, scheme);
        }
    }

}


This code retrieves site pages, caches it and wraps into XML document. The question I faced was how do I distinguish pages those should be shown in site map from those that should not?! Earlier There was a checkbox “Show in site map”, but it is not longer available in Kentico MVC. The tricky part here is that appropriate column is present in the database and there is a way to put it onto Form tab. The question is if they are going to keep that field or will drop in the future? As that checkbox is not longer available on the user interface it might be eliminated from database at some point of time, so be aware that using it might cause some issues in the future. Anyway, my decision was to use that field to flag pages I want to appear on site map and you can see appropriate where condition in Document Query.

Once we have content for site map file ready we are in a good shape to implement controller that will return it as XML file:



public class HomeController : Controller
    {
      
        // GET: Home
        public ActionResult Index()
        {
    		//some code
            return View();
        }


        public ActionResult Sitemap()
        {
            Generator sitemapGenerator = new Generator();
            var sitemapNodes = sitemapGenerator.GetSitemapNodes(this.Url);
            string xml = sitemapGenerator.GetSitemapDocument(sitemapNodes);
            return this.Content(xml, "text/xml", Encoding.UTF8);
        }
    }

So our next step would be to configure routing for site map. You can configure routes in different ways using the Model View Controller framework. The most convenient way is to set it up within /App_Start/RouteConfig.cs file. 



routes.MapRoute(
            name: "sitemap",
            url: "sitemap.xml",
             defaults: new { controller = "Home", action = "Sitemap" }
            );

An important part of the construction is the way .NET Framework processing .xml files and configuration that will allow it to handle a request for .xml file. You’ll need to add following code into web.config file: 



<system.webServer>
    <handlers>
<add name="SitemapXml" path="sitemap.xml" verb="GET" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
    </handlers>
    <modules runAllManagedModulesForAllRequests="true">
      <remove name="CMSApplicationModule" />
      <add name="CMSApplicationModule" preCondition="managedHandler" type="CMS.Base.ApplicationModule, CMS.Base" />
    </modules>
 </system.webServer>

Summing up

Perhaps, after the work done, you will need to adjust small objects of the generated code, because the result depends on the foundation of your site. Implemented site map is completely dynamic and will show up a new page once added - I believe this is the way to go with content management system. This MVC solution can also be easily brought to the next level by any .NET developer as it requires just a little of Kentico knowledge. We wish you good luck and achieve the best results in all areas of development.