Sitemap Action Result for ASP.NET MVC
Join the DZone community and get the full member experience.
Join For FreeOne common feature of any modern web application to improve its SEO and traffic is the use of sitemap as a standard XML derivation. I’ve been using such sitemaps on my blog for a very long time. I can remember the days when sitemap was an extension to Community Server written by the community, and the days when it became a built-in feature. I also had written a plug-in for Graffiti CMS to add sitemap to my blog.
However, for Behistun (my new ASP.NET MVC powered blog engine) I had to implement the sitemap with my own code. In ASP.NET WebForms the most common way to implement a sitemap is through an HttpHandler that handles the request to sitemap URL and generates the valid XML data of the sitemap in response.
For ASP.NET MVC and Behistun I used a different approach that replaces HttpModule and HttpHandler in many cases: Action Result. I implemented a custom action result that generates the sitemap as the output.
Looking around, I didn’t see such an implementation shared on the community even though it’s comparatively easy to implement it in ASP.NET MVC. So here I share my approach shortly hoping it helps some developers in the future.
Action result is a very nice and helpful part of ASP.NET MVC that can act as a extensibility point as well. There are various derivations of ActionResult base class available in ASP.NET MVC, and there are some interesting extensions such as the one written for RSS feed generation.
For my sitemap implementation in ASP.NET MVC I created my own SitemapActionResult class by inheriting from ActionResult base class, and used it to generate the sitemap based on the blog posts list.
First, assume that I have a Post class that represents a post with a very simple structure that I’ll use later in my example.
using System;
namespace SitemapActionResultSample.Models
{
public class Post
{
public int ID { get; set; }
public string Title { get; set; }
public string Body { get; set; }
public DateTime DateAdded { get; set; }
public DateTime UpdatedOn { get; set; }
public string Slug { get; set; }
}
}
Now I implement the custom action result for sitemap generation as follows.
using System;
using System.Collections.Generic;
using System.Web.Mvc;
using System.Xml;
using SitemapActionResultSample.Models;
namespace SitemapActionResultSample.Components
{
public class SitemapActionResult : ActionResult
{
private List<Post> _posts;
public SitemapActionResult(List<Post> posts)
{
this._posts = posts;
}
public override void ExecuteResult(ControllerContext context)
{
context.HttpContext.Response.ContentType = "application/rss+xml";
using (XmlWriter writer = XmlWriter.Create(context.HttpContext.Response.Output))
{
writer.WriteStartElement("urlset", "http://www.sitemaps.org/schemas/sitemap/0.9");
writer.WriteStartElement("url");
writer.WriteElementString("loc", "http://site.com");
writer.WriteElementString("lastmod", DateTime.Now.ToString("yyyy-MM-dd"));
writer.WriteElementString("changefreq", "daily");
writer.WriteElementString("priority", "1.0");
writer.WriteEndElement();
foreach (Post post in this._posts)
{
writer.WriteStartElement("url");
writer.WriteElementString("loc", string.Format("http://site.com/{0}", post.Slug));
writer.WriteElementString("lastmod", post.UpdatedOn.ToString("yyyy-MM-dd"));
writer.WriteElementString("changefreq", "daily");
writer.WriteElementString("priority", "0.5");
writer.WriteEndElement();
}
writer.WriteEndElement();
writer.Flush();
writer.Close();
}
}
}
}
The code is straightforward. The SitemapActionResult gets a list of posts via its public constructor and applies some XML operations to iterate through the list of posts and generate the appropriate and valid XML output in the response.
To test this in action, I create a controller with a single action method to generate a few fake posts and pass them to the SitemapActionResult and generate the sitemap.
using System;
using System.Collections.Generic;
using System.Web.Mvc;
using SitemapActionResultSample.Components;
using SitemapActionResultSample.Models;
namespace SitemapActionResultSample.Controllers
{
public class SitemapController : Controller
{
public SitemapActionResult Index()
{
List<Post> posts = new List<Post>();
posts.Add(new Post()
{
ID = 1,
Title = "First Post",
Body = "Body for the first post.",
Slug = "first-post",
DateAdded = DateTime.Parse("October 4, 2009 7:02 AM"),
UpdatedOn = DateTime.Parse("October 4, 2009 7:02 AM")
});
posts.Add(new Post()
{
ID = 2,
Title = "Second Post",
Body = "Body for the second post.",
Slug = "second-post",
DateAdded = DateTime.Parse("October 5, 2009 1:48 PM"),
UpdatedOn = DateTime.Parse("October 6, 2009 6:36 AM")
});
posts.Add(new Post()
{
ID = 3,
Title = "Third Post",
Body = "Body for the third post.",
Slug = "third-post",
DateAdded = DateTime.Parse("October 6, 2009 11:20 AM"),
UpdatedOn = DateTime.Parse("October 6, 2009 11:20 AM")
});
return new SitemapActionResult(posts);
}
}
}
And I add a route to my routes collection to map incoming requests to the sitemap URL to this controller.
using System.Web.Mvc;
using System.Web.Routing;
namespace SitemapActionResultSample
{
public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Sitemap",
"sitemap",
new { controller = "Sitemap", action = "Index" }
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
}
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
}
}
}
This generates something like the below output.
The sample code for this post is available for download here.
Published at DZone with permission of Keyvan Nayyeri. See the original article here.
Opinions expressed by DZone contributors are their own.
Trending
-
Integrating AWS With Salesforce Using Terraform
-
Database Integration Tests With Spring Boot and Testcontainers
-
Send Email Using Spring Boot (SMTP Integration)
-
Mainframe Development for the "No Mainframe" Generation
Comments