/DotnetSitemapGenerator

A minimalist library for creating sitemap files and also creating physical XML files

Primary LanguageC#MIT LicenseMIT

Nuget Build, Test, and Deploy

DotnetSitemapGenerator

A minimalist library for creating sitemap files inside ASP.NET Core applications and generating sitemap.xml files.

DotnetSitemapGenerator lets you create sitemap files inside action methods without any configuration. It also supports generating sitemap index files. Since you are using regular action methods you can take advantage of caching and routing available in the framework.

Advantages of DotnetSitemapGenerator

This builds upon SimpleMvcSitemap by uhaciogullari by allowing the generator to save to a physical .xml file directly with additional testing. Also this project is being actively developed and used in production applications, and upgraded to use updated dotnet features and language versions.

Table of contents

Install the NuGet package on your MVC project.

You can use SitemapProvider class to create sitemap files inside any action method. You don't even have to provide absolute URLs, DotnetSitemapGenerator can generate them from relative URLs. Here's an example:

public class SitemapController : Controller
{
    public ActionResult Index()
    {
        List<SitemapNode> nodes = new List<SitemapNode>
        {
            new SitemapNode(Url.Action("Index","Home")),
            new SitemapNode(Url.Action("About","Home")),
            //other nodes
        };

        return new SitemapProvider().CreateSitemap(new SitemapModel(nodes));
    }
}

SitemapNode class also lets you specify the optional attributes:

new SitemapNode(Url.Action("Index", "Home"))
{
    ChangeFrequency = ChangeFrequency.Weekly,
    LastModificationDate = DateTime.UtcNow.ToLocalTime(),
    Priority = 0.8M
}

To serialize a sitemap directly to a file this is done slightly differently than using the controller as you use the XMLSerializer directly.

IXmlSerializer sitemapProvider = new XmlSerializer(); // Instantiate a new serializer

// Add all your mappings to this list
List<SitemapNode> nodes = new() { 
    new($"https://yoursite.com/homepage") 
    { 
        LastModificationDate = newDateTime.ToLocalTime(), 
        ChangeFrequency = ChangeFrequency.Daily, 
        Priority = 1.0M 
    }
};

// second parameter xmlSavePath is the path to where you want to save your file, often "sitemap.xml"
// third parameter (true in this case) is used to mark whether the outputed xml file should be 
// formatted in a way that it is easily readable by human eyes which is helpful with visual validation
sitemapProvider.Serialize(new SitemapModel(nodes), xmlSavePath, true);

Sitemap files must have no more than 50,000 URLs and must be no larger then 50MB as stated in the protocol. If you think your sitemap file can exceed these limits you should create a sitemap index file. If you have a logical seperation, you can create an index manually.

List<SitemapIndexNode> sitemapIndexNodes = new List<SitemapIndexNode>
{
   new SitemapIndexNode(Url.Action("Categories","Sitemap")),
   new SitemapIndexNode(Url.Action("Products","Sitemap"))
};

return new SitemapProvider().CreateSitemapIndex(new SitemapIndexModel(sitemapIndexNodes));

If you are dealing with dynamic data and you are retrieving the data using a LINQ provider, DotnetSitemapGenerator can handle the paging for you. A regular sitemap will be created if you don't have more nodes than the sitemap size.

Generating sitemap index files

This requires a little configuration:

public class ProductSitemapIndexConfiguration : SitemapIndexConfiguration<Product>
{
    private readonly IUrlHelper urlHelper;

    public ProductSitemapIndexConfiguration(IQueryable<Product> dataSource, int? currentPage, IUrlHelper urlHelper)
        : base(dataSource,currentPage)
    {
        this.urlHelper = urlHelper;
    }

    public override SitemapIndexNode CreateSitemapIndexNode(int currentPage)
    {
        return new SitemapIndexNode(urlHelper.RouteUrl("ProductSitemap", new { currentPage }));
    }

    public override SitemapNode CreateNode(Product source)
    {
        return new SitemapNode(urlHelper.RouteUrl("Product", new { id = source.Id }));
    }
}

Then you can create the sitemap file or the index file within a single action method.

public ActionResult Products(int? currentPage)
{
    var dataSource = products.Where(item => item.Status == ProductStatus.Active);
    var productSitemapIndexConfiguration = new ProductSitemapIndexConfiguration(dataSource, currentPage, Url);
    return new DynamicSitemapIndexProvider().CreateSitemapIndex(new SitemapProvider(), productSitemapIndexConfiguration);
}

You should convert your DateTime values to local time. Universal time format generated by .NET is not accepted by Google. You can use .ToLocalTime() method to do the conversion.

You can use Google's sitemap extensions to provide detailed information about specific content types like images, videos, news or alternate language pages. You can still use relative URLs for any of the additional URLs.

using DotnetSitemapGenerator.Images;

new SitemapNode(Url.Action("Display", "Product"))
{
    Images = new List<SitemapImage>
    {
        new SitemapImage(Url.Action("Image","Product", new {id = 1})),
        new SitemapImage(Url.Action("Image","Product", new {id = 2}))
    }
}

By version 4, multiple videos are supported. Start using Videos property if you are upgrading from v3 to v4.

using DotnetSitemapGenerator.Videos;

new SitemapNode("http://www.example.com/videos/some_video_landing_page.html")
{
    Videos = new List<SitemapVideo>
    { 
        new SitemapVideo(title: "Grilling steaks for summer",
                         description: "Alkis shows you how to get perfectly done steaks every time",
                         thumbnailUrl: "http://www.example.com/thumbs/123.jpg", 
                         contentUrl: "http://www.example.com/video123.flv")
    }
}
using DotnetSitemapGenerator.News;

new SitemapNode("http://www.example.org/business/article55.html")
{
    News = new SitemapNews(newsPublication: new NewsPublication(name: "The Example Times", language: "en"),
                           publicationDate: new DateTime(2014, 11, 5, 0, 0, 0, DateTimeKind.Utc),
                           title: "Companies A, B in Merger Talks")
}
using DotnetSitemapGenerator.Translations;

new SitemapNode("abc")
{
    Translations = new List<SitemapPageTranslation>
    {
        new SitemapPageTranslation("http://www.example.com/deutsch/", "de"),
		new SitemapPageTranslation("http://www.example.com/english/", "en")
    }
}

DotnetSitemapGenerator supports XSL style sheets by version 3. Keep in mind that XML stylesheets are subjected to the same origin checks.

using DotnetSitemapGenerator.StyleSheets;

new SitemapModel(new List<SitemapNode> { new SitemapNode("abc") })
{
    StyleSheets = new List<XmlStyleSheet>
    {
        new XmlStyleSheet("/sitemap.xsl")
    }
};

You can see how you can utilize multiple XSL style sheets in this tutorial.

DotnetSitemapGenerator can generate absolute URLs from the relative URLs using the HTTP request context. If you want to customize this behaviour, you can implement IBaseUrlProvider interface and pass it to the SitemapProvider class.

public class BaseUrlProvider : IBaseUrlProvider
{
    public Uri BaseUrl => new Uri("http://example.com");
}

var sitemapProvider = new SitemapProvider(new BaseUrlProvider());

SitemapProvider class implements the ISitemapProvider interface which can be injected to your controllers and be replaced with test doubles. All methods are thread safe so they can be used with singleton life cycle.

public class SitemapController : Controller
{
    private readonly ISitemapProvider _sitemapProvider;

    public SitemapController(ISitemapProvider sitemapProvider)
    {
        _sitemapProvider = sitemapProvider;
    }
	
    //action methods
}

DotnetSitemapGenerator is licensed under MIT License. Refer to license file for more information.