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.
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:
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.
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.
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! :)