Create a Google Site Map with ASP.NET

4.27 (22 votes)

Over Christmas, I had a bit of spare time, so I decided to add some functionality to this site. One of the things that I added was the ability for people to post comments about specific articles, and you will see that a form has been added at the end of this article. The other thing I have been meaning to get round to is to add a Google Site Map. I believe I have created my site to be as friendly to Google's bots as possible, but there is no harm in submitting a site map to guarantee that Google knows about all the pages I want indexed.

Google publish extremely helpful guidelines for site owners (are they still called Web Masters??), including details on the specification of the sitemap format that they are happy to accept. The current standard is SiteMap Protocol 0.9, and is the definition of a fairly simple xml file. One way to create ths is manually, but doing so means that I would have to update it every time I added an article, or even deleted one. So, much like my current RSS feed, I decided to generate the sitemap dynamically on demand.

The xml format is simply this:


<?xml version="1.0" encoding="UTF-8"?>

<urlset xmlns="">













For each <url> tag, there must be a <loc> tag pointing to the URL of the page. <lastmod>, <changefreq> and <priority> are optional. More details can be found at the link above for the specification.

I have four distinct sections that I want to include: the home page, the articles, the list of articles by type and the list of articles by category. I'm not bothered about Google indexing the About Me or Contact pages. Very few people are that interested in finding out how boring I am and subsequently wanting to get in touch. The Home page is always the same URL, so most of the <url> section could be hardcoded for that, but I need to get the creation date of the most recent article for the <lastmod> tag. I also need to get the IDs and the last modified date for each of the articles for section 2, and the list of IDs for the Article Types and the Categories for the remaining sections. So the stored procedure is simple, and contains just 4 SELECT statements, one after the other. Rather than post bits of the code as I usually do, here it is all in one go, with some explanation at the end as to how it works:

using System;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Text;
using System.Web;
using System.Xml;

