Product base url

Is it possible to modify the product base url?
Ex.
http://se.emnordic.spoton.se/cases/softcase/digitech-gb100
should be
http://se.emnordic.spoton.se/products/cases/softcase/digitech-gb100

without adding a product category “products” and move all categories into that.

Litium version: 7.1.0

Hi,
It is possible by decorating UrlService and RoutingService. I’ll try to write an article about that at the docs.litium.com. I will put a link to the article under this question when I’m done.

4 Likes

Hi, does this solution looks ok?
(Except from putting productUrlPrefix in some better place)

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

namespace Litium.Accelerator.Mvc.Decorators
{
    [Litium.Runtime.DependencyInjection.ServiceDecorator(typeof(UrlService))]
    public class UrlServiceDecorator : UrlService, IInfrastructure<RouteRequestLookupInfoAccessor>
    {
        private readonly UrlService _parent;
        private string productUrlPrefix = "products";
        public UrlServiceDecorator(UrlService urlService)
        {
            _parent = urlService;
        }

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

        public override string GetUrl([NotNull] BaseProduct baseProduct, [NotNull] ProductUrlArgs args)
        {
            var originalUrl = _parent.GetUrl(baseProduct, args);
            return ModifyUrl(originalUrl, args.AbsoluteUrl);
        }


        public override string GetUrl([NotNull] Variant variant, [NotNull] ProductUrlArgs args)
        {
            var originalUrl = _parent.GetUrl(variant, args);
            return ModifyUrl(originalUrl, args.AbsoluteUrl);
        }

        public override string GetUrl([NotNull] Category category, [NotNull] CategoryUrlArgs args)
        {
            var originalUrl = _parent.GetUrl(category, args);
            return ModifyUrl(originalUrl, args.AbsoluteUrl);
        }
        public override string GetUrl([NotNull] Page page, [NotNull] PageUrlArgs args) => _parent.GetUrl(page, args);
        public override string GetUrl([NotNull] Channel channel, [NotNull] ChannelUrlArgs args) => _parent.GetUrl(channel, args);

        private string ModifyUrl(string originalUrl, bool absoluteUrl)
        {
            if (string.IsNullOrWhiteSpace(originalUrl))
            {
                return originalUrl;
            }
            if (absoluteUrl)
            {
                var uri = new Uri(originalUrl);
                return uri.Scheme + "://" + uri.Host + "/" + productUrlPrefix + uri.AbsolutePath;
            }
            else
            {
                return "/" + productUrlPrefix + originalUrl;
            }
        }
    }
}



using JetBrains.Annotations;
using Litium.Application.Web.Routing;

namespace Litium.Accelerator.Mvc.Decorators
{
    [Litium.Runtime.DependencyInjection.ServiceDecorator(typeof(RoutingService))]
    public class RoutingServiceDecorator : RoutingService
    {
        private readonly RoutingService _parent;
        private string productUrlPrefix = "products";

        public RoutingServiceDecorator(RoutingService routingService)
        {
            _parent = routingService;
        }
        public override bool TryGet([NotNull] AssortmentRouteLookupInfo lookupInfo, out RouteInfo routeInfo)
        {
            if(lookupInfo.Segments.Length > 1 && lookupInfo.Segments[0] == productUrlPrefix)
            {
                lookupInfo = new AssortmentRouteLookupInfo(
                    lookupInfo.CultureInfo, 
                    lookupInfo.Segments.Slice(1), 
                    lookupInfo.WebsiteSystemId, 
                    lookupInfo.AssortmentSystemId,
                    lookupInfo.ChannelSystemId,
                    lookupInfo.UseHistory, 
                    lookupInfo.DisplayOnWebsiteSystemId);
            }
            var result = _parent.TryGet(lookupInfo, out routeInfo);
            return result;
        }
        public override bool TryGet([NotNull] CategoryRouteLookupInfo lookupInfo, out RouteInfo routeInfo)
        {
            if (lookupInfo.Segments.Length > 1 && lookupInfo.Segments[0] == productUrlPrefix)
            {
                lookupInfo = new CategoryRouteLookupInfo(
                    lookupInfo.CultureInfo,
                    lookupInfo.Segments.Slice(1),
                    lookupInfo.WebsiteSystemId,
                    lookupInfo.CategorySystemId, 
                    lookupInfo.ChannelSystemId,
                    lookupInfo.UseHistory);
            }
            var result = _parent.TryGet(lookupInfo, out routeInfo);
            return result;
        }

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

Great! I don’t need to write an article about it now. You can maybe save the prouductUrlPrefix as a field on the channel. You have always access to the channelId in both UrlService and RoutingService.

1 Like

Those parts/lines with “+” would look better with $“” instead (as suggestions) :slight_smile:

1 Like

@utku Do you see any way this could be used to have products url completely without category url?

For example:
http://se.emnordic.spoton.se/products/digitech-gb100
Or even better
http://se.emnordic.spoton.se/digitech-gb100

Use case:
1 Product in many categories, where category urls will look confusing for the user

Maybe you could use something like this (modified version of above):

UrlServiceDecorator

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;
using System;
using System.Linq;

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

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

