Custom endpoints for subcategories do not work

Hello,

Our client wanted custom endpoints for products and categories, so I used this topic as a solution to this request. It seemed to work just fine until I wanted to access a subcategory, which resulted in a Page not found error.

The implementation works fine for products and main categories but shows the wrong URL for subcategories. As a result, no products are shown on the page.

Here are some examples:

Our client wanted to have the prefix produkt on every product. This seems to work fine.



Same applies for main categories. The custom URL works fine here as well. The main category in this case is Spillskydd and we can see that the endpoint is /productkategori/spillskydd/


The problem occurs when I attempt to access a subcategory. If I want to navigate to absorbenter, for instance, I get redirected to the following page:

Notice the URL. It should be /productkategori/spillskydd/absorbenter/, but it is instead /productkategori/absorbenter/. I assume this is the reason why the page cannot be found.

I have tried modifying UrlServiceDecorator and RoutingServiceDecorator further, but without any success.

Here are the contents of the two files:

RoutingServiceDecorator

using System;
using System.Linq;
using JetBrains.Annotations;
using Litium.Application.Web.Routing;
using Litium.FieldFramework;
using Litium.Products;
using Litium.Runtime.DependencyInjection;

namespace Litium.Accelerator.Mvc.Routing
{
	[ServiceDecorator(typeof(RoutingService))]
	public class RoutingServiceDecorator : RoutingService
	{
		private readonly RoutingService _routingService;
		private readonly RoutingHelperService _routingHelperService;
		private readonly BaseProductService _baseProductService;
		private readonly VariantService _variantService;
		private readonly FieldTemplateService _fieldTemplateService;
		private readonly DisplayTemplateService _displayTemplateService;
		private readonly CategoryService _categoryService;

		public RoutingServiceDecorator(RoutingService routingService,
			RoutingHelperService routingHelperService,
			BaseProductService baseProductService,
			VariantService variantService,
			FieldTemplateService fieldTemplateService,
			DisplayTemplateService displayTemplateService,
			CategoryService categoryService)
		{
			_routingService = routingService;
			_routingHelperService = routingHelperService;
			_baseProductService = baseProductService;
			_variantService = variantService;
			_fieldTemplateService = fieldTemplateService;
			_displayTemplateService = displayTemplateService;
			_categoryService = categoryService;
		}

        public override bool TryGet([NotNull] AssortmentRouteLookupInfo lookupInfo, out RouteInfo routeInfo)
        {
            var result = _routingService.TryGet(lookupInfo, out routeInfo);

            Guid systemId = Guid.Empty;
            BaseProduct baseProduct = null;
            Variant variant = null;
            Category category = null;

            var websiteSystemId = lookupInfo.WebsiteSystemId;

            var lookupSegments = lookupInfo.Segments;
            if (_routingHelperService.TryGetBaseProduct(lookupSegments[^1], lookupInfo.CultureInfo, out systemId))
            {
                baseProduct = _baseProductService.Get(systemId);
            } 
            else if (_routingHelperService.TryGetVariant(lookupSegments[^1], lookupInfo.CultureInfo, out systemId))
            {
                variant = _variantService.Get(systemId);
                baseProduct = _baseProductService.Get(variant.BaseProductSystemId);
            } 
            else if (_routingHelperService.TryGetCategory(Guid.Empty, lookupSegments[^1], lookupInfo.CultureInfo, out Guid categorySystemId, lookupInfo.AssortmentSystemId))
            {
                category = _categoryService.Get(categorySystemId);
            }

            if (category != null)
            {
                var categoryFieldTemplate = _fieldTemplateService.Get<CategoryFieldTemplate>(category.FieldTemplateSystemId);
                var categoryDisplayTemplate = _displayTemplateService.Get<CategoryDisplayTemplate>(categoryFieldTemplate.DisplayTemplateSystemId);

                var categoryTemplatePath = categoryDisplayTemplate.Templates.FirstOrDefault(x => x.WebSiteSystemId == websiteSystemId)?.Path
                                            ?? categoryDisplayTemplate?.TemplatePath;

                routeInfo = new CategoryRouteInfo(categoryTemplatePath, categoryDisplayTemplate?.Id, category)
                {
                    Category = category
                };

                return !string.IsNullOrEmpty(categoryTemplatePath) || result;
            }

            if (baseProduct != null)
            {
                var fieldTemplate = _fieldTemplateService.Get<ProductFieldTemplate>(baseProduct.FieldTemplateSystemId);
                var displayTemplate = _displayTemplateService.Get<ProductDisplayTemplate>(fieldTemplate.DisplayTemplateSystemId);

                var mainCategory = baseProduct.GetMainCategory(lookupInfo.ChannelSystemId);

                var templatePath = string.Empty;

                if (mainCategory != null) 
                {
                    templatePath = displayTemplate.Templates.FirstOrDefault(x => x.WebSiteSystemId == websiteSystemId)?.Path ?? displayTemplate?.TemplatePath;
                }

                if (!string.IsNullOrEmpty(templatePath))
                {
                    routeInfo = new ProductRouteInfo(templatePath, displayTemplate?.Id, baseProduct, mainCategory)
                    { 
                        Variant = variant 
                    };
                    result = true;
                }
            }

            return result;
        }

