When the Link Field Renderer and Link Manager disagree about the link URL in Sitecore

While upgrading a Sitecore solution from 7.1 to 8.2, it was discovered that the rendering of a general link field, referencing an internal item, could strip the URL under a set of specific circumstances.

Given that the internal item link had a hash inserted into its URL, meaning that this isn't set explicitly as an anchor attribute on the link field, everything after (including the hash) would get stripped when the link field was rendered out using the default link field renderer.

The background story

To give some context, the Sitecore solution my colleague Kristian Gansted and I were working on contained a significant amount of items based on a specific item template. Each of these items needed a hash (or anchor) inserted into the last part of the URL. Since this wasn't something the content editors should be tasked to do, the default link provider had been extended, such that it could perform the logic and return the correct URL (containing the hash) automatically.

Smile, then sadness

However, when the field was rendered out using the default link field renderer, the result of rendering out the link field looked like this http://www.something.com/domain, instead of http://www.something.com/domain#foobar.

To our surprise it was also discovered, that if the URL of the item was retrieved using the link manager directly, the item URL was retrieved correctly with the hash included.

So, what changed somewhere along the way to Sitecore 8.2?

In order to figure out what caused this behaviour, we started going over the most recent versions and updates released by Sitecore, trying to figure out what was changed. Since the link manager was able to resolve the URL correctly, our initial thought was that the change had to be somewhere in the GetLinkFieldValue pipeline.

While going through the different layers of the code used by the pipeline, it turned out that Sitecore did change the implementation on how links were retrieved inside the pipeline. The change was found in the LinkUrl class and was introduced as part of the Sitecore 8.1 initial release.

Prior to Sitecore 8.1, the implementation of the GetInternalUrl method looked like this:

Basically, the URL of the item is simply retrieved by calling the link manager (which internally uses the custom link provider, previously mentioned), and then appending any anchor and query string parameters to it.

Now, looking at how the implemention of the same method looks in Sitecore 8.1 (and above) this yields:

Compared to the previous implementation, Sitecore wraps the item URL retrieved from the link manager in an UrlString object, and thereby let the UrlString do the heavy-lifting of parsing the URL correctly. Inside the UrlString constructor, the passed URL is parsed whereas the hash provided through the custom link provider is correctly stored. However, notice that there is a subtlety in the way the UrlString is constructed.

No way!

The default constructor of the UrlString object uses an additional object initializer to set both the parameters, and the hash to the anchor of the link. But wait, this doesn't work in the case of having an URL containing a hash, since we aren't explicitly setting an anchor on the link field - this is done by the custom link provider. This means that the first call to the default constructor will interpret and set the hash correctly, but then the hash is removed from the URL due to the object initialization where the hash is overridden with an empty value which strips away the hash along with anything after the hash in the URL along with it.

Alternative facts?

Although I appreciate that Sitecore 8.1 (and above) handles the parsing of the item URLs a more robust manner, I find it a bit unfortunate that the link field renderer and the link manager, returns two different results in terms of retrieving the URL. Looking at it from the perspective of responsibility, it would make sense letting the link field renderer render the item URL in the exact same way the link manager does, otherwise we are left with alternative facts in terms of which item URL is the correct one.

Without judging which of the URLs are the correct one, it would be great if the logic could be streamlined such that rendering the link would return the same URL as retrieving the URL using the link manager. Based on this, a support ticket has been created at Sitecore with the following ID: 478348.

Working around the problem

While waiting for Sitecore support to get back, we decided to apply a minor workaround. Doing so, we simply reverted the logic for retrieving the item URL back to the previous Sitecore 8.0 logic, as described in the following.

Step 1: Create a custom link url

Create a custom link url implementation that extends from the LinkUrl class and override the default implementation of the GetInternalUrl method. The implementation uses the "old" logic for retrieving the url for the link field:

Step 2: Create a custom link renderer

Create a custom link renderer that extends from the LinkRenderer class which uses the custom LinkUrl class created in step 1:

Step 3: Create a custom GetLinkFieldValue render pipeline

Create a custom GetLinkFieldValue render pipeline and override the CreateRenderer() method such that it uses the custom LinkRenderer created in step 2:

Final words

In terms of the proposed workaround, I would have liked it better, if I could set the anchor in a different way, when I construct the URL in the custom link provider. However, I haven't found a good solution to this, so if you have any details on how this can be done, please drop me a note in the comment section below.