1. Inherit the SlugRouteTransformer class and change like below
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Nop.Core.Domain.Localization;
using Nop.Services.Events;
using Nop.Services.Localization;
using Nop.Services.Seo;
using Nop.Web.Framework.Events;
using Nop.Web.Framework.Mvc.Routing;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Nop.Plugin.Widgets.NivoSlider.Infrastructure
{
public class GenericRoutePlugin : SlugRouteTransformer
{
#region Fields
private readonly IEventPublisher _eventPublisher;
private readonly ILanguageService _languageService;
private readonly IUrlRecordService _urlRecordService;
private readonly LocalizationSettings _localizationSettings;
#endregion
#region Ctor
public GenericRoutePlugin(IEventPublisher eventPublisher,
ILanguageService languageService,
IUrlRecordService urlRecordService,
LocalizationSettings localizationSettings):base(eventPublisher, languageService, urlRecordService, localizationSettings)
{
_eventPublisher = eventPublisher;
_languageService = languageService;
_urlRecordService = urlRecordService;
_localizationSettings = localizationSettings;
}
#endregion
public override ValueTask<RouteValueDictionary> TransformAsync(HttpContext httpContext, RouteValueDictionary values)
{
if (values == null)
return new ValueTask<RouteValueDictionary>(values);
if (!values.TryGetValue("SeName", out var slugValue) || string.IsNullOrEmpty(slugValue as string))
return new ValueTask<RouteValueDictionary>(values);
var slug = slugValue as string;
var urlRecord = _urlRecordService.GetBySlug(slug);
//no URL record found
if (urlRecord == null)
return new ValueTask<RouteValueDictionary>(values);
//virtual directory path
var pathBase = 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 new ValueTask<RouteValueDictionary>(values);
//redirect to active slug if found
values[NopPathRouteDefaults.ControllerFieldKey] = "Common";
values[NopPathRouteDefaults.ActionFieldKey] = "InternalRedirect";
values[NopPathRouteDefaults.UrlFieldKey] = $"{pathBase}/{activeSlug}{httpContext.Request.QueryString}";
values[NopPathRouteDefaults.PermanentRedirectFieldKey] = true;
httpContext.Items["nop.RedirectFromGenericPathRoute"] = true;
return new ValueTask<RouteValueDictionary>(values);
}
//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
if (_localizationSettings.SeoFriendlyUrlsForLanguagesEnabled)
{
var urllanguage = values["language"];
if (urllanguage != null && !string.IsNullOrEmpty(urllanguage.ToString()))
{
var language = _languageService.GetAllLanguages().FirstOrDefault(x => x.UniqueSeoCode.ToLowerInvariant() == urllanguage.ToString().ToLowerInvariant());
if (language == null)
language = _languageService.GetAllLanguages().FirstOrDefault();
var slugForCurrentLanguage = _urlRecordService.GetActiveSlug(urlRecord.EntityId, urlRecord.EntityName, language.Id);
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
values[NopPathRouteDefaults.ControllerFieldKey] = "Common";
values[NopPathRouteDefaults.ActionFieldKey] = "InternalRedirect";
values[NopPathRouteDefaults.UrlFieldKey] = $"{pathBase}/{language.UniqueSeoCode}/{slugForCurrentLanguage}{httpContext.Request.QueryString}";
values[NopPathRouteDefaults.PermanentRedirectFieldKey] = false;
httpContext.Items["nop.RedirectFromGenericPathRoute"] = true;
return new ValueTask<RouteValueDictionary>(values);
}
}
}
//since we are here, all is ok with the slug, so process URL
switch (urlRecord.EntityName.ToLowerInvariant())
{
case "product":
values[NopPathRouteDefaults.ControllerFieldKey] = "Product";
values[NopPathRouteDefaults.ActionFieldKey] = "ProductDetailsFromPlugin"; //Your Plugin Method
values[NopPathRouteDefaults.ProductIdFieldKey] = urlRecord.EntityId;
values[NopPathRouteDefaults.SeNameFieldKey] = urlRecord.Slug;
values["NameSpace"] = "Nop.Plugin.Widgets.NivoSlider.Controllers";//Your Plugin Controller NameSpace
break;
case "producttag":
values[NopPathRouteDefaults.ControllerFieldKey] = "Catalog";
values[NopPathRouteDefaults.ActionFieldKey] = "ProductsByTag";
values[NopPathRouteDefaults.ProducttagIdFieldKey] = urlRecord.EntityId;
values[NopPathRouteDefaults.SeNameFieldKey] = urlRecord.Slug;
break;
case "category":
values[NopPathRouteDefaults.ControllerFieldKey] = "Catalog";
values[NopPathRouteDefaults.ActionFieldKey] = "Category";
values[NopPathRouteDefaults.CategoryIdFieldKey] = urlRecord.EntityId;
values[NopPathRouteDefaults.SeNameFieldKey] = urlRecord.Slug;
break;
case "manufacturer":
values[NopPathRouteDefaults.ControllerFieldKey] = "Catalog";
values[NopPathRouteDefaults.ActionFieldKey] = "Manufacturer";
values[NopPathRouteDefaults.ManufacturerIdFieldKey] = urlRecord.EntityId;
values[NopPathRouteDefaults.SeNameFieldKey] = urlRecord.Slug;
break;
case "vendor":
values[NopPathRouteDefaults.ControllerFieldKey] = "Catalog";
values[NopPathRouteDefaults.ActionFieldKey] = "Vendor";
values[NopPathRouteDefaults.VendorIdFieldKey] = urlRecord.EntityId;
values[NopPathRouteDefaults.SeNameFieldKey] = urlRecord.Slug;
break;
case "newsitem":
values[NopPathRouteDefaults.ControllerFieldKey] = "News";
values[NopPathRouteDefaults.ActionFieldKey] = "NewsItem";
values[NopPathRouteDefaults.NewsItemIdFieldKey] = urlRecord.EntityId;
values[NopPathRouteDefaults.SeNameFieldKey] = urlRecord.Slug;
break;
case "blogpost":
values[NopPathRouteDefaults.ControllerFieldKey] = "Blog";
values[NopPathRouteDefaults.ActionFieldKey] = "BlogPost";
values[NopPathRouteDefaults.BlogPostIdFieldKey] = urlRecord.EntityId;
values[NopPathRouteDefaults.SeNameFieldKey] = urlRecord.Slug;
break;
case "topic":
values[NopPathRouteDefaults.ControllerFieldKey] = "Topic";
values[NopPathRouteDefaults.ActionFieldKey] = "TopicDetails";
values[NopPathRouteDefaults.TopicIdFieldKey] = urlRecord.EntityId;
values[NopPathRouteDefaults.SeNameFieldKey] = urlRecord.Slug;
break;
default:
//no record found, thus generate an event this way developers could insert their own types
_eventPublisher.Publish(new GenericRoutingEvent(values, urlRecord));
break;
}
return new ValueTask<RouteValueDictionary>(values);
}
}
}
2. Now register the dependency of SlugRouteTransformer by your plugin's one which is GenericRoutePlugin here.
using Autofac;
using Nop.Core.Configuration;
using Nop.Core.Infrastructure;
using Nop.Core.Infrastructure.DependencyManagement;
using Nop.Web.Framework.Mvc.Routing;
using System;
using System.Collections.Generic;
using System.Text;
namespace Nop.Plugin.Widgets.NivoSlider.Infrastructure
{
public class DependencyRegister : IDependencyRegistrar
{
public int Order =>int.MaxValue;
public void Register(ContainerBuilder builder, ITypeFinder typeFinder, NopConfig config)
{
builder.RegisterType<GenericRoutePlugin>().As<SlugRouteTransformer>().InstancePerDependency();
}
}
}
3. The controller
using Microsoft.AspNetCore.Mvc;
using Nop.Core;
using Nop.Core.Domain.Catalog;
using Nop.Core.Domain.Localization;
using Nop.Core.Domain.Orders;
using Nop.Core.Domain.Security;
using Nop.Services.Catalog;
using Nop.Services.Customers;
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.Factories;
using Nop.Web.Controllers;
using Nop.Web.Framework;
using Nop.Web.Framework.Controllers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Nop.Plugin.Widgets.NivoSlider.Controllers
{
public class ProductController : BasePublicController
{
#region Fields
private readonly CaptchaSettings _captchaSettings;
private readonly CatalogSettings _catalogSettings;
private readonly IAclService _aclService;
private readonly ICompareProductsService _compareProductsService;
private readonly ICustomerActivityService _customerActivityService;
private readonly ICustomerService _customerService;
private readonly IEventPublisher _eventPublisher;
private readonly ILocalizationService _localizationService;
private readonly IOrderService _orderService;
private readonly IPermissionService _permissionService;
private readonly IProductAttributeParser _productAttributeParser;
private readonly IProductModelFactory _productModelFactory;
private readonly IProductService _productService;
private readonly IRecentlyViewedProductsService _recentlyViewedProductsService;
private readonly IReviewTypeService _reviewTypeService;
private readonly IShoppingCartModelFactory _shoppingCartModelFactory;
private readonly IShoppingCartService _shoppingCartService;
private readonly IStoreContext _storeContext;
private readonly IStoreMappingService _storeMappingService;
private readonly IUrlRecordService _urlRecordService;
private readonly IWebHelper _webHelper;
private readonly IWorkContext _workContext;
private readonly IWorkflowMessageService _workflowMessageService;
private readonly LocalizationSettings _localizationSettings;
private readonly ShoppingCartSettings _shoppingCartSettings;
#endregion
#region Ctor
public ProductController(CaptchaSettings captchaSettings,
CatalogSettings catalogSettings,
IAclService aclService,
ICompareProductsService compareProductsService,
ICustomerActivityService customerActivityService,
ICustomerService customerService,
IEventPublisher eventPublisher,
ILocalizationService localizationService,
IOrderService orderService,
IPermissionService permissionService,
IProductAttributeParser productAttributeParser,
IProductModelFactory productModelFactory,
IProductService productService,
IRecentlyViewedProductsService recentlyViewedProductsService,
IReviewTypeService reviewTypeService,
IShoppingCartModelFactory shoppingCartModelFactory,
IShoppingCartService shoppingCartService,
IStoreContext storeContext,
IStoreMappingService storeMappingService,
IUrlRecordService urlRecordService,
IWebHelper webHelper,
IWorkContext workContext,
IWorkflowMessageService workflowMessageService,
LocalizationSettings localizationSettings,
ShoppingCartSettings shoppingCartSettings)
{
_captchaSettings = captchaSettings;
_catalogSettings = catalogSettings;
_aclService = aclService;
_compareProductsService = compareProductsService;
_customerActivityService = customerActivityService;
_customerService = customerService;
_eventPublisher = eventPublisher;
_localizationService = localizationService;
_orderService = orderService;
_permissionService = permissionService;
_productAttributeParser = productAttributeParser;
_productModelFactory = productModelFactory;
_productService = productService;
_reviewTypeService = reviewTypeService;
_recentlyViewedProductsService = recentlyViewedProductsService;
_shoppingCartModelFactory = shoppingCartModelFactory;
_shoppingCartService = shoppingCartService;
_storeContext = storeContext;
_storeMappingService = storeMappingService;
_urlRecordService = urlRecordService;
_webHelper = webHelper;
_workContext = workContext;
_workflowMessageService = workflowMessageService;
_localizationSettings = localizationSettings;
_shoppingCartSettings = shoppingCartSettings;
}
#endregion
public virtual IActionResult ProductDetailsFromPlugin(int productId, int updatecartitemid = 0)
{
var product = _productService.GetProductById(productId);
if (product == null || product.Deleted)
return InvokeHttp404();
var notAvailable =
//published?
(!product.Published && !_catalogSettings.AllowViewUnpublishedProductPage) ||
//ACL (access control list)
!_aclService.Authorize(product) ||
//Store mapping
!_storeMappingService.Authorize(product) ||
//availability dates
!_productService.ProductIsAvailable(product);
//Check whether the current user has a "Manage products" permission (usually a store owner)
//We should allows him (her) to use "Preview" functionality
var hasAdminAccess = _permissionService.Authorize(StandardPermissionProvider.AccessAdminPanel) && _permissionService.Authorize(StandardPermissionProvider.ManageProducts);
if (notAvailable && !hasAdminAccess)
return InvokeHttp404();
//visible individually?
if (!product.VisibleIndividually)
{
//is this one an associated products?
var parentGroupedProduct = _productService.GetProductById(product.ParentGroupedProductId);
if (parentGroupedProduct == null)
return RedirectToRoute("Homepage");
return RedirectToRoutePermanent("Product", new { SeName = _urlRecordService.GetSeName(parentGroupedProduct) });
}
//update existing shopping cart or wishlist item?
ShoppingCartItem updatecartitem = null;
if (_shoppingCartSettings.AllowCartItemEditing && updatecartitemid > 0)
{
var cart = _shoppingCartService.GetShoppingCart(_workContext.CurrentCustomer, storeId: _storeContext.CurrentStore.Id);
updatecartitem = cart.FirstOrDefault(x => x.Id == updatecartitemid);
//not found?
if (updatecartitem == null)
{
return RedirectToRoute("Product", new { SeName = _urlRecordService.GetSeName(product) });
}
//is it this product?
if (product.Id != updatecartitem.ProductId)
{
return RedirectToRoute("Product", new { SeName = _urlRecordService.GetSeName(product) });
}
}
//save as recently viewed
_recentlyViewedProductsService.AddProductToRecentlyViewedList(product.Id);
//display "edit" (manage) link
if (_permissionService.Authorize(StandardPermissionProvider.AccessAdminPanel) &&
_permissionService.Authorize(StandardPermissionProvider.ManageProducts))
{
//a vendor should have access only to his products
if (_workContext.CurrentVendor == null || _workContext.CurrentVendor.Id == product.VendorId)
{
DisplayEditLink(Url.Action("Edit", "Product", new { id = product.Id, area = AreaNames.Admin }));
}
}
//activity log
_customerActivityService.InsertActivity("PublicStore.ViewProduct",
string.Format(_localizationService.GetResource("ActivityLog.PublicStore.ViewProduct"), product.Name), product);
//model
var model = _productModelFactory.PrepareProductDetailsModel(product, updatecartitem, false);
//template
var productTemplateViewPath = _productModelFactory.PrepareProductTemplateViewPath(product);
//productTemplateViewPath = "~/Nop.Web/Views/Product/" + productTemplateViewPath + ".cshtml";
return View(productTemplateViewPath, model);
}
}
}
Note: I do not override the view so the default will show after this implementation. But it is possible to show views from the plugin by view override or just adding something like "~/plugin/Widgets.NivoSlider/Views/". Here Widgets.NivoSlider is just for test purposes. To implement it at your plugin change the namespace carefully.