		public override string GetUrl([NotNull] BaseProduct baseProduct, [NotNull] ProductUrlArgs args)
		{
			var originalUrl = _parent.GetUrl(baseProduct, args);
			return ModifyUrl(originalUrl, args.AbsoluteUrl);
		}


		public override string GetUrl([NotNull] Variant variant, [NotNull] ProductUrlArgs args)
		{
			var originalUrl = _parent.GetUrl(variant, args);
			return ModifyUrl(originalUrl, args.AbsoluteUrl);
		}

		public override string GetUrl([NotNull] Category category, [NotNull] CategoryUrlArgs args) => _parent.GetUrl(category, args);
		public override string GetUrl([NotNull] Page page, [NotNull] PageUrlArgs args) => _parent.GetUrl(page, args);
		public override string GetUrl([NotNull] Channel channel, [NotNull] ChannelUrlArgs args) => _parent.GetUrl(channel, args);

		private string ModifyUrl(string originalUrl, bool absoluteUrl)
		{
			if (string.IsNullOrWhiteSpace(originalUrl))
			{
				return originalUrl;
			}

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

RoutingServiceDecorator

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

namespace Litium.Accelerator.Mvc.Decorators
{
	[ServiceDecorator(typeof(RoutingService))]
	public class RoutingServiceDecorator : RoutingService
	{
		private readonly RoutingService _parent;
		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)
		{
			_parent = 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 = _parent.TryGet(lookupInfo, out routeInfo);

			if (!result)
			{
				Variant variant = null;
				BaseProduct baseProduct = null;

				if (_routingHelperService.TryGetBaseProduct(lookupInfo.Segments[0], lookupInfo.CultureInfo, out Guid systemId))
				{
					baseProduct = _baseProductService.Get(systemId);
				}
				else if (_routingHelperService.TryGetVariant(lookupInfo.Segments[0], lookupInfo.CultureInfo, out systemId))
				{
					variant = _variantService.Get(systemId);
					baseProduct = _baseProductService.Get(variant.BaseProductSystemId);
				}

				if (baseProduct != null)
				{
					var category = baseProduct.GetMainCategory(lookupInfo.ChannelSystemId);

					if (category != null)
					{
						var websiteSystemId = lookupInfo.WebsiteSystemId;
						var fieldTemplate = _fieldTemplateService.Get<ProductFieldTemplate>(baseProduct.FieldTemplateSystemId);
						var displayTemplate = _displayTemplateService.Get<ProductDisplayTemplate>(fieldTemplate.DisplayTemplateSystemId);
						var templatePath = displayTemplate.Templates.FirstOrDefault(x => x.WebSiteSystemId == websiteSystemId)?.Path ?? displayTemplate.TemplatePath;
						if (templatePath != null)
						{
							routeInfo = new ProductRouteInfo(templatePath, baseProduct, category) { Variant = variant };
							return true;
						}
					}
				}
			}

			return result;
		}

		public override bool TryGet([NotNull] CategoryRouteLookupInfo lookupInfo, out RouteInfo routeInfo) => _parent.TryGet(lookupInfo, out routeInfo);

		public override bool TryGet([NotNull] DomainNameRouteLookupInfo lookupInfo, out DomainNameRouteInfo routeInfo) => _parent.TryGet(lookupInfo, out routeInfo);
	}
}
2 Likes

Looks like it working great! This should change the urls for product feeds aswell right?
Thanks Nils!

I didn’t test that, but yes, it should, since that one calls the UrlService as well.

1 Like

I’ve got the same request from a customer, and tried the above solution but only the first part of it works. UrlServiceDecorator works as it should and gives me new URLs for each product, but it looks like RoutingService was removed in later versions of Litium (this customer is running 7.7.1). My guess is that I would need to decorate PageRouteResolver instead, is that correct? If so, I’d gladly accept any pointers in the right direction to help me get started with this!

Regards,
Felix

It should work with 7.7.1 as well, make sure the Litium.Application package (that has RoutingService) is added to the project.

I finally got around to test this, and it seems to be working. Not sure what the issue was the first time around but this time it is working just fine. Thanks!