How to override nopCommerce generic route from a plugin

How to override nopCommerce generic route from a plugin

nopCommerce is the leading ASP.NET-based open-source eCommerce platform. It is a solution with comprehensive features(link: https://www.nopcommerce.com/featurelist.aspx) that applies to all types of users from new online businesses to already established e-commerce businesses. The platform is preferred by e-commerce experts globally who are thinking of growing bigger at a faster pace to deliver the best e-commerce experience to their customers.  

Reference: https://www.nopcommerce.com/whatisnopcommerce.aspx

nopCommerce  generic route:

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. If it eventually finds any active existing record, it then provides additional values to the RouteData such as the Controller, the Action and the ID etc.

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.

This is a way of NopCommerce ID-Less URL Structure.

For more details Please visit ==>https://www.pronopcommerce.com/nopcommerce-id-less-url-structure-demystified

 

How to override a generic route from a plugin

We have to do the following approach and there's no easier way to do it:

1. Add a new class inherited from GenericPathRoute. Override it "GetRouteData" method (put your custom logic there - specify custom controller and action method name)
2. Create a new IRouteProvider implementation in your plugin. Just ensure that it's invoked after the \Infrastructure\GenericUrlRouteProvider.cs (use "Priority" property)


3. Remove the "GenericUrl" route name in this IRouteProvider implementation. And add a new custom one. But do not use the default "MapGenericPathRoute" extension method. Create a new one that won't use the "GenericPathRoute" class but use your new custom one (created in step 1)

Reference===>https://www.nopcommerce.com/boards/t/41173/how-to-override-genericpathroutegetroutedata-in-plugin.aspx#164373

 

I am going to give you an example from one of my implemented plugins,

Add a new class inherited from GenericPathRoute. Override it "GetRouteData":


using System;


using System.Web;
using System.Web.Routing;
using Nop.Core;
using Nop.Core.Data;
using Nop.Core.Infrastructure;
using Nop.Services.Events;
using Nop.Services.Seo;
using Nop.Web.Framework.Seo;


namespace Nop.Plugin.Sohel.ManageGiftCertificates.Infrastructure.SEO
{
/// <summary>
/// Provides properties and methods for defining a SEO friendly route, and for getting information about the route.
/// </summary>
public partial class CustomGenericPathRoute : Nop.Web.Framework.Seo.GenericPathRoute
{
#region Constructors


/// <summary>
/// Initializes a new instance of the System.Web.Routing.Route class, using the specified URL pattern and handler class.
/// </summary>
/// <param name="url">The URL pattern for the route.</param>
/// <param name="routeHandler">The object that processes requests for the route.</param>
public CustomGenericPathRoute(string url, IRouteHandler routeHandler)
: base(url, routeHandler)
{
}


/// <summary>
/// Initializes a new instance of the System.Web.Routing.Route class, using the specified URL pattern, handler class and default parameter values.
/// </summary>
/// <param name="url">The URL pattern for the route.</param>
/// <param name="defaults">The values to use if the URL does not contain all the parameters.</param>
/// <param name="routeHandler">The object that processes requests for the route.</param>
public CustomGenericPathRoute(string url, RouteValueDictionary defaults, IRouteHandler routeHandler)
: base(url, defaults, routeHandler)
{
}


/// <summary>
/// Initializes a new instance of the System.Web.Routing.Route class, using the specified URL pattern, handler class, default parameter values and constraints.
/// </summary>
/// <param name="url">The URL pattern for the route.</param>
/// <param name="defaults">The values to use if the URL does not contain all the parameters.</param>
/// <param name="constraints">A regular expression that specifies valid values for a URL parameter.</param>
/// <param name="routeHandler">The object that processes requests for the route.</param>
public CustomGenericPathRoute(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, IRouteHandler routeHandler)
: base(url, defaults, constraints, routeHandler)
{
}


/// <summary>
/// Initializes a new instance of the System.Web.Routing.Route class, using the specified URL pattern, handler class, default parameter values, 
/// constraints,and custom values.
/// </summary>
/// <param name="url">The URL pattern for the route.</param>
/// <param name="defaults">The values to use if the URL does not contain all the parameters.</param>
/// <param name="constraints">A regular expression that specifies valid values for a URL parameter.</param>
/// <param name="dataTokens">Custom values that are passed to the route handler, but which are not used to determine whether the route matches a specific URL pattern. The route handler might need these values to process the request.</param>
/// <param name="routeHandler">The object that processes requests for the route.</param>
public CustomGenericPathRoute(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, RouteValueDictionary dataTokens, IRouteHandler routeHandler)
: base(url, defaults, constraints, dataTokens, routeHandler)
{
}


#endregion


#region Methods


/// <summary>
/// Returns information about the requested route.
/// </summary>
/// <param name="httpContext">An object that encapsulates information about the HTTP request.</param>
/// <returns>
/// An object that contains the values from the route definition.
/// </returns>
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var NAME_SPACE = "Nop.Web.Controllers";
RouteData data = base.GetRouteData(httpContext);
if (data != null && DataSettingsHelper.DatabaseIsInstalled())
{
var urlRecordService = EngineContext.Current.Resolve<IUrlRecordService>();
var slug = data.Values["generic_se_name"] as string;
//performance optimization.
//we load a cached verion here. it reduces number of SQL requests for each page load
var urlRecord = urlRecordService.GetBySlugCached(slug);
//comment the line above and uncomment the line below in order to disable this performance "workaround"
//var urlRecord = urlRecordService.GetBySlug(slug);
if (urlRecord == null)
{
//no URL record found


//var webHelper = EngineContext.Current.Resolve<IWebHelper>();
//var response = httpContext.Response;
//response.Status = "302 Found";
//response.RedirectLocation = webHelper.GetStoreLocation(false);
//response.End();
//return null;


data.Values["controller"] = "Common";
data.Values["action"] = "PageNotFound";
data.DataTokens["Namespaces"] = new[] { NAME_SPACE };
return data;
}
//ensre that URL record is active
if (!urlRecord.IsActive)
{
//URL record is not active. let's find the latest one
var activeSlug = urlRecordService.GetActiveSlug(urlRecord.EntityId, urlRecord.EntityName, urlRecord.LanguageId);
if (string.IsNullOrWhiteSpace(activeSlug))
{
//no active slug found


//var webHelper = EngineContext.Current.Resolve<IWebHelper>();
//var response = httpContext.Response;
//response.Status = "302 Found";
//response.RedirectLocation = webHelper.GetStoreLocation(false);
//response.End();
//return null;


data.Values["controller"] = "Common";
data.Values["action"] = "PageNotFound";
data.DataTokens["Namespaces"] = new[] { NAME_SPACE };
return data;
}


//the active one is found
var webHelper = EngineContext.Current.Resolve<IWebHelper>();
var response = httpContext.Response;
response.Status = "301 Moved Permanently";
response.RedirectLocation = string.Format("{0}{1}", webHelper.GetStoreLocation(false), activeSlug);
response.End();
return null;
}


//ensure that the slug is the same for the current language
//otherwise, it can cause some issues when customers choose a new language but a slug stays the same
var workContext = EngineContext.Current.Resolve<IWorkContext>();
var slugForCurrentLanguage = SeoExtensions.GetSeName(urlRecord.EntityId, urlRecord.EntityName, workContext.WorkingLanguage.Id);
if (!String.IsNullOrEmpty(slugForCurrentLanguage) &&
!slugForCurrentLanguage.Equals(slug, StringComparison.InvariantCultureIgnoreCase))
{
//we should make not null or "" validation above because some entities does not have SeName for standard (ID=0) language (e.g. news, blog posts)
var webHelper = EngineContext.Current.Resolve<IWebHelper>();
var response = httpContext.Response;
//response.Status = "302 Found";
response.Status = "302 Moved Temporarily";
response.RedirectLocation = string.Format("{0}{1}", webHelper.GetStoreLocation(false), slugForCurrentLanguage);
response.End();
return null;
}
#region 
var productService = EngineContext.Current.Resolve<Nop.Services.Catalog.IProductService>();
#endregion
//process URL
switch (urlRecord.EntityName.ToLowerInvariant())
{
case "product":
{
data.Values["controller"] = "Product"; //Write here your plugin Controller name
data.Values["action"] = "ProductDetails"; //Write here your plugin Action name
data.Values["productid"] = urlRecord.EntityId;
data.Values["SeName"] = urlRecord.Slug;


data.DataTokens["Namespaces"] = new[] { "Nop.Plugin.Sohel.ManageGiftCertificates.Controllers" }; // write here your own Plugin Controller Namespaces


}
break;
case "category":
{
data.Values["controller"] = "Catalog";
data.Values["action"] = "Category";
data.Values["categoryid"] = urlRecord.EntityId;
data.Values["SeName"] = urlRecord.Slug; 
}
break;
case "manufacturer":
{
data.Values["controller"] = "Catalog";
data.Values["action"] = "Manufacturer";
data.Values["manufacturerid"] = urlRecord.EntityId;
data.Values["SeName"] = urlRecord.Slug;
data.DataTokens["Namespaces"] = new[] { NAME_SPACE };
}
break;
case "vendor":
{
data.Values["controller"] = "Catalog";
data.Values["action"] = "Vendor";
data.Values["vendorid"] = urlRecord.EntityId;
data.Values["SeName"] = urlRecord.Slug;
data.DataTokens["Namespaces"] = new[] { NAME_SPACE };
}
break;
case "newsitem":
{
data.Values["controller"] = "News";
data.Values["action"] = "NewsItem";
data.Values["newsItemId"] = urlRecord.EntityId;
data.Values["SeName"] = urlRecord.Slug;
data.DataTokens["Namespaces"] = new[] { NAME_SPACE };
}
break;
case "blogpost":
{
data.Values["controller"] = "Blog";
data.Values["action"] = "BlogPost";
data.Values["blogPostId"] = urlRecord.EntityId;
data.Values["SeName"] = urlRecord.Slug;
data.DataTokens["Namespaces"] = new[] { NAME_SPACE };
}
break;
case "topic":
{
data.Values["controller"] = "Topic";
data.Values["action"] = "TopicDetails";
data.Values["topicId"] = urlRecord.EntityId;
data.Values["SeName"] = urlRecord.Slug;
data.DataTokens["Namespaces"] = new[] { NAME_SPACE };
}
break;
default:
{
//no record found


//generate an event this way developers could insert their own types
EngineContext.Current.Resolve<IEventPublisher>()
.Publish(new CustomUrlRecordEntityNameRequested(data, urlRecord));
}
break;
}
}
return data;
}


#endregion
}
}

 

