18 March 2009 - 1:30 AM / by Dominic Pettifer. 15 Comments for ASP.NET MVC and Clean SEO Friendly URLs.
Technical Article - I show you how to generate SEO friendly URLs in ASP.NET MVC using the actual title/name of the record or item (product/blog name etc.) instead of the ID. That way we can have nice looking URLs that look like /products/123/apple-ipod-nano-4gb instead of /products/detail/123. The trouble is by default, ASP.NET MVC Html helpers don’t always encode the URL properly. Also how do we deal with updating the title, and old broken links?
Believe it or not, search engines such as Google will pay attention to keywords contained in the URL, so that a URL like gadgetshop.com/products/123/apple-ipod-nano-4gb will rank higher in the search engines than a URL that looks like gadgetshop.com/showProduct.aspx?id=123. Also if people post links to your site in message boards/blogs/MyFace pages etc. they tend to paste the URL in directly. Other people will see a hyperlink with the text http://www.gadgetshop.com/products/123/apple-ipod-nano-4gb and can easily imagine where the page might lead to (a page to buy a 4GB iPod Nano), whereas the URL http://www.gadgetshop.com/showProduct.aspx?id=123 doesn't give any hint as to where it might lead to. So, nice clean URLs are clearly beneficial.
One of the major benefits of ASP.NET MVC is that it allows us truly dynamic URLs via its Routing system. We could declare a new Route like this:
routes.MapRoute(
"ViewProduct",
"products/{id}/{productName}",
new { controller = "Product", action = "Detail", id = "", productName = "" }
);...and in your ProductController class...
public ActionResult Detail(int? id, string productName)
{
Product product = IProductRepository.Fetch(id);
return View(product);
}Plenty of tutorials on ASP.NET MVC declare routes for detail pages as /Product/View/{id} which doesn't include any useful SEO indexable content in the URL. So I thought for this blog, I would declare the View Blog Route as /Blogs/{id}/{blogTitle} (Note: I'm currently rewriting this blog software in MVC).
Things were fine until I hit a snag with some of the titles for my blogs that contained funny characters. For instance, "JavaScript/CSS and Custom Controls" (the slash causes MVC routing system to think it's a folder), and "Get a<HEAD> with ASP.NET 2.0" (HTML brackets) were causing problems.
I was doing things properly and using Html.ActionLink helper method:
<%= Html.ActionLink(item.Title, "View", "Blog",
new { id = item.BlogId, blogTitle = item.Title }, null)%>...but it just wasn't encoding the special characters properly. Limitation with ASP.NET MVC? I'm not sure, someone please enlighten me!
I wrote a simple method that encodes the URLs into a nice, safe, SEO friendly URL format:
public static class UrlEncoder
{
public static string ToFriendlyUrl (this UrlHelper helper,
string urlToEncode)
{
urlToEncode = (urlToEncode ?? "").Trim().ToLower();
StringBuilder url = new StringBuilder();
foreach (char ch in urlToEncode)
{
switch (ch)
{
case ' ':
url.Append('-');
break;
case '&':
url.Append("and");
break;
case '\'':
break;
default:
if ((ch >= '0' && ch <= '9') ||
(ch >= 'a' && ch <= 'z'))
{
url.Append(ch);
}
else
{
url.Append('-');
}
break;
}
}
return url.ToString();
}
}Notice the "(this UrlHelper helper," part? This is an extension method so we can access it in our view using:
<%= Url.ToFriendlyUrl(item.Title) %>
It works on a white-list principle, allowing all 0-9 and a-z characters through, dealing with a few special cases, and converting everything else to -hyphens-. You'll notice that spaces are being converted to hyphens as well, and you might be tempted to use _underscores_ instead. Don't! There are sound SEO benefits for using hyphens in that search engines bots treat them as spaces. "Get a<HEAD> with ASP.NET 2.0" although it gets converted to "get-a-head--with-asp-net-2-0", search bots will treat it as "get a head with asp net 2 0", providing some nice indexable keyword content.
Now we can change our HTML ActionLink helper method to:
<a href="<%= Url.Action("View", "Blog",
new { id = item.BlogId, blogTitle = Url.ToFriendlyUrl(item.Title) }) %>">
<%= Html.Encode(item.Title) %>
</a>You might be wondering why we carry the id in the URL, why not just have the URL as /Blogs/get-a-head--with-asp-net-2-0. Well first, when we encode the blog title in the URL, we lose a bit of information. It would be difficult to match "get-a-head--with-asp-net-2-0" to the actual title "Get a<HEAD> with ASP.NET 2.0" stored in the database. We could easily overcome this by storing the fully encoded URL in the database as a separate field and search on that. That would certainly work, except if the URLEncode function was later updated and database values weren't synced up properly to the new way the function was encoding URLs.
But one of the biggest reasons is performance. Lookups performed on Primary Key IDs in databases are a lot faster than searching on a VARCHAR field.
We also have the benefit that we can tell search engines if the blog (or indeed product) title changes. If we have the URL /Products/123/ipod-nano, Google Bot goes about indexing the site and puts the page /Products/123/ipod-nano into its index, and users add the link to forums, theirs blogs and so on. Later the administrator updates the product title to something a little more descriptive so the URL is now /Products/123/apple-ipod-nano-4gb-black-3rd-generation.
But wait, we now have a bunch of old URLs all over the place, in blogs/forums etc. and on Google's index that point to the old page /Products/123/ipod-nano. Broken links!? Not at all, because the ID '123' is still the same, Primary Key IDs never change, and this is what the database lookup is performed on, so it can still display the correct product, no need for any 404. We can programmatically detect if the product title URL is different from the one stored in the database, and issue a 301 response if it is.
A 301 response code is a 'Permanent Redirect' response (another response code similar to 200 OK, or 404 Page Not Found), it basically tells Google that the page has 'permanently' moved to /Products/123/apple-ipod-nano-4gb-black-3rd-generation if it tries to re-index /Products/123/ipod-nano again, and Google will update its index accordingly. Note that calling Response.Redirect(); in code, issues a 302 response code, which is a 'temporary' redirect (page has been temporarily moved), these are no good as google won't follow them.
Finally, users clicking on a /Products/123/ipod-nano link in a blog or bulletin board will be harmlessly redirected to the correct URL, so when they try to bookmark, or copy/paste the URL from the address bar themselves, it'll be the correct one. Here is some code on how you how could do this from your Controller Action:
public ActionResult Details(int? id, string productTitle)
{
Product product = ProductRepository.Fetch(id);
string realTitle = UrlEncoder.ToFriendlyUrl(product.Title);
string urlTitle = (productTitle ?? "").Trim().ToLower();
if (realTitle != urlTitle)
{
Response.Status = "301 Moved Permanently";
Response.StatusCode = 301;
Response.AddHeader("Location", "/Products/" + product.Id + "/" + realTitle);
Response.End();
}
return View(product);
}
Great, been looking for something like this. But, say you want to go TRULY dynamic and store your controller > url mappings (with the rest of the menu info) in your database, which you then obviously generate on every pagerequest...any idea how you could accomplish that?
Posted on 21 January 2010 - 2:42 PM / by Pleun
Not sure if this is what you're after, but one of the ASP.NET MVC developers, Phil Haack, has written a couple of articles on edittable routes:
Editable Routes Using App_Code
Posted on 24 January 2010 - 10:11 PM / by Dominic Pettifer (Administrator)
Thank you for sharing This knowledge.Excellently written article, if only all bloggers offered the same level of content as you, the internet would be a much better place. Please keep it up!..
Posted on 30 December 2011 - 6:01 AM / by printed t shirts
Yes, I like to move on with my business trends in market.
Posted on 4 January 2012 - 8:38 AM / by text messages
this is great I altered only the view code to show:
<a href="<%= Url.Action("post", "blog",
new { id = BlogPost.ID, Title = Url.ToFriendlyUrl(BlogPost.Title) }) %>but when I do it renders a ?Title= instead of a / in the url as shown here:
blog/post/4?Title=blog-post-title-4
Posted on 25 December 2009 - 12:21 AM / by John
You have to have a route that includes the paramater within the route. If you don't then asp.net mvc will tack the paramater on as a query string.
Posted on 13 November 2010 - 11:55 PM / by Alex Ford
An SEO Company USA hires the best employees for their organization, who have the ability to face the new challenges and accomplish the need of clients. Indian SEO companies offer high quality services at affordable rates as they find qualified resources easily. If you are seeking out instant result, hire a best SEO company London, who is fully experienced and has a good rapport in the online market. As soon as you hire an Indian company, it starts showing you result (WEB Development India).
Posted on 3 January 2012 - 12:52 PM / by WEB Development India
An SEO Company USA hires the best employees for their organization, who have the ability to face the new challenges and accomplish the need of clients. Indian SEO companies offer high quality services at affordable rates as they find qualified resources easily. If you are seeking out instant result, hire a best SEO company London, who is fully experienced and has a good rapport in the online market. As soon as you hire an Indian company, it starts showing you result (WEB Development India).
Posted on 3 January 2012 - 12:53 PM / by WEB Development India
Great Post. Just what I needed. Here is a complete sample of a class you could use - basically a combination of the above techniques.
public static class UrlEncoderHelper
{
public static string ToFriendlyUrl( this UrlHelper helper, string urlToEncode )
{
urlToEncode = (urlToEncode ?? "").Trim().ToLower();
urlToEncode = Regex.Replace( urlToEncode, @"[^a-z0-9]", "-" ); // invalid chars
urlToEncode = Regex.Replace( urlToEncode, @"-+", "-" ).Trim(); // convert multiple dashes into one
return urlToEncode;
}
}
Posted on 29 October 2009 - 5:38 PM / by Stuart Clode
I had got a desire to make my organization, but I didn't have got enough of cash to do that. Thank heaven my mate recommended to take the home loans. Thence I used the consolidation loans and made real my dream.
Posted on 19 November 2011 - 9:28 AM / by home loans
Hi All
I have never posted a comment before so this is new for me.
Thanks for the article Dominic. I have created a little URL helper which gives a nice clean url string. Here it is
public static string CleanURL(this UrlHelper helper, string originalUrl)
{
string cleanUrl = Regex.Replace(originalUrl, @"[^a-z0-9\s-]", "_"); // invalid chars
cleanUrl = Regex.Replace(originalUrl, @"\s+", "_").Trim(); // convert multiple spaces into one
return cleanUrl;
}
To use it, you must use the System.Text.RegularExpressions namespace. Hope that helps someone else.
Rob
Posted on 1 October 2009 - 1:39 PM / by Rob
Instead of a huge function uo could do this with one line of code, using a simple regular expression pattern based on something like this:
return new Regex("[^a-zA-Z]").Replace(s, "_")
I.e. if a character isn't acceptable, replace it with one that is.
I would always try to avoid loops in managed code, especially in a web application where performance is naturally an important consideration. I would also try to avoid string manipulation in managed code.
The System.Text namespace in the .NET framework is an unmanaged library that provides string manipulation classes like Regex which will typically perform better and at a lower level than anything we would write in C# or VB.NET.
Posted on 3 July 2009 - 2:32 PM / by Tim Acheson
"I would also try to avoid string manipulation in managed code"... Thats was seriously good advice in early '90. ;) ASP.NET MVC is full of reflection and other things that will take 1000x more time.
Posted on 22 March 2010 - 5:50 PM / by Tom
@Nik,
You will need to add reference to the Namespace in which your URLEncoder class is onto the viewpage.
For Example:
<%@Import Namespace="MvcApplication1.Helpers" %>
Your <%= Html.ToFriendlyUrl()%> should be available in all other places on the page.
Posted on 12 June 2009 - 5:19 PM / by Turnkey
Good post Dominic!
Where did you put the UrlEncoder class? My view can't see the method.
A slight problem with your method is that it could potentially lead to duplicate content in search engines.
e.g.
If multiple URLs with the same ID in the URL get linked to from somewhere on the web and indexed by a search engine they effectively link to the same page, hence duplicate content.
http://www.dominicpettifer.co.uk/Blog/34/asp-net-mvc-and-clean-seo-friendly-urls
http://www.dominicpettifer.co.uk/Blog/34/asp-net-mvc-and-clean-seo-friendly-urls-some-other-text-here
Both the URLs above link to this page...
Posted on 12 June 2009 - 5:13 PM / by Nik Makris
If multiple URLs with the same ID in the URL get linked to from somewhere on the web and indexed by a search engine they effectively link to the same page, hence duplicate content.
http://www.dominicpettifer.co.uk/Blog/34/asp-net-mvc-and-clean-seo-friendly-urls
http://www.dominicpettifer.co.uk/Blog/34/asp-net-mvc-and-clean-seo-friendly-urls-some-other-text-here
Both the URLs above link to this page...
The old out-of-date URL would 301 redirect to the new URL. Although both URLs could potentially exist in the search engine's database, the search engine would eventually reindex the links, find the 301, and remove the old URL from it's index.
Posted on 12 June 2009 - 7:21 PM / by Dominic Pettifer (Administrator)
rand-name side, while discussing how to be thrifty. The topic of two extreme <a href="http://www.nikejordan-outlet.com/air-jordan-retro-23-C55.html" title="buy jordan 23">buy jordan 23</a> contradictions in them can cause a sensation. Because of money, to publicly talk about enjoyment; because it is hard origin, it can be righteous stingy Stingy. The convenience of a rich man, <a href="http://www.nikejordan-outlet.com/" title="retro jordans shoes">retro jordans shoes</a> but do not bear the kind of r...
Posted on 21 May 2011 - 4:22 AM / by Air Jordan 3
Good post Dominic. you add some useful info into my knowledge. link
Posted on 16 December 2011 - 11:34 AM / by super sale
Cool! Thanks for your suggestion.
Have you by any chance blogged about ASP.NET MVC Client and server validation? I'm fighting away with it. I was following S.Hanselmann's post about NerdDinner to get a good idea about MVC (mainly because I was interested in finding out Business rule and data validation integration), however, got stuck at Validation!! Scott is using Linq to SQL but I'm using Entity Framework as DAL. There is no OnValidate Event in EntityFramework! Got directed towards a MSDN article about entity framework validation but it is painfully difficult and clumsy to get it to work. What I want is a clean and easy client and server validation. Something that generates JQuery javascript by interrogating the model that is in context for that particular view. This model could be an Entity object with DataAnnotation validation attributes added to all the properties that need validation.
Perhaps there is a better way in doing what I want to achieve?? Any thoughts are much appreciated.
P.S. Couldn't post my code here, may be too large!
Posted on 28 May 2009 - 11:21 AM / by Turnkey
if you're going to search for a legitimate <a title="wmns lunar elite+" href="http://www.shoeswall.com/wmns-lunar-elite-women-shoes-store-selling.htm" target="_blank">wmns lunar elite+</a> handbag supplier, all you have to do is type wholesale designer handbags in the google search box, and you've got <a title="women nfl jerseys" href="http://www.westonsale.net/wholesale-women-nfl-jerseys-cheap-women-nfl-football-jerseys.htm" target="_blank">women nfl jerseys</a> thousands of potential handbag wholesalers.to be able to spot a scammer, keep in mind this old adage: if it's too <a title="versace men long jeans" href="http://www.westonsale.net/versace-mens-long-jeans.htm" target="_blank">versace men long jeans</a> good to be true, then it usually is. don't be easily fooled with purchasing so-called wholesale lists. while some <a title="air jordan 19 xix x9" href="http://www.shoeswall.com/nike-air-jordan-19-xix-x9-shoes-high-quality.htm" target="_blank">air jordan 19 xix x9</a> of these may be true, others are not. these wholesale lists are being sold in ebay and a variety of other <a title="mbt tunisha women shoes" href="http://www.antishoesmbt.com/mbt-tunisha-women-anti-shoes-credit-card.htm" target="_blank">mbt tunisha women shoes</a> websites, so beware of these lists. save as much as $7.99 by not buying these designer handbag wholesale
Posted on 15 August 2011 - 8:03 AM / by IWC Watches
. are wearing lately. after all, trend for the masses <a title="mbt tariki shoes men" href="http://www.brandcheep.com/paypal-credit-card-mbt-tariki-shoes-men.htm" target="_blank">mbt tariki shoes men</a> is frequently dictated by the styles of the famous and rich. shopping, entertainment and fashion magazines are always <a title="replica watch discount" href="http://www.watchdiscounted.net" target="_blank">replica watch discount</a> full of clues as to what is hot and, by omission, what is not. following these guidelines can help <a title="yves saint lauret platform pumps" href="http://www.footcheap.com/yves-saint-lauret-platform-pumps-for-ladies.htm" target="_blank">yves saint lauret platform pumps</a> you pick out the best wholesale fashions.purchasing wholesale is the practice
Posted on 17 August 2011 - 7:50 AM / by Vibram Five Fingers Classic
I have been constructing SEO friendly URLs myself but did not care about the special characters very much. Perhaps it is a good idea to take those in consideration.
Also, I haven't really developed websites for the Internet but good to know how google bots work. Dominic mentioned that if a product's title has changed we could respond to the request by a StatusCode of 301. What if the product is discontinued or no longer available? What is the response status code then? What would you recommend to do in that case? Still show a page that dictates that the product is no longer available?
There might be no harm in showing the page but if it is no longer relevant perhaps we should inform that that product no longer exists. In my case, I am dealing with a property website, I don't want to keep/show properties that have been SOLD. If someone keeps the URL indexed/bookmarked and I do not want to show the page if the property is sold, is there a gracious way to handle this?
Posted on 25 May 2009 - 10:03 AM / by Turnkey
What if the product is discontinued or no longer available? What is the response status code then? What would you recommend to do in that case? Still show a page that dictates that the product is no longer available?...I don't want to keep/show properties that have been SOLD. If someone keeps the URL indexed/bookmarked and I do not want to show the page if the property is sold, is there a gracious way to handle this?
You could issue a custom built 404 page with a "call to action" eg. "The property you are looking for is no longer available but you might be interested in these properties instead" or something to that effect. You could even check the property title string for clues (/Properties/123/2-bedroom-flat-in-manchester) and perform a search on that and display related properties on the 404 page.
The fact you're serving a 404 will mean Google will remove the page from it's index (providing you've removed inbound links on your site to that page), while any users with the URL bookmarked, or following from another site, will get a useful list of related Properties they might be interested in.
Posted on 28 May 2009 - 10:58 AM / by Dominic Pettifer (Administrator)
Nice article. Stupid that the standard Url.Encode and HttpUtility.UrlEncod() don't catch the forward slash.
Posted on 20 March 2009 - 9:05 AM / by BorisCallens
Newest styles of <a href="http://www.christianlouboutinreplicacl.com/"><strong>christian louboutin high heels</strong></a> in hot sale now, <a href="http://www.christianlouboutinreplicacl.com/"><strong>Christian Louboutin Knockoffs</strong></a> shoes sale now, buy <a href="http://www.christianlouboutinreplicacl.com/"><strong>christian louboutin replica</strong></a> in our online uk store .your shoes sales prices will save.
Posted on 22 September 2011 - 2:32 AM / by christian louboutin replica
I asked a friend of mine to change my company`s site and he said it is not SEO friendly, so he used ASP.NET MVC to change my URL`s. It took him 3 days to complete the task, now the majority of the pages are easier to be indexed by Google bot.
Posted on 29 September 2011 - 3:19 PM / by XML Editor
And YouTube still auto-fucking-plays videos!! This is TWO-THOUSAND-AND-FUCKING-TWELVE FFS!!!
about 20 hours ago from webOn a side-note, YouTube's commenting system is god-awful atrocious dreadful horrible horrible horrible!! Constant meaningless error messages
about 20 hours ago from webJavaScript is slow mmmkay http://t.co/NbB4eQjw - Actually, no, it's not http://t.co/kpGEIoPO #nodejs
about 20 hours ago from webTFS: It's super expensive, so it must be brilliant, right? Like Sharepoint #tekpubtfstitlesuggestion
5:22 PM February 3rd from web