Integrated Solutions, Inc.

How to use an ASP.NET SiteMapProvider to produce a XML SiteMap for Google, Yahoo, Bing, etc

More often I’m being asked to provide “SiteMap.xml”s for sites we work on, and up until recently we had been hand rolling the xml and placing them in static documents on our sites. This is fine but seemed a little silly because ASP.NET has a built in SiteMap mechanism. So we decided to do something about using it for auto generating the SiteMap.xml.

My example is using Maarten Balliauw’s awesome MvcSiteMap available at http://mvcsitemap.codeplex.com/ but the conversion code works perfectly fine with the built in SiteMapProvider in ASP.NET.

Using the following class we can generate the XML object representing the SiteMap.

  public class SiteMapXMLNode
  {
    public enum enChangeFrequency
    {
      NoneGiven,
      Always,
      Hourly,
      Daily,
      Weekly,
      Monthly,
      Yearly,
      Never
    }

    [System.Xml.Serialization.XmlElement(ElementName = "loc")]
    public string URL;

    #region Last Modified
    [System.Xml.Serialization.XmlIgnore]
    public DateTime LastModified;
    [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
    [System.Xml.Serialization.XmlElement(ElementName = "lastmod")]
    public string __LastModified
    {
      get { return (LastModified == DateTime.MinValue ? null : string.Format("{0:yyyy-MM-dd}", LastModified)); }
      set { LastModified = DateTime.MinValue; } //Note: This property will never be deserialized
    }
    #endregion

    #region Change Frequency
    [System.Xml.Serialization.XmlIgnore]
    public enChangeFrequency ChangeFrequency;
    [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
    [System.Xml.Serialization.XmlElement(ElementName = "changefreq")]
    public string __ChangeFrequency
    {
      get { return (ChangeFrequency == enChangeFrequency.NoneGiven ? null : ChangeFrequency.ToString().ToLower()); }
      set { ChangeFrequency = enChangeFrequency.NoneGiven; } //Note: This property will never be deserialized
    }
    #endregion

    #region Priority
    [System.Xml.Serialization.XmlIgnore]
    public decimal? Priority;
    [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
    [System.Xml.Serialization.XmlElement(ElementName = "priority")]
    public string __Priority
    {
      get { return (!Priority.HasValue ? null : string.Format("{0:0.0}", Priority)); }
      set { Priority = null; } //Note: This property will never be deserialized
    }
    #endregion

    public SiteMapXMLNode()
    {
      URL = null;
      LastModified = DateTime.MinValue;
      ChangeFrequency = enChangeFrequency.NoneGiven;
      Priority = null;
    }

    public SiteMapXMLNode(System.Web.SiteMapNode node)
      : this()
    {
      UriBuilder uriBuilder = new UriBuilder(System.Web.HttpContext.Current.Request.Url);
      uriBuilder.Path = node.Url;
      URL = uriBuilder.ToString();
    }
  }

  [System.Xml.Serialization.XmlRootAttribute(Namespace = "http://www.sitemaps.org/schemas/sitemap/0.9", IsNullable = false, ElementName = "urlset")]
  public class SiteMapXML
  {
    private List<SiteMapXMLNode> Nodes;

    #region "urls"
    [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
    [System.Xml.Serialization.XmlElementAttribute("url")]
    public SiteMapXMLNode[] __Nodes
    {
      get
      {
        return Nodes.ToArray();
      }
      set
      {
        Nodes.Clear();
        Nodes.AddRange(value);
      }
    }
    #endregion

    public SiteMapXML()
    {
      Nodes = new List<SiteMapXMLNode>();
    }

    public SiteMapXML(System.Web.SiteMapNode rootNode)
      : this()
    {
      Add(rootNode);
    }

    public void Add(System.Web.SiteMapNode rootNode)
    {
      Nodes.Add(new SiteMapXMLNode(rootNode));

      if (rootNode.HasChildNodes)
      {
        foreach (System.Web.SiteMapNode node in rootNode.ChildNodes)
        {
          if (node.IsAccessibleToUser(System.Web.HttpContext.Current)) Add(node);
        }
      }
    }
  }

Then we use this action for MVC

    public ActionResult SiteMapXML()
    {
      ActionResult result = null;

      SiteMapXML siteMapXML = new SiteMapXML(System.Web.SiteMap.RootNode);

      using (System.IO.MemoryStream oStream = new System.IO.MemoryStream())
      {
        System.Xml.Serialization.XmlSerializer xmlSerializer = new System.Xml.Serialization.XmlSerializer(typeof(SiteMapXML));
        xmlSerializer.Serialize(oStream, siteMapXML);
        oStream.Flush();
        xmlSerializer = null;

        System.Text.UTF8Encoding UTF8 = new System.Text.UTF8Encoding(false);

        result = new ContentResult() { ContentType = "text/xml", Content = UTF8.GetString(oStream.ToArray()), ContentEncoding = UTF8 };
      }

      return result;
    }

Or this code behind for ASP.NET

    protected void Page_Load(object sender, EventArgs e)
    {
      Response.Clear();

      Response.ContentType = "text/xml";

      SiteMapXML siteMapXML = new SiteMapXML(System.Web.SiteMap.RootNode);

      System.Xml.Serialization.XmlSerializer xmlSerializer = new System.Xml.Serialization.XmlSerializer(typeof(SiteMapXML));
      xmlSerializer.Serialize(Response.OutputStream, siteMapXML);
      xmlSerializer = null;

      Response.Flush();

      Response.End();
    }

It’s really that simple

 

ToDo: add Attributes and elements to the SiteMap definition so we can take advantage of the additional Attributes in the SiteMap XSD (“lastmod”, “changefreq” and “priority”)

Published 10/16/2009 by Ron Muth