Create a new IRouteProvider implementation


using System.Web.Routing;


using Nop.Plugin.Sohel.ManageGiftCertificates.Infrastructure.SEO;
using Nop.Web.Framework.Mvc.Routes;


namespace Nop.Plugin.Sohel.ManageGiftCertificates.Infrastructure
{
public partial class GenericUrlRouteProvider : IRouteProvider
{
public void RegisterRoutes(RouteCollection routes)
{


//generic URLs
routes.CustomMapGenericPathRoute("CustomGiftCirtificateGenericUrl",
"{generic_se_name}",
new { controller = "Common", action = "GenericUrl" },
new[] { "Nop.Web.Controllers" });

}


public int Priority
{
get
{
//it should be the last route
//we do not set it to -int.MaxValue so it could be overridden (if required)
return -99;
}
}
}
}

  Create a new GenericPathRouteExtensions:



using System;
using System.Web.Mvc;
using System.Web.Routing;


namespace Nop.Plugin.Sohel.ManageGiftCertificates.Infrastructure.SEO
{
public static class CustomGenericPathRouteExtensions
{
//Override for localized route
public static Route CustomMapGenericPathRoute(this RouteCollection routes, string name, string url)
{
return CustomMapGenericPathRoute(routes, name, url, null /* defaults */, (object)null /* constraints */);
}
public static Route CustomMapGenericPathRoute(this RouteCollection routes, string name, string url, object defaults)
{
return CustomMapGenericPathRoute(routes, name, url, defaults, (object)null /* constraints */);
}
public static Route CustomMapGenericPathRoute(this RouteCollection routes, string name, string url, object defaults, object constraints)
{
return CustomMapGenericPathRoute(routes, name, url, defaults, constraints, null /* namespaces */);
}
public static Route CustomMapGenericPathRoute(this RouteCollection routes, string name, string url, string[] namespaces)
{
return CustomMapGenericPathRoute(routes, name, url, null /* defaults */, null /* constraints */, namespaces);
}
public static Route CustomMapGenericPathRoute(this RouteCollection routes, string name, string url, object defaults, string[] namespaces)
{
return CustomMapGenericPathRoute(routes, name, url, defaults, null /* constraints */, namespaces);
}
public static Route CustomMapGenericPathRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces)
{
if (routes == null)
{
throw new ArgumentNullException("routes");
}
if (url == null)
{
throw new ArgumentNullException("url");
}


var route = new CustomGenericPathRoute(url, new MvcRouteHandler())
{
Defaults = new RouteValueDictionary(defaults),
Constraints = new RouteValueDictionary(constraints),
DataTokens = new RouteValueDictionary()
};


if ((namespaces != null) && (namespaces.Length > 0))
{
route.DataTokens["Namespaces"] = namespaces;
}


routes.Add(name, route);


return route;
}
}
}

Hope it will help you override the Generic route from a plugin. Enjoy Coding!!!!

 

Content contributor: Md Minul Islam Sohel
Designation: Software Developer (nopMVP)
Skype ID: sohel.bs23

Leave your comment
Only registered users can leave comments.
card icon