Search code examples
nopcommerce

URL Routing in NOP Commerce


I am trying to figure out how this works, for example if I pull up a category page: http://localhost:15536/desktops it hits the following Action method in the CatalogController:

public virtual IActionResult Category(int categoryId, CatalogPagingFilteringModel command)

So how it's passing in the correct category id (int) but it's not part of the URL how does this work?


Solution

  • In the Nopcommerce, there is an entity named UrlRecord, you can find it in Nop.Core\Domain\Seo\UrlRecord.cs:

    public partial class UrlRecord : BaseEntity
    {
        public int EntityId { get; set; }
    
        public string EntityName { get; set; }
    
        public string Slug { get; set; }
    
        public bool IsActive { get; set; }
    
        public int LanguageId { get; set; }
    }
    

    The EntityName, indicates that this UrlRecord is using for which entity (i.g. Product or Category, etc.). The EntityId indicates that the Id of pointed entity (i.g. Id of Product). The Slug indicates that with what URL we can reach the intended entity. In your example, EntityName is "Category", EntityId is the Id of the category and Slug is "desktop".

    So, how Nopcommerce routes these Slugs to the right Controller and Action? For figuring out of this, we have to look at the GenericPathRoute class that is located in Nop.Web.Framework\Seo\GenericPathRoute.cs. This class is registered as a custom Route to the IRouteBuilder. Regardless of asp.net core routing and Nopcommerce details, the RouteAsync method of GenericPathRoute is called at the start of every request. If we take a look into this method, we could see this section (before this section, urlRecord is fetched from the database by the Slug, so we know what is the entity and its Id, so we can route it to the desired Controller and Action with right arguments):

    var currentRouteData = new RouteData(context.RouteData);
    switch (urlRecord.EntityName.ToLowerInvariant())
    {
        case "product":
            currentRouteData.Values[NopPathRouteDefaults.ControllerFieldKey] = "Product";
            currentRouteData.Values[NopPathRouteDefaults.ActionFieldKey] = "ProductDetails";
            currentRouteData.Values[NopPathRouteDefaults.ProductIdFieldKey] = urlRecord.EntityId;
            currentRouteData.Values[NopPathRouteDefaults.SeNameFieldKey] = urlRecord.Slug;
            break;
        case "producttag":
            currentRouteData.Values[NopPathRouteDefaults.ControllerFieldKey] = "Catalog";
            currentRouteData.Values[NopPathRouteDefaults.ActionFieldKey] = "ProductsByTag";
            currentRouteData.Values[NopPathRouteDefaults.ProducttagIdFieldKey] = urlRecord.EntityId;
            currentRouteData.Values[NopPathRouteDefaults.SeNameFieldKey] = urlRecord.Slug;
            break;
        case "category":
            currentRouteData.Values[NopPathRouteDefaults.ControllerFieldKey] = "Catalog";
            currentRouteData.Values[NopPathRouteDefaults.ActionFieldKey] = "Category";
            currentRouteData.Values[NopPathRouteDefaults.CategoryIdFieldKey] = urlRecord.EntityId;
            currentRouteData.Values[NopPathRouteDefaults.SeNameFieldKey] = urlRecord.Slug;
            break;
        case "manufacturer":
            currentRouteData.Values[NopPathRouteDefaults.ControllerFieldKey] = "Catalog";
            currentRouteData.Values[NopPathRouteDefaults.ActionFieldKey] = "Manufacturer";
            currentRouteData.Values[NopPathRouteDefaults.ManufacturerIdFieldKey] = urlRecord.EntityId;
            currentRouteData.Values[NopPathRouteDefaults.SeNameFieldKey] = urlRecord.Slug;
            break;
        case "vendor":
            currentRouteData.Values[NopPathRouteDefaults.ControllerFieldKey] = "Catalog";
            currentRouteData.Values[NopPathRouteDefaults.ActionFieldKey] = "Vendor";
            currentRouteData.Values[NopPathRouteDefaults.VendorIdFieldKey] = urlRecord.EntityId;
            currentRouteData.Values[NopPathRouteDefaults.SeNameFieldKey] = urlRecord.Slug;
            break;
        case "newsitem":
            currentRouteData.Values[NopPathRouteDefaults.ControllerFieldKey] = "News";
            currentRouteData.Values[NopPathRouteDefaults.ActionFieldKey] = "NewsItem";
            currentRouteData.Values[NopPathRouteDefaults.NewsItemIdFieldKey] = urlRecord.EntityId;
            currentRouteData.Values[NopPathRouteDefaults.SeNameFieldKey] = urlRecord.Slug;
            break;
        case "blogpost":
            currentRouteData.Values[NopPathRouteDefaults.ControllerFieldKey] = "Blog";
            currentRouteData.Values[NopPathRouteDefaults.ActionFieldKey] = "BlogPost";
            currentRouteData.Values[NopPathRouteDefaults.BlogPostIdFieldKey] = urlRecord.EntityId;
            currentRouteData.Values[NopPathRouteDefaults.SeNameFieldKey] = urlRecord.Slug;
            break;
        case "topic":
            currentRouteData.Values[NopPathRouteDefaults.ControllerFieldKey] = "Topic";
            currentRouteData.Values[NopPathRouteDefaults.ActionFieldKey] = "TopicDetails";
            currentRouteData.Values[NopPathRouteDefaults.TopicIdFieldKey] = urlRecord.EntityId;
            currentRouteData.Values[NopPathRouteDefaults.SeNameFieldKey] = urlRecord.Slug;
            break;
        default:
            //no record found, thus generate an event this way developers could insert their own types
            EngineContext.Current.Resolve<IEventPublisher>()
                ?.Publish(new CustomUrlRecordEntityNameRequestedEvent(currentRouteData, urlRecord));
            break;
    }
    context.RouteData = currentRouteData;
    

    We can see here that it changes the routeData of this request, regarding EntityName. We can see here that this feature is going to work only for entities named in this switch/case command.