Override Generic Route From Plugin NopCommerce4.3

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.
3 года назад
Below are the steps you can follow to override the generic route. It is a little different than the previous verion.

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.  
3 года назад
This is the way I ended up using as well, except for this part:

values["NameSpace"] = "Nop.Plugin.Misc.SupplierCatalogues";

what is the purpose of this value?

Also, is there any better way? It seems like a lot of boilerplate to just override:

         
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


Cheers.
3 года назад
aaronguilford wrote:
This is the way I ended up using as well, except for this part:

values["NameSpace"] = "Nop.Plugin.Misc.SupplierCatalogues";

what is the purpose of this value?

Also, is there any better way? It seems like a lot of boilerplate to just override:

         
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


Cheers.


"Nop.Plugin.Misc.SupplierCatalogues";
=====> you can avoid this.
Also, is there any better way?
=====> There is another way by ActionFilter, you can try that. But rather than ActionFilter for me, it is more handy.
It seems like a lot of boilerplate to just override:
=====> Not at all. Because it becomes easier for 4.3. If you see closely that the main thing is the dependency registration and action method name change that is all. Then about the view you can show direct the path or override the view.
3 года назад
Very Helpful post.
This seem to me awesome.
3 года назад
I implemented this successfully with one plugin overriding the Generic Route. But just recently I implemented a second plugin on the same nop installation that is overriding a different Route of the Generic Route and it is not working. When debugging, only one of the GenericRoutePlugin is called the other one is ignored. How can this be addressed, so that both plugins GenericRoutePlugin are processed?
один год назад
Hi all,    if you want to use generic slug routing, You can use this code (I used on NopCommerce 4.3)

public partial class GenericRouteEventConsumer : IConsumer<GenericRoutingEvent>
    {
        public void HandleEvent(GenericRoutingEvent eventMessage)
        {
            if (eventMessage.UrlRecord.EntityName == nameof(CustomerOffer))
            {
                eventMessage.RouteValues[NopPathRouteDefaults.ControllerFieldKey] = YOUR_CONTROLLER_NAME;
                eventMessage.RouteValues[NopPathRouteDefaults.ActionFieldKey] = YOUR_ACTION_NAME;
                eventMessage.RouteValues[YOUR_ACTION_ID_PARAMETER] = eventMessage.UrlRecord.EntityId;
                eventMessage.RouteValues[NopPathRouteDefaults.SeNameFieldKey] = eventMessage.UrlRecord.Slug;
            }
        }
    }
один год назад
dizaynplus wrote:
Hi all,    if you want to use generic slug routing, You can use this code (I used on NopCommerce 4.3)

public partial class GenericRouteEventConsumer : IConsumer<GenericRoutingEvent>
    {
        public void HandleEvent(GenericRoutingEvent eventMessage)
        {
            if (eventMessage.UrlRecord.EntityName == nameof(CustomerOffer))
            {
                eventMessage.RouteValues[NopPathRouteDefaults.ControllerFieldKey] = YOUR_CONTROLLER_NAME;
                eventMessage.RouteValues[NopPathRouteDefaults.ActionFieldKey] = YOUR_ACTION_NAME;
                eventMessage.RouteValues[YOUR_ACTION_ID_PARAMETER] = eventMessage.UrlRecord.EntityId;
                eventMessage.RouteValues[NopPathRouteDefaults.SeNameFieldKey] = eventMessage.UrlRecord.Slug;
            }
        }
    }


Is this code also valid for version 4.60?

Thank you.
This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.