nopCommerce ID-less URL Structure Demystified

I believe many of you nopCommerce pro users and developers are aware that nopCommerce 2.70 and 2.80 have employed a cleaner URL compared to the previous versions. From URLs that are suffixed with '.aspx' in versions 1.XX; to extentionless but rather verbous URLs in versions 2.65 and below, we have seen a lot of changes in the URL structure in nopCommerce. However, none of them are as mysterious as the URLs in 2.70 and 2.80. Why? Because nopCommerce seems to know the magic to convert from any arbitrary texts to integer IDs.

For example, the link for my NopLite - nopCommerce Responsive Theme is http://www.pronopcommerce.com/noplite-nopcommerce-responsive-theme. You don't see ANY integer in the URL, but nopCommerce somehow knows how to map from the URL to the appropriate ID. On the other hand, the nopCommerce 2.65 URL for my NopLite theme would have been: http://www.pronopcommerce.com/p/7/noplite-nopcommerce-responsive-theme. Note the '7' somewhere in between the URL, that's the Integer Product ID.

So the question is, how does nopCommerce 2.70 and 2.80 know the ID without looking the ID?

The UrlRecord Database Table

Well, the information is actually stored in a database table called UrlRecord. The table stores the slugs of entities to be mapped. A slug is any URL friendly-text and must be unique per nopCommerce installation. And then there is the EntityId column, which actually maps back to the actual entity represented by the slug. Last but not least, the EntityName column tells nopCommerce the actual entity type (Category, Product, BlogPost and etc) that an EntityId represents.

The nopCommerce database table UrlRecord stores the information of URL Slugs

This table, although useful, is only one part of the equation. We have stored the information, then there must be a way to connect the dots to somehow retrieve the information from the database, and map it with the URLs. The next part of the "magic" lies in the code.

Connecting the Dots - GenericUrlRouteProvider, GenericPathRoute and GenericPathRouteExtensions

First of all, let's open Nop.Web.Framework.Seo.GenericPathRoute.cs, and you'll see something like below:

Code snippet from GenericPathRoute.cs showing how it retrives UrlRecord from the database

Basically what the GenericPathRoute class does is to retrieve the RouteData information from the HttpRequest, extract the slug, and compare it with the database record (remember our UrlRecord database table?). If it eventually finds any active exsting record, it then provides additional values to the RouteData (see figure below) such as the Controller, the Action and the ID. In short, GenericPathRoute.cs encapsulates the logic that glue together the three pieces: UrlRecord database table, the actual Controller & Action that is responsible for producing the HTML result, and any other parameters required for the Action to perform correctly.

Code snippet showing how GenericPathRoute.cs class adds the required RouteData

But we are still missing one thing - we need to actually tell MVC to map the ID-less URLs to our freshly baked GenericPathRoute class. In other words, we have to let MVC routing engine knows that: when there is any ID-less URL coming in, we'll let GenericPathRoute to do the heavy lifting of determining which Controller and Action to call and with what parameters. The figure below shows the GenericUrlRouteProvider class (found in Nop.Web.Infrastructure.GenericUrlRouteProvider.cs) doing exactly this job. See the lines around the MapGenericPathRoute() method. The MapGenericPathRoute() method can be found in Nop.Web.Framework.Seo.GenericPathRouteExtensions.cs.

GenericUrlRouteProvider doing the mapping between ID-less URLs and GenericPathRoute class

Conclusion - There is Actually No Magic in nopCommerce ID-less URLs

Yeap, the whole architecture in nopCommerce ID-less URLs is pretty clever, but there is really no magic in it. To recap, here are what make up of the ID-less URLs architecture:

  • UrlRecord database table - to store the mapping between a slug and the actual entity
  • GenericPathRoute class - to map a slug with the actual entity with the help of UrlRecord, thereby providing the RouteData to MVC's routing engine
  • GenericUrlRouteProvider class - to tell MVC's routing engine to let GenericPathRoute class handle ID-less URLs

Hope this explains the issue! Have any other topics that you want explained? Let me know in the comments, or better yet, use the UserVoice feedback widget at the right side to tell me your ideas! :)

Leave your comment
*
Only registered users can leave comments.
Comments
6/20/2013 8:31 AM
Any reason why these could not be generated on the fly, instead of messing around with the UrlRecord table?  I'm using 2.80.
6/20/2013 11:03 AM
Hi Michael,

They are generated on the fly (when you click save for the first time) and then being saved in the DB. In this way, the admin can change the URL any time in the future (will also gets saved in the DB).
9/21/2013 1:10 AM
Woon,

I'm trying to add the product's category in the URL. I've tried to modify GenericUrlRouteProvider.cs to the following :

routes.MapLocalizedRoute("Product",
                                           "{category}/{SeName}",
                                           new {controller = "Catalog", action = "Product"},
                                           new[] {"Nop.Web.Controllers"});

and building my URLs this way : @Url.RouteUrl("Product", new { SeName = product.SeName, category = Model.Name.Replace(" ","-").ToLower() })"

but I get the following error : The parameters dictionary contains a null entry for parameter 'productId' of non-nullable type 'System.Int32' for method 'System.Web.Mvc.ActionResult Product(Int32)' in 'Nop.Web.Controllers.CatalogController'.

Somehow the product ID isn't going through anymore, and I've no idea why.

Any ideas?

Thanks!

Leo
9/21/2013 4:48 AM
Ok I figured it out. I had to implement GetRouteData() in LocalizedRoute.cs, the same way it was implemented in GenericPathRoute.cs. This lead to the homepage not working anymore, but it was easy to fix it.
1/3/2014 1:59 AM
I do not understand why nop would not want to have a continued url structure to follow categories (i.e www.xyz.com/cat-name/subcat-name/product-name).  Any insight on how to achieve this URL structure?

Also, I have issues when I have multiple sub-categories with the same name (shorts > basketball and shoes > basketball) nop changes the category "basketball" to "basketball-2" because it is used multiple times even though it is a sub category.  Frustrated. Any insight?
2/20/2014 11:32 PM
HI Woon,
great article on expalining how the URL work in nopCommerce. Can you give a bit more detailed intructions on how to actually make modifications to the URL structure? I need to add the categoryName to the URL for the product Urls such as www.mysite.com/categoryName/productName.

I would really appreciate your help.

Thanks,

Kelly
2/20/2014 11:55 PM
Hi Kelly,

Perhaps I'll write a new article on the issue you requested. Stay tuned! :)
3/17/2014 1:53 AM
Hi Woon,

who calls GetRouteData() ? is MVC itself? if so how you hook up this method to MVC?
many thanks
Claudio
3/17/2014 2:05 AM
Hi Claudio,

GenericPathRoute inherits LocalizedRoute, which in turn inherits MVC's System.Web.Routing.Route. Does this answer your question? :)
3/17/2014 2:17 AM
yeah , makes sense now, I didn't realized that this method is overriden from System.Web.Routing.Route
many thanks Woon!!!
3/17/2014 2:25 AM
No problem Claudio! :)