namespace Mikesdotnetting
    public class Sitemap : IHttpHandler

        public void ProcessRequest(HttpContext context) {
            context.Response.ContentType = "text/xml";
            using (XmlTextWriter writer = new XmlTextWriter(context.Response.OutputStream, Encoding.UTF8)) {
                writer.WriteAttributeString("xmlns", "");

                string connect = ConfigurationManager.ConnectionStrings["MyConnString"].ConnectionString;
                string url = "";
                using (SqlConnection conn = new SqlConnection(connect)) {
                    using (SqlCommand cmd = new SqlCommand("GetSiteMapContent", conn)) {
                        cmd.CommandType = CommandType.StoredProcedure;
                        using (SqlDataReader rdr = cmd.ExecuteReader()) {
                            // Get the date of the most recent article
                            writer.WriteElementString("loc", string.Format("{0}Default.aspx", url));
                            writer.WriteElementString("lastmod", string.Format("{0:yyyy-MM-dd}", rdr[0]));
                            writer.WriteElementString("changefreq", "weekly");
                            writer.WriteElementString("priority", "1.0");
                            // Move to the Article IDs
                            while (rdr.Read()) {
                                writer.WriteElementString("loc", string.Format("{0}Article.aspx?ArticleID={1}", url,  rdr[0]));

                                if (rdr[1] != DBNull.Value)
                                    writer.WriteElementString("lastmod", string.Format("{0:yyyy-MM-dd}", rdr[1]));
                                writer.WriteElementString("changefreq", "monthly");
                                writer.WriteElementString("priority", "0.5");
                            // Move to the Article Type IDs
                            while (rdr.Read()) {
                                writer.WriteElementString("loc", string.Format("{0}ArticleTypes.aspx?Type={1}", url, rdr[0]));
                                writer.WriteElementString("priority", "0.5");
                            // Finally move to the Category IDs
                            while (rdr.Read()) {
                                writer.WriteElementString("loc", string.Format("{0}Category.aspx?Category={1}", url, rdr[0]));
                                writer.WriteElementString("priority", "0.5");

        public bool IsReusable {
            get {
                return false;

The code above is placedin the ProcessRequest method of a Generic Handler (ashx file). This is the most efficient way of outputting formatted text which is not HTML-based to the browser in ASP.NET Web Forms.

Then it's just a question of iterating the different ResultSets within the SqlDataReader object. The result of the first SELECT statement gives me the date that the most recent story was added. This tells me when the Home page was last modified. The String.Format() method is used to ensure that the format of the date complies with one of the two options that the Sitemap Protocol will accept as valid. I set the change frequency to weekly, as that's about how often I hope to add articles. Then I gave the page the priority of 1.0. That means nothing in terms of page ranking or search engine results. It simply tells Google that I think the home page is the most important page in relation to all others on my site.

Then I use the NextResult() method of the SqlDataReader. This moves things on to the results of the second SELECT statement, which has the AricleID and the DateModified in it. If there is a DateModified, I add the optional <lastmod> tag, and I give all the pages the same priority. It occurred to me that, since I keep a count of the article views in the database, I could have weighted each article based on that count, and applied a different priority to each of them. However, that can keep to another time, perhaps.

The final two calls to NextResult() are pretty self-explanatory.

I then logged into my Google account, and submitted the URL ( for my site map, having tested that it validates against the protocol.

Date Posted:
Last Updated:
Posted by:
Total Views to date: 45199


- huan

an exciting place to learn

- moratmarit

thanks for information..

- Speedbird186

That's a great sample, and just what I was looking for. Thanks!
Is there a particular reason why you would not be using an ASP.NET Handler instead of a page?

- Mike


No reason at all. In fact, a handler would be better.

- john

Hi Mike

Nice article, I'm fairly new to SQL, would you please list the GetSiteMapContent stored procedure

- Mike


SELECT TOP 1 DateCreated FROM Articles ORDER BY ArticleID DESC
SELECT ArticleID, DateAmended FROM Articles ORDER BY ArticleID DESC
SELECT ArticleTypeID FROM ArticleTypes
SELECT CategoryID FROM Categories

- Vikas Nagil

could you please post the complete source code and especially comnplete stored procedure, great article i was searching for it since many days and now found but would be great for others also if you could do the need full

- Craig Greenway

After about 2 hours of searching for a perfect Google dynamic sitemap feed, stumbled across this. All I can say is SPOT ON and thank you!

I read somewhere there is a limit on the number of links in a sitemap.txt (i.e. specific URL's in a plain text file format). Do you know if there are such limitations on RSS style feeds?

Thanks in advance


- Mike


I'm not aware of any such limit. I can't see any point in having a limit on sitemaps either. That seems to me to defeat the object of them.

- udham singh


i want to add below code in above example then how to do.

media="only screen and (max-width: 640px)"
href="" />

- Mike


I have no idea what you are trying to do. If you added that to a sitemap file, it wouldn't be valid.

- Raphael

Thanks alot. This is what is needed! Keep it up.

Recent Comments

Rajasekar 24/11/2015 12:27
In response to Import Data From Excel to Access with ASP.NET
While use this code i'm facing on error: "Unrecognized Database format C:\mydabase.accdb" can any...

Parmod 24/11/2015 07:28
In response to ASP.NET 5 Project Basics
For a new learner (Fresher) in ASP.NET there is a issue Fresher have to learn two types of , old...

Robert 22/11/2015 21:35
In response to ASP.NET 5 By Numbers
I have to agree fully with Paul, this does sound like an entire mis-mash of technologies. Sort of in...

Christian 21/11/2015 15:46
In response to MVC 5 with EF 6 in Visual Basic - Creating an Entity Framework Data Model
Many thanks Mike to introduce me in the EF6 Code First way of thinking. Exactly what I need for my...

ax plains 20/11/2015 16:29
In response to Examining the Details and Delete Methods
Hello, really great tutorial for a beginner like me! Is it possible to have an explanation on how a...

Abdul Latif 20/11/2015 14:42
In response to Reading Excel Files Without Saving To Disk In ASP.NET
Could anyone please help me, I am getting : "OfficeOpenXml.ExcelPackage" does not contain a for...

Thomas 20/11/2015 09:44
In response to Cheat Sheet - .NET Framework Exceptions
Hey Mike, nice list. I've also checked your article on how you created the list, but is there any to...

Pramod Gagare 19/11/2015 11:18
In response to Date Formatting in C#

Robby 19/11/2015 08:16
In response to WebMatrix - Database Helpers for IN Clauses
Would it also be possible to override the default query and querysingle methods to include the azure...

Menja 18/11/2015 08:28
In response to Sessions and Shopping Carts
Thank you for a perfect description and that you show all the screenshot at the same time!!! It a...