This post is a simple walkthrough to illustrate the usage the MVC XML Sitemap project.
If you haven’t already, grab the source here: http://mvcxmlsitemap.codeplex.com/Release/ProjectReleases.aspx
Once you’ve downloaded it, go ahead and unzip it, open the solution up in Visual Studio and do a rebuild so the assembly is compiled.
A Simple Sitemap Example
- Create a new ASP.NET MVC web application (the name is not important) and add a reference to the Cintellect.Web.Mvc.Sitemap assembly to ASP.NET MVC web application you just compiled.
- Now open up the HomeController in the Controllers folder and add the following using statement to the class:
using Cintellect.Web.Mvc.Sitemap;
- You’re now able to “opt in” actions in the HomeController by placing a [Sitemap] attribute on the action, like so:
[Sitemap]
public ActionResult Index()
{
ViewData["Message"] = "Welcome to ASP.NET MVC!";
return View();
}
- Now create a Sitemap action on the HomeController:
public ContentResult SiteMap()
{
return Content(SitemapFactory.Create(), "text/xml", System.Text.Encoding.UTF8);
}
- Press F5 to start debugging, then navigate to the Sitemap action and you should see something similar to the following:
<?xml version="1.0" encoding="utf-8" ?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>http://localhost:58496/home/Index</loc>
</url>
</urlset>
[Sitemap] Attribute Named Parameters
When you added the [Sitemap] attribute to the Index action, you might have noticed there are a number named parameters for which you can specify values. Three of these parameters correspond to the XML tags specified in the sitemaps.org protocol, while the others are extended functionality of the Mvc Xml Sitemap framework.
Before we take a look at some more advanced examples, let’s review what each of the named parameters does.
- ChangeFrequency – Allows you to explicitly indicate the change frequency of the action. Valid values are: always, hourly, daily, weekly, monthly, yearly, never. Please see the sitemaps.org protocolfor more information.
- LastModified – Ignore this parameter for right now, you can’t specify the value for it in the [Sitemap] attribute.
- Priority – Allows you to explicitly indicate the priority of this action relative to others on your site. Valid values range from 0.0 to 1.0.
- IgnoreAuthorizeAttribute – This parameter must be set to true on all actions that have an [Authorize] attribute, and serves merely as verification that you want a protected page URL to be exposed in your sitemap.
- QueryKey – The sitemap framework allows you to pass the SitemapFactory class delegate functions to create URLs that go deeper than just the controller action. For instance, let’s say you have a Product controller with a Details action that accepts a product id as a parameter in the route (i.e. http://www.yourdomain.com/Products/Details/1), you can pass a function to the SitemapFactory which will retrieve all products for inclusion in the sitemap. This parameter is simply specifies which delegate function should be called for the current action.
- Route – The sitemap framework will work out of the box with the default {controller}/{action}/{id} route of ASP.NET MVC. However, there are times when you’ll want to specify more advanced routes. This parameter allows you to specify the route which the current action will be executed under so that the URL is generated correctly.
- SitemapProfile – The sitemap framework allows you to create sitemap profiles in the web.config in which you can provide values for any of the previously discussed named parameters. This parameter tells the framework which profile to load for the action.
I think specifying values for ChangeFrequency and Priority are fairly straight forward, so let’s jump into two more advanced topics:
- Creating sitemap profiles in the web.config
- Using QueryKey to specify a delegate to return URLs that are deeper than just the controller action.
Creating Sitemap Profiles in the Web.config
Since many of your actions may share the same sitemap configuration, storing these configurations as a profile in the web.config is a great way to reduce redundant code and provide a way for bulk update. It’s also great because if you need to change a value, you won’t need to recompile your code and redeploy.
To enable sitemap profiles in your web.config, follow these steps:
- Modify the <configSections> element to the following (note the inclusion of the new cintellect.web.mvc.sitemap section):
<configSections>
<section name="cintellect.web.mvc.sitemap" type="Cintellect.Web.Mvc.Sitemap.SitemapSection, Cintellect.Web.Mvc.Sitemap" />
<sectionGroup name="system.web.extensions" type="System.Web.Configuration.SystemWebExtensionsSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
<sectionGroup name="scripting" type="System.Web.Configuration.ScriptingSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
<section name="scriptResourceHandler" type="System.Web.Configuration.ScriptingScriptResourceHandlerSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication"/>
<sectionGroup name="webServices" type="System.Web.Configuration.ScriptingWebServicesSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
<section name="jsonSerialization" type="System.Web.Configuration.ScriptingJsonSerializationSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="Everywhere"/>
<section name="profileService" type="System.Web.Configuration.ScriptingProfileServiceSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication"/>
<section name="authenticationService" type="System.Web.Configuration.ScriptingAuthenticationServiceSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication"/>
<section name="roleService" type="System.Web.Configuration.ScriptingRoleServiceSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication"/>
</sectionGroup>
</sectionGroup>
</sectionGroup>
</configSections>
- Add the following section between the <connectionStrings> and <system.web> sections:
<cintellect.web.mvc.sitemap>
<sitemapProfiles>
<clear />
<add name="HomeWeeklyProfile" route="Default" changeFrequency="weekly" priority="0.7" />
</sitemapProfiles>
</cintellect.web.mvc.sitemap>
- Now, modify the About action of the HomeController to:
[Sitemap(SitemapProfile = "HomeWeeklyProfile")]
public ActionResult About()
{
return View();
}
- Press F5 to start debugging, then navigate to the Sitemap action and you should see something similar to the following:
<?xml version="1.0" encoding="utf-8" ?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>http://localhost:58496/home/Index</loc>
</url>
<url>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<loc>http://localhost:58496/home/About</loc>
</url>
</urlset>
Notice how the sitemap framework picked up the change frequency and priority which was specified in the profile. Pretty neat, eh?
Using QueryKey to Specify a Delegate to Return URLs
What I’m about to show you next is probably my favorite feature of the framework so far – mainly because it opens up the framework to the developer.
All the work we’ll be doing this time is in the HomeController.
- Add a new action called Detail and add a [Sitemap] attribute with the QueryKey = “GetAllProducts”, like this:
[Sitemap(QueryKey = "GetAllProducts")]
public ActionResult Detail()
{
return View();
}
- Add a new static method to the HomeController called GetAllProducts:
private static Url[] GetAllProducts()
{
List<Url> result = new List<Url>();
string baseUrl = @"http://localhost:58496/Home/Detail";
result.Add(new Url() { Location = String.Format("{0}/{1}", baseUrl, 1) });
result.Add(new Url() { Location = String.Format("{0}/{1}", baseUrl, 2) });
result.Add(new Url() { Location = String.Format("{0}/{1}", baseUrl, 3) });
result.Add(new Url() { Location = String.Format("{0}/{1}", baseUrl, 4) });
result.Add(new Url() { Location = String.Format("{0}/{1}", baseUrl, 5) });
result.Add(new Url() { Location = String.Format("{0}/{1}", baseUrl, 6) });
return result.ToArray();
}
- Update the Sitemap action to the following:
public ContentResult SiteMap()
{
Dictionary<string, Func<Url[]>> queries = new Dictionary<string, Func<Url[]>>();
queries.Add("GetAllProducts", GetAllProducts);
return Content(SitemapFactory.Create(queries), "text/xml", System.Text.Encoding.UTF8);
}
- Press F5 to start debugging, then navigate to the Sitemap action and you should see something similar to the following:
<?xml version="1.0" encoding="utf-8" ?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>http://localhost:58496/home/Index</loc>
</url>
<url>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<loc>http://localhost:58496/home/About</loc>
</url>
<url>
<loc>http://localhost:58496/home/Detail</loc>
</url>
<url>
<loc>http://localhost:58496/Home/Detail/1</loc>
</url>
<url>
<loc>http://localhost:58496/Home/Detail/2</loc>
</url>
<url>
<loc>http://localhost:58496/Home/Detail/3</loc>
</url>
<url>
<loc>http://localhost:58496/Home/Detail/4</loc>
</url>
<url>
<loc>http://localhost:58496/Home/Detail/5</loc>
</url>
<url>
<loc>http://localhost:58496/Home/Detail/6</loc>
</url>
</urlset>
Let’s briefly walk through this. We created a new action and decorated it with a [Sitemap] attribute. The sitemap attribute is looking for a query with a key of “GetAllProducts” to be passed to the Create method of the SitemapFactory.
We then added the GetAllProducts static method to the HomeController. This function had a return type of Url[] since that is the only return type currently accepted by SitemapFactory for any delegates passed to it. The GetAllProducts method contained some business logic to create an array of Url. In this case we were simply creating instances of the Url class and adding it to the List, however, this method could contain some complicated logic which retrieves the Urls via LINQ to SQL, Entity Framework, WCF, or practically any other means.
Finally we updated our Sitemap action to be aware of the queries we were sending to it. Notice how we instantiated the queries dictionary with Func<Url[]> as the value of the dictionary, which is exactly what the SitemapFactory is expecting - a function with a return type of Url[].
Wrap Up
Hopefully this is a useful walkthrough of how to use the Mvc Xml Sitemap framework. Please feel free to leave comments either here or on Codeplex.