        public override bool TryGet([NotNull] CategoryRouteLookupInfo lookupInfo, out RouteInfo routeInfo)
        {
            var result = _routingService.TryGet(lookupInfo, out routeInfo);
            return result;
        }

        public override bool TryGet([NotNull] DomainNameRouteLookupInfo lookupInfo, out DomainNameRouteInfo routeInfo)
        {
            var result = _routingService.TryGet(lookupInfo, out routeInfo);
            return result;
        }
    }
}

UrlServiceDecorator

using System;
using System.Linq;
using JetBrains.Annotations;
using Litium.Globalization;
using Litium.Products;
using Litium.Runtime;
using Litium.Runtime.DependencyInjection;
using Litium.Web;
using Litium.Web.Routing;
using Litium.Websites;

namespace Litium.Accelerator.Mvc.Routing
{
	[ServiceDecorator(typeof(UrlService))]
	public class UrlServiceDecorator : UrlService, IInfrastructure<RouteRequestLookupInfoAccessor>
	{
		private readonly UrlService _urlService;
		
		public UrlServiceDecorator(UrlService urlService)
		{
			_urlService = urlService;
		}

		public RouteRequestLookupInfoAccessor Instance => ((IInfrastructure<RouteRequestLookupInfoAccessor>)_urlService).Instance;

        public override string GetUrl([NotNull] BaseProduct baseProduct, [NotNull] ProductUrlArgs args)
        {
			return CustomUrlEndpoint(_urlService.GetUrl(baseProduct, args), args.AbsoluteUrl, "produkt"); // Temporarily hard-coded constant. TODO: Add multiculture fields so that both english and swedish values are available. 
		}

        public override string GetUrl([NotNull] Variant variant, [NotNull] ProductUrlArgs args)
        {
			return CustomUrlEndpoint(_urlService.GetUrl(variant, args), args.AbsoluteUrl, "produkt"); // Temporarily hard-coded constant. TODO: Add multiculture fields so that both english and swedish values are available. 
		}

        public override string GetUrl([NotNull] Category category, [NotNull] CategoryUrlArgs args)
        {
			return CustomUrlEndpoint(_urlService.GetUrl(category, args), args.AbsoluteUrl, "produktkategori"); // Temporarily hard-coded constant. TODO: Add multiculture fields so that both english and swedish values are available. 
		}

        public override string GetUrl([NotNull] Page page, [NotNull] PageUrlArgs args)
        {
			return _urlService.GetUrl(page, args);
        }

        public override string GetUrl([NotNull] Channel channel, [NotNull] ChannelUrlArgs args)
        {
            return _urlService.GetUrl(channel, args);
		}

        public override string GetUrl([NotNull] DomainName domainName)
        {
            return _urlService.GetUrl(domainName);
        }

		private static string CustomUrlEndpoint(string originalUrl, bool absoluteUrl, string endpointPrefix)
		{
			if (string.IsNullOrWhiteSpace(originalUrl))
			{
				return originalUrl;
			}

			if (absoluteUrl)
			{
				var uri = new Uri(originalUrl);
				return $"{uri.Scheme}://{uri.Host}/{uri.Segments.Last()}";
			}
			else
			{
				return $"/{endpointPrefix}/{originalUrl.Split('/').Last()}";
			}
		}

	}
}

Litium version: 8.5.0

Not an answer on the question itself but interesting about what the customer thinks will be better with this type of structure?

I am not entirely sure, to be honest. I think they only want this because it will look nicer, or perhaps it is easier to tell apart products from categories this way. I think what they want is /produkt/{product name} for products and /produktkategori/{product category} for categories, and then /produktkategori/{sub categori}/{category} if the main category has subcategories.

The current implementation is a default one:

For products

For categories

So I’m thinking they want these types of endpoints in order to differentiate products and categories.

Please, check with the customer about why they want it that way.

In general, I think it is hard to make the structure like that because you need to create route logic etc for all type and levels of child categories that is possible to create.

In Litium it already exists logic so the same product can have different locations (URLs) for different categories and the system is creating corresponding canonical links that points to the products main category.

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