For nopcommerce4.0 you need to comment out some lines of code from the bellow "RouteAsync" method and also need to uncomment out some code from the same method.
Please change the namespace by your plugin namespace. I put these three class under "PluginRoute" folder of the plugin.
Step1:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Nop.Core.Infrastructure;
using Nop.Web.Framework.Localization;
using Nop.Web.Framework.Mvc.Routing;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
/// <summary>
/// Please Replace Bellow NameSpace By Your Plugin
/// </summary>
namespace Nop.Plugin.Widgets.NivoSlider.PluginRoute
{
public class PGRoute : IRouteProvider
{
public int Priority => int.MaxValue;
public void RegisterRoutes(IRouteBuilder routeBuilder)
{
routeBuilder.CustomMapGenericPathRoute("CustomProductDetailGenericRoute", "{SeName}",
new { controller = "ProductPG", action = "ProductDetails" });
}
}
}
Step2:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Template;
using Nop.Core;
using Nop.Core.Domain.Localization;
using Nop.Core.Infrastructure;
using Nop.Services.Events;
using Nop.Services.Seo;
using Nop.Web.Framework.Localization;
using Nop.Web.Framework.Seo;
using Nop.Core.Data;
/// <summary>
/// Please Replace Bellow NameSpace By Your Plugin
/// </summary>
///
namespace Nop.Plugin.Widgets.NivoSlider.PluginRoute
{
/// <summary>
/// Provides properties and methods for defining a SEO friendly route, and for getting information about the route.
/// </summary>
public class CustomGenericPathRoute : GenericPathRoute
{
#region Fields
private readonly IRouter _target;
#endregion
#region Ctor
/// <summary>
/// Ctor
/// </summary>
/// <param name="target">Target</param>
/// <param name="routeName">Route name</param>
/// <param name="routeTemplate">Route remplate</param>
/// <param name="defaults">Defaults</param>
/// <param name="constraints">Constraints</param>
/// <param name="dataTokens">Data tokens</param>
/// <param name="inlineConstraintResolver">Inline constraint resolver</param>
public CustomGenericPathRoute(IRouter target, string routeName, string routeTemplate, RouteValueDictionary defaults,
IDictionary<string, object> constraints, RouteValueDictionary dataTokens, IInlineConstraintResolver inlineConstraintResolver)
: base(target, routeName, routeTemplate, defaults, constraints, dataTokens, inlineConstraintResolver)
{
_target = target ?? throw new ArgumentNullException(nameof(target));
}
#endregion
protected new RouteValueDictionary GetRouteValues(RouteContext context)
{
//remove language code from the path if it's localized URL
var path = context.HttpContext.Request.Path.Value;
if (this.SeoFriendlyUrlsForLanguagesEnabled && path.IsLocalizedUrl(context.HttpContext.Request.PathBase, false, out Language _))
path = path.RemoveLanguageSeoCodeFromUrl(context.HttpContext.Request.PathBase, false);
//parse route data
var routeValues = new RouteValueDictionary(this.ParsedTemplate.Parameters
.Where(parameter => parameter.DefaultValue != null)
.ToDictionary(parameter => parameter.Name, parameter => parameter.DefaultValue));
var matcher = new TemplateMatcher(this.ParsedTemplate, routeValues);
matcher.TryMatch(path, routeValues);
return routeValues;
}
#region Methods
/// <summary>
/// Route request to the particular action
/// </summary>
/// <param name="context">A route context object</param>
/// <returns>Task of the routing</returns>
///
public override Task RouteAsync(RouteContext context)
{
//Uncomment the bellow two lines for nopcommerce4.0
//if (!DataSettingsHelper.DatabaseIsInstalled())
// return Task.CompletedTask;
//comment the bellow two lines for nopcommerce4.0
if (!DataSettingsManager.DatabaseIsInstalled)
return Task.CompletedTask;
//try to get slug from the route data
var routeValues = GetRouteValues(context);
if (!routeValues.TryGetValue("SeName", out object slugValue) || string.IsNullOrEmpty(slugValue as string))
return Task.CompletedTask;
var slug = slugValue as string;
//performance optimization, we load a cached verion here. It reduces number of SQL requests for each page load
var urlRecordService = EngineContext.Current.Resolve<IUrlRecordService>();
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);
//no URL record found
if (urlRecord == null)
return Task.CompletedTask;
//virtual directory path
var pathBase = context.HttpContext.Request.PathBase;
//if URL record is not active let's find the latest one
if (!urlRecord.IsActive)
{
var activeSlug = urlRecordService.GetActiveSlug(urlRecord.EntityId, urlRecord.EntityName, urlRecord.LanguageId);
if (string.IsNullOrEmpty(activeSlug))
return Task.CompletedTask;
//redirect to active slug if found
var redirectionRouteData = new RouteData(context.RouteData);
redirectionRouteData.Values["controller"] = "Common";
redirectionRouteData.Values["action"] = "InternalRedirect";
redirectionRouteData.Values["url"] = $"{pathBase}/{activeSlug}{context.HttpContext.Request.QueryString}";
redirectionRouteData.Values["permanentRedirect"] = true;
context.HttpContext.Items["nop.RedirectFromGenericPathRoute"] = true;
context.RouteData = redirectionRouteData;
return _target.RouteAsync(context);
}
//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
//Uncomment the bellow three lines for nopcommerce4.0
//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))
//Comment out the bellow two lines for nopcommerce 4.0
var slugForCurrentLanguage = urlRecordService.GetSeName(urlRecord.EntityId, urlRecord.EntityName);
if (!string.IsNullOrEmpty(slugForCurrentLanguage) && !slugForCurrentLanguage.Equals(slug, StringComparison.InvariantCultureIgnoreCase))
{
//we should make validation above because some entities does not have SeName for standard (Id = 0) language (e.g. news, blog posts)
//redirect to the page for current language
var redirectionRouteData = new RouteData(context.RouteData);
redirectionRouteData.Values["controller"] = "Common";
redirectionRouteData.Values["action"] = "InternalRedirect";
redirectionRouteData.Values["url"] = $"{pathBase}/{slugForCurrentLanguage}{context.HttpContext.Request.QueryString}";
redirectionRouteData.Values["permanentRedirect"] = false;
context.HttpContext.Items["nop.RedirectFromGenericPathRoute"] = true;
context.RouteData = redirectionRouteData;
return _target.RouteAsync(context);
}
//since we are here, all is ok with the slug, so process URL
var currentRouteData = new RouteData(context.RouteData);
switch (urlRecord.EntityName.ToLowerInvariant())
{
case "product":
currentRouteData.Values["controller"] = "ProductPG";//replace the controller name by your one
currentRouteData.Values["action"] = "ProductDetails";//replace the action name by your one
currentRouteData.Values["productid"] = urlRecord.EntityId;
currentRouteData.Values["SeName"] = urlRecord.Slug;
break;
case "category":
currentRouteData.Values["controller"] = "Catalog";
currentRouteData.Values["action"] = "Category";
currentRouteData.Values["categoryid"] = urlRecord.EntityId;
currentRouteData.Values["SeName"] = urlRecord.Slug;
break;
case "manufacturer":
currentRouteData.Values["controller"] = "Catalog";
currentRouteData.Values["action"] = "Manufacturer";
currentRouteData.Values["manufacturerid"] = urlRecord.EntityId;
currentRouteData.Values["SeName"] = urlRecord.Slug;
break;
case "vendor":
currentRouteData.Values["controller"] = "Catalog";
currentRouteData.Values["action"] = "Vendor";
currentRouteData.Values["vendorid"] = urlRecord.EntityId;
currentRouteData.Values["SeName"] = urlRecord.Slug;
break;
case "newsitem":
currentRouteData.Values["controller"] = "News";
currentRouteData.Values["action"] = "NewsItem";
currentRouteData.Values["newsItemId"] = urlRecord.EntityId;
currentRouteData.Values["SeName"] = urlRecord.Slug;
break;
case "blogpost":
currentRouteData.Values["controller"] = "Blog";
currentRouteData.Values["action"] = "BlogPost";
currentRouteData.Values["blogPostId"] = urlRecord.EntityId;
currentRouteData.Values["SeName"] = urlRecord.Slug;
break;
case "topic":
currentRouteData.Values["controller"] = "Topic";
currentRouteData.Values["action"] = "TopicDetails";
currentRouteData.Values["topicId"] = urlRecord.EntityId;
currentRouteData.Values["SeName"] = urlRecord.Slug;
break;
default:
//Uncomment the bellow line
//EngineContext.Current.Resolve<IEventPublisher>().Publish(new CustomUrlRecordEntityNameRequested(currentRouteData, urlRecord));
//Comment out bellow line
EngineContext.Current.Resolve<IEventPublisher>().Publish(new CustomUrlRecordEntityNameRequestedEvent(currentRouteData, urlRecord));
break;
}
context.RouteData = currentRouteData;
//route request
return _target.RouteAsync(context);
}
#endregion
}
}
Step3:
using System;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
/// <summary>
/// Please Replace Bellow NameSpace By Your Plugin
/// </summary>
namespace Nop.Plugin.Widgets.NivoSlider.PluginRoute
{
/// <summary>
/// Represents extensions of GenericPathRoute
/// </summary>
public static class CustomGenericPathRouteExtensions
{
/// <summary>
/// Adds a route to the route builder with the specified name and template
/// </summary>
/// <param name="routeBuilder">The route builder to add the route to</param>
/// <param name="name">The name of the route</param>
/// <param name="template">The URL pattern of the route</param>
/// <returns>Route builder</returns>
public static IRouteBuilder CustomMapGenericPathRoute(this IRouteBuilder routeBuilder, string name, string template)
{
return CustomMapGenericPathRoute(routeBuilder, name, template, defaults: null);
}
/// <summary>
/// Adds a route to the route builder with the specified name, template, and default values
/// </summary>
/// <param name="routeBuilder">The route builder to add the route to</param>
/// <param name="name">The name of the route</param>
/// <param name="template">The URL pattern of the route</param>
/// <param name="defaults">An object that contains default values for route parameters.
/// The object's properties represent the names and values of the default values</param>
/// <returns>Route builder</returns>
public static IRouteBuilder CustomMapGenericPathRoute(this IRouteBuilder routeBuilder, string name, string template, object defaults)
{
return CustomMapGenericPathRoute(routeBuilder, name, template, defaults, constraints: null);
}
/// <summary>
/// Adds a route to the route builder with the specified name, template, default values, and constraints.
/// </summary>
/// <param name="routeBuilder">The route builder to add the route to</param>
/// <param name="name">The name of the route</param>
/// <param name="template">The URL pattern of the route</param>
/// <param name="defaults"> An object that contains default values for route parameters.
/// The object's properties represent the names and values of the default values</param>
/// <param name="constraints">An object that contains constraints for the route.
/// The object's properties represent the names and values of the constraints</param>
/// <returns>Route builder</returns>
public static IRouteBuilder CustomMapGenericPathRoute(this IRouteBuilder routeBuilder,
string name, string template, object defaults, object constraints)
{
return CustomMapGenericPathRoute(routeBuilder, name, template, defaults, constraints, dataTokens: null);
}
/// <summary>
/// Adds a route to the route builder with the specified name, template, default values, constraints and data tokens.
/// </summary>
/// <param name="routeBuilder">The route builder to add the route to</param>
/// <param name="name">The name of the route</param>
/// <param name="template">The URL pattern of the route</param>
/// <param name="defaults"> An object that contains default values for route parameters.
/// The object's properties represent the names and values of the default values</param>
/// <param name="constraints">An object that contains constraints for the route.
/// The object's properties represent the names and values of the constraints</param>
/// <param name="dataTokens">An object that contains data tokens for the route.
/// The object's properties represent the names and values of the data tokens</param>
/// <returns>Route builder</returns>
public static IRouteBuilder CustomMapGenericPathRoute(this IRouteBuilder routeBuilder,
string name, string template, object defaults, object constraints, object dataTokens)
{
if (routeBuilder.DefaultHandler == null)
throw new ArgumentNullException(nameof(routeBuilder));
//get registered InlineConstraintResolver
var inlineConstraintResolver = routeBuilder.ServiceProvider.GetRequiredService<IInlineConstraintResolver>();
//create new generic route
routeBuilder.Routes.Add(new CustomGenericPathRoute(routeBuilder.DefaultHandler, name, template,
new RouteValueDictionary(defaults), new RouteValueDictionary(constraints), new RouteValueDictionary(dataTokens),
inlineConstraintResolver));
return routeBuilder;
}
}
}
Step4:
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Nop.Core;
using Nop.Core.Domain.Catalog;
using Nop.Core.Domain.Customers;
using Nop.Core.Domain.Localization;
using Nop.Core.Domain.Orders;
using Nop.Services.Catalog;
using Nop.Services.Events;
using Nop.Services.Localization;
using Nop.Services.Logging;
using Nop.Services.Messages;
using Nop.Services.Orders;
using Nop.Services.Security;
using Nop.Services.Seo;
using Nop.Services.Stores;
using Nop.Web.Controllers;
using Nop.Web.Framework;
using Nop.Web.Framework.Controllers;
using Nop.Web.Framework.Mvc;
using Nop.Web.Framework.Mvc.Filters;
using Nop.Web.Framework.Mvc.Rss;
using Nop.Web.Framework.Security;
using Nop.Web.Framework.Security.Captcha;
/// <summary>
/// Please Replace Bellow NameSpace By Your Plugin
/// </summary>
namespace Nop.Plugin.Widgets.NivoSlider.Controllers
{
public partial class ProductPGController : BasePublicController
{
#region Product details page
public virtual IActionResult ProductDetails(int productId, int updatecartitemid = 0)
{
// Do you code and return view from your plugin's view folder.
return View("~/Plugins/YourPluginFolderName/Views/CustomeProductDetails.cshtml");
}
#endregion
}
}