65.9K
CodeProject is changing. Read more.
Home

XML Sitemap in ASP.NET Application

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0 vote)

May 11, 2014

CPOL

1 min read

viewsIcon

14382

XML sitemap in ASP.NET application

What is an XML Sitemap?

Here is a good set of information where you can find what it is and why it is used. Typical structure of an XML sitemap is like below:
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>http://www.domain.com/store/great-coffee-shop</loc>
    <lastmod>2012-3-3</lastmod>
    <changefreq>Weekly</changefreq>
    <priority>1</priority>
  </url>
</urlset>
After creating an XML sitemap, it needs to be submitted to search engine and maintaining an accurate Sitemap is important to proper indexing of a website pages. Creating an XML in C# is no difficult job, but one may find it hard to maintain the source code in future releases as it will grow as the website progresses. So I came up with few simple rules and patterns I can use to maintain the future maintenance.

The Diagram

sitemap-diagram

As the diagram implies, Sitemap classes will implement the interface ISitemap and inherit an abstract class SitemapBase. When I implement, I can set the ExecutionOrder which will take an integer value and Sitemap classes will execute their Build method in that order. The config class has a static method BuildSitemapXml which will start the process. You can generate the XML on Application_Start or you can make a separate controller (for MVC app) for that.

The Code

public class SitemapConfig : SitemapBase
{
    public static void BuildSitemapXml()
    {
        var ctx = HttpContext.Current;
            var sitemap = new SitemapConfig();
            var sitemapThread = new Thread(() =>
            {
                HttpContext.Current = ctx;
                sitemap.Start();
            });
            sitemapThread.Start();
    }

    public void Start()
    {
        string encoding = Response.ContentEncoding.WebName;

        var sitemap = new XDocument(new XDeclaration("1.0", encoding, ""),
            new XElement(Ns + "urlset",
                new XAttribute("xmlns", Ns.NamespaceName),
                new XAttribute(XNamespace.Xmlns + "image", Imagens),
                new XAttribute(XNamespace.Xmlns + "video", Videons),
                base.GetSitemapElements())
            );
        sitemap.Save(HttpContext.Current.Server.MapPath(@"~/sitemap.xml"));
    }
}

… and the ISitemap.cs and SitemapBase.cs.

public interface ISitemap
{
    int ExecutionOrder { get; }
    string UrlPrefix { get; }
    List<XElement> Build();
}

public abstract class SitemapBase
{
    protected XNamespace Ns = "http://www.sitemaps.org/schemas/sitemap/0.9";
    protected XNamespace Imagens = "http://www.google.com/schemas/sitemap-image/1.1";
    protected XNamespace Videons = "http://www.google.com/schemas/sitemap-video/1.1";
    protected HttpContext CurrentContext { get { return HttpContext.Current; } }
    protected HttpRequest Request { get { return HttpContext.Current.Request; } }
    protected HttpResponse Response { get { return HttpContext.Current.Response; } }

    protected XElement GetElement(string url, DateTime lastModified, ChangeFrequency changeFrequency, int priority)
    {
        var element = new XElement(Ns + "url", new XElement(Ns + "loc", url));
        element.Add(new XElement(Ns + "lastmod", lastModified.ToString("MM-dd-yyyy")));
        element.Add(new XElement(Ns + "changefreq", changeFrequency));
        element.Add(new XElement(Ns + "priority", priority.ToString(CultureInfo.InvariantCulture)));
        return element;
    }

    protected XElement GetImageElement(string content, string caption)
    {
        var imageElement = new XElement(Imagens + "image");
        imageElement.Add(new XElement(Imagens + "loc", content));
        imageElement.Add(new XElement(Imagens + "caption", caption));
        return imageElement;
    }

    protected XElement GetVideoElement(string thumbnailUrl, string title, string description, string videoUrl)
    {
        var videoElement = new XElement(Videons + "video");
        videoElement.Add(new XElement(Videons + "thumbnail_loc", thumbnailUrl));
        videoElement.Add(new XElement(Videons + "title", title));
        videoElement.Add(new XElement(Videons + "description", description));
        videoElement.Add(new XElement(Videons + "content", videoUrl));
        return videoElement;
    }

    public enum ChangeFrequency
    {
        Always,
        Hourly,
        Daily,
        Weekly,
        Monthly,
        Yearly,
        Never
    }

    /// <summary>
    /// Returns all types in the current AppDomain implementing the interface or inheriting the type. 
    /// </summary>
    public static IEnumerable<Type> TypesImplementingInterface(Type desiredType)
    {
        return System.Reflection.Assembly.GetCallingAssembly().GetTypes()
               .Where(type => (desiredType.IsAssignableFrom(type) && 
               !type.IsAbstract && !type.IsGenericTypeDefinition && !type.IsInterface));
    }

    public List<XElement> GetSitemapElements()
    {
        var sitemapElements = new List<XElement>();
        IEnumerable<Type> sitemaps = TypesImplementingInterface(typeof(ISitemap));
        return sitemaps.Select(sitemap => (ISitemap)Activator.CreateInstance(sitemap))
            .OrderBy(instance => instance.ExecutionOrder)
            .Select(instance => instance.Build())
            .Where(elements => elements != null)
            .Aggregate(sitemapElements, (current, elements) => current.Union(elements).ToList());
    }
}

… and here is a demo implementation. The Build() function returns the store information along with url in a XElement List object. Chances are you will have more of these types of implementations for your website.

public class SitemapStores : SitemapBase, ISitemap
{
    public int ExecutionOrder { get { return 1; } }
    public string UrlPrefix { get { return "Store/"; } }
    
    public List<XElement> Build()
    {
        var stores = Db.GetStores();
        var storeElements = new List<XElement>();

        stores.ForEach(store =>{
            string storeUrl = string.Format("{0}/{1}{2}", WebHelper.SiteDomain, UrlPrefix, store.StoreUrl);
            var element = base.GetElement(storeUrl, store.UpdateDate, ChangeFrequency.Weekly, 1);
            storeElements.Add(element);
        });

        return storeElements;
    }
}

From all the implementations, the Build() function will be invoked by GetSitemapElements() from SitemapBase. This method will look for all the ISitemap implantation in its assembly and execute its Build() function.