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?
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 Slug
s 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.