Fluent Sitemap Generator

A sitemap allows search engine crawlers to find pages on your website. The sitemap references pages on your site and when they were last updated. The file is written using XML.

Example Sitemap.xml

Below is an example of a sitemap. Further down you will see how this is created

<urlset xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>https://www.lotushope.co.uk/index.html</loc>
    <lastmod>2020-05-10</lastmod>
    <priority>0.5</priority>
  </url>
  <url>
    <loc>https://www.lotushope.co.uk/about-me.html</loc>
    <lastmod>2020-05-10</lastmod>
    <priority>0.5</priority>
  </url>
  <url>
    <loc>https://www.lotushope.co.uk/coding-articles-all.html</loc>
    <lastmod>2020-05-10</lastmod>
    <priority>0.5</priority>
  </url>
  <url>
    <loc>https://www.lotushope.co.uk</loc>
    <xhtml:link hreflang="de" href="https://www.lotushope.co.de" />
    <xhtml:link hreflang="fr" href="https://www.lotushope.co.fr" />
    <priority>0.0</priority>
  </url>
</urlset>

Fluent Sitemap Generator

The class (FluentSitemapGenerator) has been written in the FluentSyntax. Each method with the exception of Create() will return the class. Internally it will build a list of links and when the method Create() is called a serialized string is created.

Methods()

  • AddSiteItem(Url, DateTime, Priority) - Call for each url you wish to add to the sitemap
  • AddSiteItemWithAlternates(Url, List(Alternate Urls))
  • Create() - returns XML as a string

Below is the code to run using LINQPad

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml;
using System.Xml.Serialization;

void Main()
{
	var outputPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop),"Sitemap.xml");

	// Use the Fluent Builder to Create the XML Sitemap
	var output = new FluentSitemapGenerator()
		.AddSiteItem("https://www.lotushope.co.uk/index.html", DateTime.Now, 0.5D)
		.AddSiteItem("https://www.lotushope.co.uk/about-me.html", DateTime.Now, 0.5D)
		.AddSiteItem("https://www.lotushope.co.uk/coding-articles-all.html", DateTime.Now, 0.5D)
		.AddSiteItemWithAlternates("https://www.lotushope.co.uk",
                                new List<(string Iso, string Link)>
                                {
                                ("de", "https://www.lotushope.co.de"),
                                ("fr", "https://www.lotushope.co.fr") })
		.Create(true);

	if (File.Exists(outputPath))
		File.Delete(outputPath);
	using (var sw = new StreamWriter(outputPath))
	{
		sw.Write(output);
		sw.Flush();
		sw.Close();
	}
}


#region FluentSiteMapGenerator
public class FluentSitemapGenerator
{
	private SitemapRoot _rootElement;

	public string Create(bool indent = false) => Serialize(_rootElement, indent);
	public FluentSitemapGenerator() => _rootElement = new SitemapRoot();

	public FluentSitemapGenerator AddSiteItem(string url, DateTime? lastModified = null, double priority = 0D)
	{
		if (string.IsNullOrWhiteSpace(url))
			throw new ArgumentException(nameof(url));

		_rootElement.SitemapElements.Add(new UrlElement
			{ 	Location = url,
				LastModified = lastModified?.ToString("yyyy-MM-dd"),
				Priority = priority.ToString("0.0")
			});

		return this;
	}
	public FluentSitemapGenerator AddSiteItemWithAlternates
        (string url, List<(string Iso, string Link)> alternates, DateTime? lastModified = null, double priority = 0D)
	{
		if (string.IsNullOrWhiteSpace(url))
			throw new ArgumentException(nameof(url));

		var newElement = new UrlElement
		{
			Location = url,
			LastModified = lastModified?.ToString("yyyy-MM-dd"),
			Priority = priority.ToString("0.0")
		};

		foreach (var alt in alternates)
			newElement.AlternateLink.Add(new AlternateLink { IsoLanguageCode = alt.Iso, Link = alt.Link});

		_rootElement.SitemapElements.Add(newElement);
		return this;
	}
	public FluentSitemapGenerator RemoveSiteItem(string url)
	{
		var found = _rootElement.SitemapElements.Where(c=> c.Location == url).SingleOrDefault();
		if (found != null)
			_rootElement.SitemapElements.Remove(found);
		return this;
	}
	public FluentSitemapGenerator ResetItems()
	{
		_rootElement.SitemapElements.Clear();
		return this;
	}

	private string Serialize<T>(T t, bool indent)
	{
		var ns = new XmlSerializerNamespaces();
		ns.Add("xhtml", "http://www.w3.org/1999/xhtml");

		var settings = new XmlWriterSettings { Indent = indent, OmitXmlDeclaration = true };
		var xml = "";

		var xmlSerializer = new XmlSerializer(typeof(T));

		using (var sw = new StringWriter())
		using (XmlWriter writer = XmlWriter.Create(sw, settings))
		{
			xmlSerializer.Serialize(writer, t, ns);
			xml = sw.ToString();
		}
		return xml.Replace("xhtmllink","xhtml:link");
	}
}
#endregion

#region Sitemap Serializable Classes
[XmlRoot(Namespace = "http://www.sitemaps.org/schemas/sitemap/0.9", ElementName = "urlset", DataType = "string", IsNullable = true)]
public class SitemapRoot
{
	[XmlAttribute(AttributeName = "xmlns", Type = typeof(SitemapRoot))]
	public string secondaryNamespace => "http://www.sitemaps.org/schemas/sitemap/0.9";

	[XmlElement("url")]
	public List<UrlElement> SitemapElements { get; set; } = new List<UrlElement>();
}
public class UrlElement
{
	[XmlElement("loc")]
	public string Location { get; set; }

	[XmlElement(ElementName = "xhtmllink" )]
	public List<AlternateLink> AlternateLink {get;set;} = new List<AlternateLink>();

	[XmlElement("lastmod")]
	public string LastModified { get; set; }

	[XmlElement("priority")]
	public string Priority { get; set; }
}
public class AlternateLink
{
	[XmlAttribute("rel")]
	public string Alternate => "Alternate";

	[XmlAttribute("hreflang")]
	public string IsoLanguageCode { get; set; }

	[XmlAttribute("href")]
	public string Link {get;set;}
}
#endregion