Use ASP.NET 4 URL Routing in nopCommerce

Note: in this discussion the nopCommerce version is 1.90 and it addresses only the product category page, not the product details page. In 1.90 the old Manager classes were re-named to Service classes, but beside such naming change, most of the inside logics remain intact (as far as this topic is concerned).

nopCommerce by default uses the UrlRewriting.NET solution and it actually works wonderfully. The only thing that I don’t like about was having to include the CategoryID in the URL. For instance, if URLRewriting is turned on (you can do so in Admin>Global Settings>SEO), you get ~/category/{id}-{sename}.aspx as your URL (Or whatever format you prefer, just that you can’t avoid having the {id} in the URL, which seems annoying to me.)

We can, however, construct a nice, SEO-friendly URL step by step:

1. Add the UrlRoutingModule to web.config

2. Disable the UrlRewriteModule from web.config

3. Make sure runAllManagedModulesForAllRequests is set to true

Here’s the <system.webServer> node in web.config highlighting steps 1 – 3

<system.webServer>
    <validation validateIntegratedModeConfiguration="false" />
    <modules runAllManagedModulesForAllRequests="true">
        <remove name="NopCommerceFilter" />
        <remove name="UrlRewriteModule" />
        <add name="MembershipHttpModule" preCondition="managedHandler" type="NopSolutions.NopCommerce.BusinessLogic.Profile.MembershipHttpModule, Nop.BusinessLogic" />            
        <!--<add name="UrlRewriteModule" preCondition="managedHandler" type="UrlRewritingNet.Web.UrlRewriteModule, UrlRewritingNet.UrlRewriter" />-->
        <add name="BlacklistHttpModule" preCondition="managedHandler" type="NopSolutions.NopCommerce.BusinessLogic.Security.BlacklistHttpModule, Nop.BusinessLogic" />
        <add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
    </modules>
    <handlers>
        <add name="ChartImageHandler" preCondition="integratedMode" verb="GET,HEAD,POST" path="ChartImg.axd" type="System.Web.UI.DataVisualization.Charting.ChartHttpHandler, System.Web.DataVisualization, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
        <add name="PricelistHandler" verb="*" path="pricelist.csv" preCondition="integratedMode" type="NopSolutions.NopCommerce.BusinessLogic.ExportImport.PricelistHandler, Nop.BusinessLogic" />
        <add name="UrlRoutingHandler" preCondition="integratedMode" verb="*" path="UrlRouting.axd" type="System.Web.HttpForbiddenHandler,System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
</handlers>
</system.webServer>

4. Update how the category URL is constructed in  NopSolutions.NopCommerce.BusinessLogic.SEO.GetCategoryUrl(Category category)

string url2 = "{0}shop/{1}";
stringurl = string.Format(url2, CommonHelper.GetStoreLocation(), seName);
returnurl.ToLowerInvariant();

which means Enabling URL Rewriting or not will yield the same result (We do want to enforce the SEO-friendly URL). Note that the CategoryId is no longer in the URL. shop in the example above is just the hardcoded piece of the path so it could be anything.

5.  Make sure that the web app has reference to System.Web.Routing assembly

6. Add the routing map to Global.asax so now we can have a URL that looks something like  http://yourstore.com/shop/electronics and the application will know to redirect it to Category.aspx

void RegisterRoutes(System.Web.Routing.RouteCollection routes)
{
    routes.MapPageRoute(
         "category-browse",       // Route name 
         "shop/{SEName}",         // URL  
         "~/Category.aspx" // Web Forms page   
    );
}
void Application_Start(object sender, EventArgs e)
{
    RegisterRoutes(System.Web.Routing.RouteTable.Routes);    …

7. Add a method called GetCategoryBySEName(string seName) to ICategoryService & CategoryService. We need this because the URL no longer contains the CategoryId so we will need to retrieve the Category using SEName.

/// <summary>
/// Gets a category
/// </summary>
/// <param name="seName">Search engine friendly name of the category</param>
/// <returns>Category</returns>
public Category GetCategoryBySEName(string seName)
{
    if (string.IsNullOrWhiteSpace(seName))
    {
        return null;
    }

    string key = string.Format(CATEGORIES_BY_SENAME, seName);
    object obj2 = _cacheManager.Get(key);
    if (this.CategoriesCacheEnabled && (obj2 != null))
    {
        return (Category)obj2;
    }

    bool showHidden = NopContext.Current.IsAdmin;

    var query = from c in _context.Categories
                where c.SEName == seName
                select c;
    Category category = query.FirstOrDefault();

    //filter by access control list (public store)
    if (category != null && !showHidden && IsCategoryAccessDenied(category))
    {
        category = null;
    }
    if (this.CategoriesCacheEnabled)
    {
        _cacheManager.Add(key, category);
    }

    return category;
}

8. Update in Category.aspx to retrieve the category using the nice URL. ~/shop/electronics

The first line of the CreateChildControlTree() method it calls CategoryService’s GetCategoryById. Now we need to use GetCategoryBySEName that we just added above.

private void CreateChildControlsTree()
{
    category = this.CategoryService.GetCategoryBySEName(this.SEName);

To do that, we need to change the SEName property to get the value from RouteData, not query string.

public string SEName
{
    get
    {
        return Page.RouteData.Values["SEName"] as string;
    }
}

9. Update the same in your product templates as well. If there is no SEName property in the product templates, add the one like in Step 8. In the method BindData() is where it references a lot to the category, so I’ve created a private variable to hold the Category object that I will retrieve using SEName,

public partial class ProductsInGrid: BaseNopFrontendUserControl
{
    private Category _category = null;
protected void BindData()
{
    _category = this.CategoryService.GetCategoryBySEName(this.SEName);

After you complete all 9 steps here, you should be all set and have a very nice SEO-friendly URL for your nopCommerce store.

By Bryan Xu

Tagged with:
Posted in eCommerce

Leave a Reply