How to create a simple sitemap module for orchard

Tagged: orchard sitemap module create cms sitemap.xml dynamic

Weight: loads

Creating a sitemap module for orchard

One thing that comes up quite regularly on stack overflow and forums are questions related to sitemaps (or lack of) in Orchard. This quick post will show you how to create a module that will provide the functionality needed to deliver a sitemap that has been automatically generated from orchard content. The solution here is very much the minimum to deliver a sitemap for the website. 

Set up module using code generation

To start off creating a module for orchard the easiest way to do this is to use the codegen tool from the orchard command line using the following two commands, these will scaffold a new visual studio project with all the files needed to start creating the module.

orchard> feature enable Orchard.CodeGeneration

orchard> codegen module AdrianBooth.Orchard.Sitemap

To serve up the sitemap I added a controller and route to access the sitemap to the new module.

    public class SitemapController : Controller
    {
        private readonly ISitemapService _sitemapService;

        public SitemapController(ISitemapService sitemapService)
        {
            _sitemapService = sitemapService;
        }
        public ContentResult Index()
        {
            var sitemapContent = _sitemapService.GetSitemap();

            return new ContentResult
            {
                Content = sitemapContent,
                ContentEncoding = Encoding.UTF8,
                ContentType = "application/xml"
            };
        }
    }
    public class Routes : IRouteProvider
    {
        public IEnumerable<RouteDescriptor> GetRoutes()
        {
            yield return new RouteDescriptor
            {
                Priority = 1,
                Route = new Route(
                     "Sitemap.xml",
                     new RouteValueDictionary {
                        {"area", "AdrianBooth.Orchard.Sitemap"},
                        {"controller", "Sitemap"},
                        {"action", "Index"}
                     },
                     new RouteValueDictionary(),
                     new RouteValueDictionary {
                        {"area", "AdrianBooth.Orchard.Sitemap"}
                     },
                     new MvcRouteHandler())
            };
        }

        public void GetRoutes(ICollection<RouteDescriptor> routes)
        {
            foreach (var routeDescriptor in GetRoutes())
                routes.Add(routeDescriptor);
        }
    }

Dynamic Sitemap code

To get the list of addresses that I wanted for the sitemap I used the content manager to query get a list of content that is published and has a display alias. For simplicity (and lazyness) I decided to just stick the result of the query into a string. To mitigate potential performance issues I also cached the generated sitemap for an arbitrary length of time.

    public class SitemapService : ISitemapService
    {
        private readonly IContentManager _contentManager;
        private readonly ICacheManager _cacheManager;
        private readonly IClock _clock;
private readonly IWorkContextAccessor _workContextAccessor;
public SitemapService(IContentManager contentManager, ICacheManager cacheManager, IClock clock, IWorkContextAccessor workContextAccessor) { _contentManager = contentManager; _cacheManager = cacheManager; _clock = clock;
   _workContextAccessor = workContextAccessor; } public string GetSitemap() { return _cacheManager.Get("simple-sitemap", true, ctx => { ctx.Monitor(_clock.When(TimeSpan.FromMinutes(60 * 24))); return SitemapString(); }); } private string SitemapString() { var contentUrls = _contentManager.Query<AutoroutePart, AutoroutePartRecord>() .Where(part => part.DisplayAlias != null).List().Select(e => e.DisplayAlias);

var baseUrl = _workContextAccessor.GetContext().CurrentSite.BaseUrl; var stringBuilder = new StringBuilder(); stringBuilder.AppendLine("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); stringBuilder.AppendLine("<urlset xmlns=\"http://www.google.com/schemas/sitemap/0.90\">"); foreach (var url in contentUrls) { stringBuilder.AppendLine($"<url><loc>{baseUrl}/{url}</loc></url>"); } stringBuilder.AppendLine("</urlset>"); return stringBuilder.ToString(); } }

I've added the code for this to github and will be extending this in another few posts to show creating new parts and improving caching use. For now though this does the job and probably won't break your website if you use it :-)