Rebuilding in ASP.NET MVC 2 - Part 3 - SEO URLS and Rating Charts

UPDATE: Links to the MVC site on the URL are no longer active, now that the site is live at

Yesterday I uploaded a new version of my web site to Besides a number of small changes in terms of layout and design, I added two main features.

SEO Friendly URLs

You may notice that rather than linking to something like QuickDocId.aspx?quickdoc=512, all internal links to content now look like this: /512/handling-disabled-links-in-aspnet-4. This is good for Search Engine Optimization as the URL now clearly describes the content of the page. I considered a few different options of building the URL, including using the date and time together with the page title as well as a short encrypted or hashed unique value together with the title, but eventually decided to just leave in the Quick Doc ID and add the title. The actual title is converted into a search engine friendly URL using a small extension method on the HtmlHelpers class. The original function came from Kamran Ayub but I am using a slightly modified version that also removes accents, created by John Roland. I improved the function a bit further by making use of optional parameters in C# to determine the final length of the string. My complete function now looks like this:

public static string GenerateSlug(this string phrase, int maxLength = 100)
  string tempResult = phrase.RemoveAccents().ToLower();
  tempResult = Regex.Replace(tempResult, @"[^a-z0-9\s-]", "");
  tempResult = Regex.Replace(tempResult, @"\s+", " ").Trim();
  tempResult = tempResult.Substring(0, 
      tempResult.Length <= maxLength ? tempResult.Length : maxLength).Trim();
  tempResult = Regex.Replace(tempResult, @"\s", "-"); 
  return tempResult;

If I don't specify a length, the string is cut off at 100 characters. However, if I need a shorter or longer string, I can now specify a max length.

Rating Image

Each article on my web site can be rated using a "five-star" rating mechanism. In the Web Forms version of my site I am using a custom-built Rating control. Building that control took quite a lot of time and code, so I was a bit worried about the MVC implementation. It turned out, however, that with the charting functionality built into ASP.NET and a simple controller, this is extremely simple to accomplish.

I created a controller called DynamicImage with a method called ContentRating. By not tying the controller name to the rating controller, I can easily reuse my controller for other dynamic images, such as the doughnut charts shown in the "Squash Blogs" section of the site:

public FileResult ContentRating(int id)

Inside this action method I am performing the following operations. largely based on code from by this post on

  • Get the rating information from the database using the ADO.NET Entity Framework.
  • Create a new System.Web.UI.DataVisualization.Charting.Chart in memory
  • Set relevant properties of the chart, such as series and chart areas.
  • Save the chart into a MemoryStream
  • Return the MemoryStream through a FileContentResult.

The code below shows the full implementation, except for the part that gets the data. To keep this post simple, I have hard-coded the chart values that are assigned to the yValues variable.

public FileResult ContentRating(int id)
  Chart chart = new Chart { Width = Unit.Pixel(170), Height = Unit.Pixel(100) };

  Series series1 = new Series("Default");
  series1.ChartArea = "ChartArea1";
  series1.ChartType = SeriesChartType.Column;
  series1.IsValueShownAsLabel = false;
  series1.Color = Color.FromArgb(0xff, 0x66, 0x00);
  series1["PointWidth"] = "0.5";

  // Populate series data
  int[] yValues = new int[] { 23, 17, 189, 411, 388 };

  string[] xValues = { "1", "2", "3", "4", "5" };
  chart.Series["Default"].Points.DataBindXY(xValues, yValues);

  ChartArea ca1 = new ChartArea("ChartArea1");
  ca1.BackColor = Color.FromArgb(0xf0, 0xf0, 0xf0);


  chart.BackColor = Color.FromArgb(0xf0, 0xf0, 0xf0);

  using (var ms = new MemoryStream())
    chart.SaveImage(ms, ChartImageFormat.Png);
    ms.Seek(0, SeekOrigin.Begin);
    return File(ms.ToArray(), "image/png", "mychart.png");

This code basically mimics the declarative syntax you use with Web Forms; you set up a chart, add one or more series, and define appearance such as the background color, chart type (SeriesChartType.Column in this case), width of the bars and so on. CleanUpAxis is a simple helper method that removes the grid, labels and tick marks from the two axis, resulting in a very clean bar chart:

private static void CleanUpAxis(Axis axis)
  axis.MajorGrid.Enabled = false;
  axis.MajorTickMark.Enabled = false;
  axis.LineWidth = 0;
  axis.LabelStyle.Enabled = false;

When you now request a URL such as /DynamicImage/ContentRating/513 the image is created and streamed to the browser as a PNG image. Inside my View I can use the simplest <img /> element possible to display the chart:

<img src="/DynamicImage/ContentRating/<%= Model.Id %>" />

Below the image I render a standard <form /> with five radio button controls. With a bit of jQuery I set each radio button to auto submit to the server when clicked. When that happens, my Rating controller fires its Update method, sends the ID of the selected content item and the rate value to the database and then redirects to the Detail action of the Content controller which in turn redisplays the content item together with the updated rating chart. You can see (and test) a live version of the chart here.

I still need to build in some details such as preventing users from rating the same content item more than once over time, but I am real glad to see that setting up the general structure of the rating functionality was so easy.

Where to Next?

Wonder where to go next? You can read existing comments below or you can post a comment yourself on this article.

Doc ID 520
Full URL
Short cut
Written by Imar Spaanjaars
Date Posted 03/15/2010 18:17

Talk Back! Comment on Imar.Spaanjaars.Com

I am interested in what you have to say about this article. Feel free to post any comments, remarks or questions you may have about this article. The Talk Back feature is not meant for technical questions that are not directly related to this article. So, a post like "Hey, can you tell me how I can upload files to a MySQL database in PHP?" is likely to be removed. Also spam and unrealistic job offers will be deleted immediately.

When you post a comment, you have to provide your name and the comment. Your e-mail address is optional and you only need to provide it if you want me to contact you. It will not be displayed along with your comment. I got sick and tired of the comment spam I was receiving, so I have protected this page with a simple calculation exercise. This means that if you want to leave a comment, you'll need to complete the calculation before you hit the Post Comment button.

If you want to object to a comment made by another visitor, be sure to contact me and I'll look into it ASAP. Don't forget to mention the page link, or the Doc ID of the document.

(Plain text only; no HTML or code that looks like HTML or XML. In other words, don't use < and >. Also no links allowed.