Don't miss out Virtual Happy Hour this Friday (April 26).

Try our conversational search powered by Generative AI!

Standard UrlSegment class/generator not generating language-specfic url segment for commerce items

Vote:
 

Hello everyone!

We are just about to finish our new website using EPiServer Commerce for the first time integrated with a brand new PIM (InRiver).

We are using their standard adapter for the integration and it is generating an XML in the format documented here: http://world.episerver.com/documentation/Items/EPiServer-Service-API/Using-bulk-import-and-export-methods/Using-the-catalog-service/ that EPiServer imports.

The problem is that inRiver does not automatically generate URLs for us, and that doesn't sound like something the PIM system should be doing anyways.

It does provide a way where you can put SEO-URIs into the PIM and it will transfer over to EPiServer but there's 2 problems with this:

  • We are already translating and putting in names for everything, we want the URLs to automatically match that (just converting those names to URL-safe versions).
  • We are not using SEO-URIs, we are using hierachiral routing (UriSegments) and there's absolutely no support at all for that (from the InRiver adapter).

In fact, the XML from inRiver will never ever generate an UriSegment tag in the XML. Even though the EPiServer documentation for the XML that I linked says that it is required (which it obviously is not).

So what happens is that EPiServer will automatically generate an UriSegment for each entry (since it is required). But the standard implementation of this (the EPiServer.Web.UrlSegment class). Only uses the "Name" property to generate these segments. In EPiServer Commerce for some reason, this property is no longer language-specific (which it is in EPiServer CMS).

Instead it uses something else for this called "DisplayName". All our DisplayNames are translated to 8+ languages. But the URLs for all these commerce items are not language specific (they are all based on non-translatable "Name" field).

I think I've manged to build a module as a workaround for this issue now. Which hooks into the DataFactory.Instance.PublishingContent event and tries to replicate the behavior of the UrlSegment class for all commerce items that are not in the master language, but using "DisplayName" instead of "Name".

If anyone is interested I could post it here.

But I feel that this is pretty ugly. Is it really intended behavior that the UrlSegment class will not automatically generate language-specific url-segments for commerce content, but it does for CMS content?

Feels like a bug or oversight for me. Or am I missing something here?

#131296
Jul 20, 2015 14:53
Vote:
 

If someone is interested here is my full workaround currently. It has not recieved more than ~30 mins of testing from myself yet creating various pages and trying to create collisions but I hope I've managed to get rid of all the problems with it. If someone sees a problem with this code though, please post!

namespace Piab.CMS.Business.Initialization
{
    using System.Linq;

    using Castle.Core.Internal;

    using EPiServer;
    using EPiServer.Cms.Shell;
    using EPiServer.Commerce.Catalog.ContentTypes;
    using EPiServer.Core;
    using EPiServer.Framework;
    using EPiServer.Framework.Initialization;
    using EPiServer.Web;

    using JetBrains.Annotations;

    /// <summary>
    /// This module is a workaround for EPiServer CMS UrlSegment class/generator not beeing aware of the "DisplayName" property in EPiServer Commerce and EPiServer Commerce not having a language specific "Name" property.
    /// </summary>
    [InitializableModule]
    [UsedImplicitly]
    public class CommerceUrlSegmentGenerator : IInitializableModule
    {
        public void Initialize(InitializationEngine context)
        {
            DataFactory.Instance.PublishingContent += InstancePublishingContent;
        }

        static void InstancePublishingContent(object sender, ContentEventArgs e)
        {
            var commerceContent = e.Content as CatalogContentBase;
            if (commerceContent == default(CatalogContentBase) || commerceContent.IsMasterLanguageBranch())
            {
                // We only need to work with commerce items which are not in the master language.
                return;
            }

            // Now we know that we are on a non-master language version of an EPiServer commerce item

            // WTF, what is wrong with this version? It doesn't return the correct language version?
            //var masterVersion = DataFactory.Instance.Get<CatalogContentBase>(commerceContent.ContentLink, commerceContent.MasterLanguage);


            var masterVersion = DataFactory.Instance.GetLanguageBranches<CatalogContentBase>(commerceContent.ContentLink).First(b => b.Language.Equals(b.MasterLanguage)); // This looks more ugly but seems to work.
            if (!commerceContent.RouteSegment.EqualsText(masterVersion.RouteSegment))
            {
                // The route segment already differs, do nothing (either we already changed it or the user did).
                return;
            }

            var generatedRouteSegment = UrlSegment.GetUrlFriendlySegment(commerceContent.GetPropertyValue("DisplayName"));

            // Since we were not able to use UrlSegment.GetUniqueURLSegment, which would check for collisions for us, we will have to do it ourselves:
            var siblings = !ContentReference.IsNullOrEmpty(commerceContent.ParentLink) ? DataFactory.Instance.GetChildren<CatalogContentBase>(commerceContent.ParentLink, commerceContent.Language).ToArray() : new CatalogContentBase[0];
            
            var routeSegment = generatedRouteSegment;
            {
                var i = 1;
                while (siblings.Any(s => s.RouteSegment.EqualsText(routeSegment)))
                {
                    routeSegment = string.Format("{0}{1}", generatedRouteSegment, ++i);
                }
            }

            commerceContent.RouteSegment = routeSegment;
        }

