Dynamic sitemap.xml generator
14 May 2016
Sitefinity provides a very good platform for Content Management System and also exposes many APIs through which developers could access the data, customize and render it according to their requirement.
If you have a smaller website(pages less than 1000), then you can use the following code to generate the sitemap XML and cache it for particular duration.
This code utilizes Sitefinity Page API (I know SitemapBase is cached and better with performance), but this page api gives you better control the pages from the multiple languages with the permission checking included. This code also takes care of situation where sites are hosted using Sitefinity MSM feature and generates the pages based on current MSM site.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
protected void Application_BeginRequest(object sender, EventArgs e) | |
{ | |
string strUrl = Request.Path.ToLowerInvariant(); | |
//Code block to redirect the generic request to the correct sitemap index and sitemap XML | |
if (new Regex(@"^/sitemap\.xml", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant).IsMatch(Request.Path)) | |
HttpContext.Current.RewritePath("/sitemap.ashx"); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<%@ WebHandler Language="C#" Class="Sitemap" %> | |
using System; | |
using System.Text; | |
using System.Web; | |
using System.Xml; | |
using System.Linq; | |
using Telerik.Sitefinity; | |
using Telerik.Sitefinity.Modules.Pages; | |
using Telerik.Sitefinity.Multisite; | |
using Telerik.Sitefinity.Services; | |
using System.Globalization; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Web.Caching; | |
public class AltInfo | |
{ | |
public string LangName { get; set; } | |
public string Link { get; set; } | |
} | |
public class Sitemap : IHttpHandler | |
{ | |
// global properties | |
protected string host; | |
private const string sitemapKey = "customsitemap"; | |
/// <summary> | |
/// Enables processing of HTTP Web requests by a custom HttpHandler that implements the <see cref="T:System.Web.IHttpHandler" /> interface. | |
/// </summary> | |
/// <param name="context">An <see cref="T:System.Web.HttpContext" /> object that provides references to the intrinsic server objects (for example, Request, Response, Session, and Server) used to service HTTP requests.</param> | |
public void ProcessRequest(HttpContext context) | |
{ | |
try | |
{ | |
// prepare response type | |
var response = context.Response; | |
response.ContentType = "text/xml"; | |
var multisiteContext = SystemManager.CurrentContext as MultisiteContext; | |
string currrentSitemapKey = sitemapKey + multisiteContext.CurrentSite.Id; | |
string sSitemap = string.Empty; | |
if (HttpRuntime.Cache[currrentSitemapKey] == null) | |
{ | |
lock (currrentSitemapKey) | |
{ | |
if (HttpRuntime.Cache[currrentSitemapKey] == null) | |
{ | |
using (MemoryStream memoryStream = new MemoryStream()) | |
{ | |
using (var writer = new XmlTextWriter(memoryStream, Encoding.UTF8){ Formatting = Formatting.Indented })// begin xml response | |
{ | |
writer.WriteStartDocument(); | |
writer.WriteStartElement("urlset"); | |
writer.WriteAttributeString("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance"); | |
writer.WriteAttributeString("xsi:schemaLocation", "http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"); | |
writer.WriteAttributeString("xmlns", "http://www.sitemaps.org/schemas/sitemap/0.9"); | |
writer.WriteAttributeString("xmlns:xhtml", "http://www.w3.org/1999/xhtml"); | |
var vars = HttpContext.Current.Request.ServerVariables; | |
string port; | |
// parse content | |
using (var api = App.WorkWith()) | |
{ | |
string defaultCultureName = multisiteContext.CurrentSite.DefaultCulture; | |
CultureInfo defCulture = new CultureInfo(defaultCultureName); | |
var allCultures = multisiteContext.CurrentSite.PublicContentCultures.Where(c => c.Name != defaultCultureName).ToList(); | |
allCultures.Insert(0, new CultureInfo(defaultCultureName)); | |
var rId = multisiteContext.CurrentSite.SiteMapRootNodeId; | |
// append pages | |
var pages = api.Pages().ThatArePublished().Where(p => p.ShowInNavigation == true && p.Crawlable && !p.IsBackend && p.NodeType == Telerik.Sitefinity.Pages.Model.NodeType.Standard && p.RootNodeId == rId).Get(); | |
foreach (var page in pages) | |
{ | |
string defaultUrl = null; | |
DateTime lastMod = DateTime.Now; | |
List<AltInfo> altInfo = new List<AltInfo>(); | |
{ | |
var pData = page.GetPageData(defCulture); | |
if (pData != null) | |
{ | |
// build host | |
var protocol = pData.NavigationNode.RequireSsl ? "https://" : "http://"; | |
// append port | |
port = pData.NavigationNode.RequireSsl ? "443" : vars["SERVER_PORT"]; | |
if (port == "80" || port == "443") | |
port = string.Empty; | |
else | |
port = string.Concat(":", port); | |
defaultUrl = string.Concat(protocol, vars["SERVER_NAME"], port, VirtualPathUtility.ToAbsolute(page.GetFullUrl())); | |
lastMod = pData.LastModified; | |
} | |
} | |
foreach (var culture in allCultures) | |
{ | |
if (page.AvailableCultures.Contains(culture)) | |
{ | |
var pData = page.GetPageData(culture); | |
if (pData != null) | |
{ | |
// build host | |
var protocol = pData.NavigationNode.RequireSsl ? "https://" : "http://"; | |
// append port | |
port = pData.NavigationNode.RequireSsl ? "443" : vars["SERVER_PORT"]; | |
if (port == "80" || port == "443") | |
port = string.Empty; | |
else | |
port = string.Concat(":", port); | |
var url = string.Concat(protocol, vars["SERVER_NAME"], port, VirtualPathUtility.ToAbsolute(page.GetFullUrl(culture, false))); | |
altInfo.Add(new AltInfo() { LangName = culture.Name, Link = url }); | |
} | |
} | |
} | |
// append page to sitemap | |
this.AppendUrl(writer, defaultUrl, lastMod, altInfo); | |
} | |
} | |
writer.WriteEndElement(); | |
writer.WriteEndDocument(); | |
writer.Flush(); | |
} | |
sSitemap = Encoding.UTF8.GetString(memoryStream.ToArray()); | |
HttpRuntime.Cache.Add(currrentSitemapKey, sSitemap, null, DateTime.Now.AddHours(12), Cache.NoSlidingExpiration, CacheItemPriority.Normal, null); | |
} | |
} | |
else | |
sSitemap = HttpRuntime.Cache[currrentSitemapKey] + ""; | |
} | |
} | |
else | |
sSitemap = HttpRuntime.Cache[currrentSitemapKey] + ""; | |
response.Write(sSitemap); | |
response.Flush(); | |
} | |
catch (Exception ex) | |
{ | |
Elmah.ErrorSignal.FromCurrentContext().Raise(ex); | |
} | |
} | |
private void AppendUrl(XmlTextWriter writer, string fullUrl, DateTime lastModified, List<AltInfo> altUrls) | |
{ | |
// calculate change frequency | |
string changeFreq = "monthly"; | |
var changeInterval = (DateTime.Now - lastModified).Days; | |
if (changeInterval <= 1) | |
changeFreq = "daily"; | |
else if (changeInterval <= 7 & changeInterval > 1) | |
changeFreq = "daily"; | |
else if (changeInterval <= 30 & changeInterval > 7) | |
changeFreq = "weekly"; | |
else if (changeInterval <= 30 & changeInterval > 365) | |
changeFreq = "weekly"; | |
// append to sitemap | |
writer.WriteStartElement("url"); | |
writer.WriteElementString("loc", fullUrl); | |
writer.WriteElementString("lastmod", lastModified.ToString("yyyy-MM-ddThh:mm:sszzzz")); | |
writer.WriteElementString("changefreq", changeFreq); | |
writer.WriteElementString("priority", "1"); | |
foreach (var xhtml in altUrls) | |
{ | |
writer.WriteStartElement("xhtml:link"); | |
writer.WriteAttributeString("rel", "alternate"); | |
writer.WriteAttributeString("hreflang", xhtml.LangName); | |
writer.WriteAttributeString("ref", xhtml.Link); | |
writer.WriteEndElement(); | |
} | |
writer.WriteEndElement(); | |
} | |
public bool IsReusable | |
{ | |
get { return true; } | |
} | |
} |