Is there default commerce hierarchical router registered in routes table?
Hi Valdis,
No there is no default commerce hierachical router registered.
Only this one.
var hierarchicalCatalogPartialRouter = new CustomHierarchicalCatalogPartialRouter(() => productPage, catalogRoot, false); RouteTable.Routes.RegisterPartialRouter(hierarchicalCatalogPartialRouter);
I’m dealing with the same problem as you. If you solved it, can you please share your solution?
Just wrote about it in my book https://leanpub.com/epicommercerecipes/read_sample
Recipe Problem 1.6.5: Url without categories
Just wrote about it in my book https://leanpub.com/epicommercerecipes/read_sample
Recipe Problem 1.6.5: Url without categories
Hi Jeroen and Quan Mai
I am struglling to make it work but its not working for me. Always get a 404 at all
I would have a seo-friendly url with custom segment
for instance:
Orl Url of product: www.domain/niteco/123-furnishings/12320-curtains/1232005-licensednon-branded/123200525-xx-pencil-pleats/vanguard-marco-pencil-pleat-curtains-pair/?variant=a_498676
new url should be www.domain/product/{plu}/{product-name}/?variant=a_498676 (www.domain/product/498676/vanguard-marco-pencil-pleat-curtains-pair/?variant=a_498676)
here is my code
public class CustomHierarchicalCatalogPartialRouter : HierarchicalCatalogPartialRouter { private readonly string CustomRouteSegment = "product/{0}/{1}"; public CustomHierarchicalCatalogPartialRouter( Func<ContentReference> routeStartingPoint, CatalogContentBase commerceRoot, bool enableOutgoingSeoUri, IContentLoader contentLoader, IRelationRepository relationRepository) : base( routeStartingPoint, commerceRoot, enableOutgoingSeoUri) { _contentLoader = contentLoader; _relationRepository = relationRepository; } public override PartialRouteData GetPartialVirtualPath(CatalogContentBase content, string language, RouteValueDictionary routeValues, RequestContext requestContext) { var product = content as BaseProduct; if (product == null) return base.GetPartialVirtualPath(content, language, routeValues, requestContext); return new PartialRouteData { BasePathRoot = RouteStartingPoint, PartialVirtualPath =string.Format(CustomRouteSegment, product.PLU, product.Name) }; } public override object RoutePartial(PageData content, SegmentContext segmentContext) { if (!content.ContentLink.CompareToIgnoreWorkID(RouteStartingPoint)) return base.RoutePartial(content, segmentContext); var routed = base.RoutePartial(content, segmentContext); var product = routed as FashionProduct; segmentContext.RoutedContentLink = product.ContentLink; segmentContext.RemainingPath = string.Empty; segmentContext.RoutedObject = product; return product; } protected override bool IsValidRoutedContent(CatalogContentBase content) { //To forbid hierarchical variation url structure: if (content is ProductContent) return false; return base.IsValidRoutedContent(content); } }
protected override void RegisterRoutes(RouteCollection routes) { var segment = new ParameterSegment("product"); var routingParameters = new MapContentRouteParameters() { SegmentMappings = new Dictionary<string, ISegment>() }; routingParameters.SegmentMappings.Add("product", segment); RouteTable.Routes.MapContentRoute( "product_node", "{language}/product/{plu}/{name}/{action}", new { controller = "Product", action = "Index", language = UrlParameter.Optional, }, routingParameters); }
Hi Thang
Here is the solution I've used in my project. The pattern of product SEO URL is https://domain/product/product-name/product-code
using EPiServer.Commerce.Catalog.ContentTypes; using EPiServer.Core; using EPiServer.ServiceLocation; using EPiServer.Web.Routing; using EPiServer.Web.Routing.Segments; using Mediachase.Commerce.Catalog; using System; using System.Collections.Generic; using System.Globalization; using System.Web; using System.Web.Routing; namespace EPiServer.Commerce.Routing { public class ProductRouter : HierarchicalCatalogPartialRouter { protected readonly IContentLoader contentLoader; protected readonly ReferenceConverter referenceConverter; public ProductRouter(Func<ContentReference> routeStartingPoint, CatalogContentBase commerceRoot, bool enableOutgoingSeoUri) : base(routeStartingPoint, commerceRoot, enableOutgoingSeoUri) { this.contentLoader = ServiceLocator.Current.GetInstance<IContentLoader>(); this.referenceConverter = ServiceLocator.Current.GetInstance<ReferenceConverter>(); } public override PartialRouteData GetPartialVirtualPath(CatalogContentBase content, string language, RouteValueDictionary routeValues, RequestContext requestContext) { var product = content as ProductContent; if (product == null) { return base.GetPartialVirtualPath(content, language, routeValues, requestContext); } return new PartialRouteData { BasePathRoot = RouteStartingPoint, PartialVirtualPath = $"product/{product.Name.Replace(' ', '-')}/{HttpUtility.UrlPathEncode(product.Code)}" // Should move to the ultility class }; } protected override CatalogContentBase FindNextContentInSegmentPair(CatalogContentBase catalogContent, SegmentPair segmentPair, SegmentContext segmentContext, CultureInfo cultureInfo) { while (!string.IsNullOrWhiteSpace(segmentPair.Next) ) { if (catalogContent.ContentType == CatalogContentType.Root) { var contentLink = referenceConverter.GetContentLink(segmentPair.Next, CatalogContentType.CatalogEntry); if (!ContentReference.IsNullOrEmpty(contentLink)) { return contentLoader.Get<ProductContent>(contentLink); } } segmentPair = segmentContext.GetNextValue(segmentPair.Remaining); } segmentContext.RemainingPath = string.Empty; return null; } } }
You should register the router in the initialization module.
RouteCollectionExtensions.RegisterPartialRouter<PageData, CatalogContentBase>(System.Web.Routing.RouteTable.Routes, new ProductRouter(() => SiteDefinition.Current.StartPage, commerceRootContent, false));
Hope this helps!
Hi Quan Mai,
I have applied your solution it seems work fine but when i try run the job EPiServer Find Content Indexing Job i get an exception and all product can't index
Have you ever thought of this case yet?
2018-07-18 17:12:27,228 [96] ERROR EPiServer.Find.Cms.ContentIndexer: HC-THAOPHAM: An exception occurred while indexing content [Link 2317__CatalogContent] [GUID 7f18a65d-cf2d-40c5-a58e-040b8e06be07] [Type DiningAndEntertainingProduct] [Name Simon Gault Replacement Seals & Rings Old Fashioned 70mm]: Exception has been thrown by the target of an invocation.
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.ArgumentNullException: The provided content link does not have a value.
Parameter name: contentLink
at EPiServer.Core.Internal.DefaultContentLoader.Get[T](ContentReference contentLink, LoaderOptions loaderOptions)
at EPiServer.Web.Routing.Segments.Internal.NodeSegment.GetVirtualPathSegment(RequestContext requestContext, RouteValueDictionary values)
at EPiServer.Web.Routing.Segments.Internal.NodeSegment.GetVirtualPathSegment(RequestContext requestContext, RouteValueDictionary values, HashSet`1 usedValues)
at EPiServer.Web.Routing.Internal.DefaultContentRoute.AddVirtualPathFromSegments(StringBuilder virtualPath, RequestContext requestContext, RouteValueDictionary values, HashSet`1 usedValues, Int32 lastNonDefaultIndex)
at EPiServer.Web.Routing.Internal.DefaultContentRoute.GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
at EPiServer.Web.Routing.Internal.DefaultUrlResolver.GetUrlFromRoute(ContentReference contentReference, String language, RouteValueDictionary routeValues, RequestContext requestContext)
at EPiServer.Web.Routing.Internal.DefaultUrlResolver.GetVirtualPath(ContentReference contentLink, String language, VirtualPathArguments arguments)
at EPiServer.Web.Routing.UrlResolver.GetUrl(ContentReference contentLink, String language)
at EPiServer.Find.Commerce.CommerceUnifiedSearchSetUp.GetContentUrl(ContentReference contentLink)
--- End of inner exception stack trace ---
at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)
at System.Delegate.DynamicInvokeImpl(Object[] args)
at EPiServer.Find.UnifiedSearch.IndexProjection.GetUrl(Object o)
at EPiServer.Find.DelegateValueProvider`2.GetValue(Object target)
at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.CalculatePropertyValues(JsonWriter writer, Object value, JsonContainerContract contract, JsonProperty member, JsonProperty property, JsonContract& memberContract, Object& memberValue)
at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeObject(JsonWriter writer, Object value, JsonObjectContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty)
at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.Serialize(JsonWriter jsonWriter, Object value, Type objectType)
at EPiServer.Find.Api.BulkActionConverter.WriteJson(JsonWriter writer, Object value, JsonSerializer serializer)
at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeConvertable(JsonWriter writer, JsonConverter converter, Object value, JsonContract contract, JsonContainerContract collectionContract, JsonProperty containerProperty)
at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.Serialize(JsonWriter jsonWriter, Object value, Type objectType)
at Newtonsoft.Json.JsonSerializer.SerializeInternal(JsonWriter jsonWriter, Object value, Type objectType)
at EPiServer.Find.Json.Serializer.SerializeToTextWriter(JsonSerializer serializer, Object value, TextWriter textWriter)
at EPiServer.Find.Json.Serializer.SerializeObjectsToJsonRequest(JsonSerializer serializer, IJsonRequest jsonRequest, IEnumerable values)
at EPiServer.Find.Api.BulkCommand.Execute(List`1& serializationFailures)
at EPiServer.Find.Api.BulkCommand.Execute()
at EPiServer.Find.Cms.ContentIndexer.IndexWithRetry(IContent[] contents, Int32 maxRetries)
at EPiServer.Find.Cms.ContentIndexer.Index(IEnumerable`1 content, IndexOptions options)
at EPiServer.Find.Cms.ContentIndexer.IndexBatch(IEnumerable`1 content, Action`1 statusAction, Int32& numberOfContentErrors, Int32& indexingCount)
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.ArgumentNullException: The provided content link does not have a value.
Parameter name: contentLink
at EPiServer.Core.Internal.DefaultContentLoader.Get[T](ContentReference contentLink, LoaderOptions loaderOptions)
at EPiServer.Web.Routing.Segments.Internal.NodeSegment.GetVirtualPathSegment(RequestContext requestContext, RouteValueDictionary values)
at EPiServer.Web.Routing.Segments.Internal.NodeSegment.GetVirtualPathSegment(RequestContext requestContext, RouteValueDictionary values, HashSet`1 usedValues)
at EPiServer.Web.Routing.Internal.DefaultContentRoute.AddVirtualPathFromSegments(StringBuilder virtualPath, RequestContext requestContext, RouteValueDictionary values, HashSet`1 usedValues, Int32 lastNonDefaultIndex)
at EPiServer.Web.Routing.Internal.DefaultContentRoute.GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
at EPiServer.Web.Routing.Internal.DefaultUrlResolver.GetUrlFromRoute(ContentReference contentReference, String language, RouteValueDictionary routeValues, RequestContext requestContext)
at EPiServer.Web.Routing.Internal.DefaultUrlResolver.GetVirtualPath(ContentReference contentLink, String language, VirtualPathArguments arguments)
at EPiServer.Web.Routing.UrlResolver.GetUrl(ContentReference contentLink, String language)
at EPiServer.Find.Commerce.CommerceUnifiedSearchSetUp.GetContentUrl(ContentReference contentLink)
--- End of inner exception stack trace ---
at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)
at System.Delegate.DynamicInvokeImpl(Object[] args)
at EPiServer.Find.UnifiedSearch.IndexProjection.GetUrl(Object o)
at EPiServer.Find.DelegateValueProvider`2.GetValue(Object target)
at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.CalculatePropertyValues(JsonWriter writer, Object value, JsonContainerContract contract, JsonProperty member, JsonProperty property, JsonContract& memberContract, Object& memberValue)
at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeObject(JsonWriter writer, Object value, JsonObjectContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty)
at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.Serialize(JsonWriter jsonWriter, Object value, Type objectType)
at EPiServer.Find.Api.BulkActionConverter.WriteJson(JsonWriter writer, Object value, JsonSerializer serializer)
at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeConvertable(JsonWriter writer, JsonConverter converter, Object value, JsonContract contract, JsonContainerContract collectionContract, JsonProperty containerProperty)
at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.Serialize(JsonWriter jsonWriter, Object value, Type objectType)
at Newtonsoft.Json.JsonSerializer.SerializeInternal(JsonWriter jsonWriter, Object value, Type objectType)
at EPiServer.Find.Json.Serializer.SerializeToTextWriter(JsonSerializer serializer, Object value, TextWriter textWriter)
at EPiServer.Find.Json.Serializer.SerializeObjectsToJsonRequest(JsonSerializer serializer, IJsonRequest jsonRequest, IEnumerable values)
at EPiServer.Find.Api.BulkCommand.Execute(List`1& serializationFailures)
at EPiServer.Find.Api.BulkCommand.Execute()
at EPiServer.Find.Cms.ContentIndexer.IndexWithRetry(IContent[] contents, Int32 maxRetries)
at EPiServer.Find.Cms.ContentIndexer.Index(IEnumerable`1 content, IndexOptions options)
at EPiServer.Find.Cms.ContentIndexer.IndexBatch(IEnumerable`1 content, Action`1 statusAction, Int32& numberOfContentErrors, Int32& indexingCount)
I'm trying to create some custom routing for the commerce catalog.
I would like all variations to have the following url structure, regardles of where they are in the catalog:
https://sitename/products/variationcode
The products page is the route starting point for the catalog.
To do this I created a custom HierarchicalCatalogPartialRouter:
The catalog partial router is registered in an initialization module
The GetPartialVirtualPath method correctly creates the outgoing Url, and the RoutePartial method also finds the correct variation in the catalog.
However I keep getting a 404 when I do a request on https://sitename/products/variationcode
What am I missing here? Any help is very much appreciated.