Building site map with Kentico MVC

SHARE

 

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.


 

Author

Check other articles

Bitsorchestra
5 5

What our clients say

Bits Orchestra team are outstanding developers‚Äč. They listen carefully to our business needs and easily turns our business objectives into a well thought out and executed development effort. Roman is very bright and definitely the most capable developer that has worked on our site. He is not only a Kentico expert but has successfully tackled other complicated development assignments demonstrating expertise in both front and backend development. Roman takes initiative to suggest enhancements that make site maintenance easier while improving the customer experience. The team is very responsive to our work requests and has great follow up. They have also worked very business partners and this has reflected positively on our company. Roman is a true partner for us and a tremendous asset to our organization. We will continue to work with them and would highly recommend Roman and his team for your development needs. He and his team will exceed your expectations!
 Alan Lehmann
Alan Lehmann
President at In energy sector

What our clients say

The Bits Orchestra team does excellent work. They are always available and I appreciate our frequent calls and screen-shares together. Their dedication to the projects and knowledge of Kentico is outstanding. They truly care about the quality of their work, and became a part of our team easily!
Shena Lowe
Shena Lowe
Managing Partner at Consensus Interactive

What our clients say

We hired Roman for a Kentico analysis project and have been very satisfied. He is very skilled and professional. We are looking to hire him and his team again on future projects.
Sylvain Audet
Sylvain Audet
CEO at MyDevPartner.com

What our clients say

Roman and team have taken over an existing Kentico EMS site for a large US Oil Company. So far, they have handled every single request that we have thrown at them and these were diverse, challenging, often bespoke, usually urgent and almost daily, over the last 11 months. Their work is of an extremely high quality, they are capable, quick and we have great confidence in the support that we are getting.
Jon Hollis
Jon Hollis
Head of Web Development at confidential

What our clients say

Bits Orchestra team was very helpful, they had a good understanding of the brief and deep knowledge of the system. They were always keen to provide advice and recommendations that benefit the project substantially.
Ramon Lapenta
Ramon Lapenta
Senior Front End Developer at Cyber-Duck Ltd