        public void Uninitialize(InitializationEngine context)
        {
            DataFactory.Instance.PublishingContent -= InstancePublishingContent;
        }
    }
}
#131297
Jul 20, 2015 14:56
Vote:
 

Since a picture is worth more than a thousand words, here is also a small little picture illustrating the problem.

URL language problem

I'm currently browsing the swedish version of the page here, viewing swedish versions of the commerce items. But the whole commerce-part of the URL is in english?
While the automatic generation of URLSegments have always been working great for us on the CMS part of the site...

#131298
Jul 20, 2015 15:01
Vote:
 

Today I tried this module I wrote with the InRiver adapter and the EPiServer Commerce Manager XML import functionality. Unfortunately this does not seem to trigger the DataFactory.Instance.PublishingContent event. So my workaround here does not actually help me. It only works when a user manually publishes a new version of a page.

So I'm back to zero I guess :-(.

Is there noone else having this issue with URLs not being translated?

How do others work? You don't care about URLs being translated? Or you actually translate every single URL by hand?

It looks very weird on our website when only the CMS parts of the URLs are translated but the commerce parts are not.

#131343
Jul 21, 2015 13:41
Vote:
 

I can confirm that the Uri segment generation is a bit weird when using inRiver PIM. To have full control over how it is generated, we take care of it ourselves.

If you look up PreImport in the inRiver wiki you will discover that by implementing ICatalogImportHandler, you can modify the XML before it is handled by EPI. In this method, you can generate the UriSegment based on DisplayName, using EPI's API and modify the XML document on the fly, before it is parsed by EPI.

#131666
Aug 01, 2015 0:03
Vote:
 

Thanks Johan.

Even though this solution still isn't perfect it's a lot better than the workaround I had in mind to build some kind of batch or scheduled job to fix all the segments after an import.

I have now written some code in the PreImport function that will add a proper UriSegment using the DisplayName property from the XML with the standard

string EPiServer.Web.UrlSegment.GetUrlFriendlySegment(string segment)


method. I haven't written any code that checks for sibling name collisions though as I do not want to make it overly complicated and/or slow down the import/export process too much. (That code would have to both try to search for collisions in the XML and items previously published to EPiServer).

A full unpublish+published just finished now and all the URLs seem to be properly translated, finally.

I still feel that the UrlSegment class from EPiServer CMS should be made aware of the DisplayName property from commerce though. That way all of this hackish code would not be needed.

Patching this method with DisplayName awareness should fix all of this and shouldn't require all that much work either:

string EPiServer.Web.UrlSegment.GetUniqueURLSegment(IContent content)



#131730
Aug 04, 2015 13:14
Vote:
 

The default implementation is to use the Name as a basis for UriSegment, which might be suitable in some scenarios, but for the majority (not to say all) of the multi-language sites I've been working on, the product names are actually translated and hence the UriSegment should follow too. It's a delicate issue, since the same logic is used for all content - not only catalog content. Making the change to using DisplayName instead of Name as basis for UriSegment might have other consequences... O_o

But this also explains the behavior when you create a new translated version of a page - it will automatically get the UriSegment of the master language, something you always manually need to change which is kind of frustrating...

Oh, and regarding duplicates - as you prolly already have noticed, this is handled automatically when the import is performed in Episerver (see GetUniqueURLSegment). For every product that is imported, it will perform a check (run the SQL profiler to see the fun stuff) for uniqueness. If it is not unique, it will simply append a counter to the end, e.g. product-1, product-2 so no need to handle this yourself.

#131763
Aug 05, 2015 2:01
Vote:
 

Actually we did get some duplicates and EPiServer did not block it when importing the XML. The duplicates were imported and we ended up with some unreachable/unroutable pages because of this.

This was because we ended up with a duplicate UriSegment for a product and when searching for a specific variant that only existed under one of these products that URL became unreachable.

We are not supposed to have products with duplicate names though inside the same category, so I retrieved list with all found duplicates and sent it to our marketing department.

If you try to edit these duplicates in EPiServer it won't allow you to save without first editing the UriSegment so that it doesn't collide with something else. The logic you are talking about on GetUniqueURLSegment is there but it won't run unless the UriSegment is left empty in the XML.

In EPiServer CMS the DisplayName property does not exist, for some reason Commerce decided to make Name non-language specific and instead add a DisplayName property. This feels really confusing and illogical. I really don't see a reason for this behaviour. To me if feels like a bad artifact still remaining after EPiServer bought Mediachase and Commerce. This makes the two products not feel fully homogeneous.

#131766
Aug 05, 2015 9:25
This topic was created over six months ago and has been resolved. If you have a similar question, please create a new topic and refer to this one.
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.