World is now on Opti ID! Learn more

limo
Feb 12, 2018
  848
(0 votes)

Access and modify the Remaining Url value on properties of type LinkItem, Episerver.Url and on UrlFragments in XHtmlStrings

Episerver version: 10.6.0

Episerver.CMS.UI version: 10.7.1

Background:

Since there was no Remaining Url segment available for Episerver.Url, LinkItem or XHtmlString fragment (i.e. link added in TinyMCE) in Episerver CMS prior to Episerver.CMS.UI version 10.6.0, we had implemented our on solution which was as follows:

For Links added in XHtmlString (tinymce) and for LinkCollectionItems, both based on LinkModel, we used the built-in Title property for typing in anchors or additional querystring parameters, and a custom tinymce plugin and a custom displaytemplate that built the url and added the title to the end of the href. (This was possible because we did not use the Title property for its correct purpose in the first place due to other constraints in the site architecture.) 

For properties of type Episerver.Url we had an additional string property that we used for adding anchors or querystring parameters when writing out the link in the view.

Now when Episerver has built-in support for RemainingUrl we wanted to switch to use this instead and remove our custom implementation. We wanted to do this using a scheduled job, since there were over 500 instances in our site content where the value was used and needed to be moved.

The questions I had to Episerver Support was:

1) How can I set the Query attribute of a property of type Episerver.Url, so that is available for the editors in the Remaining Url field in the Edit Link dialog? 

2) How can I set the Remaining Url property of a LinkItem or XHtmlString Fragment, so that it is available for the editors in the Remaining Url field in the Edit Link dialog?

I got excellent answers that helped me figure out the solutions below.

Please note that in the code examples below I am not checking wether there are values in the Query or Href properties already before adding our parameter values to it, as we had another separate script that checked this and assured it was empty.

Solution

Episerver.Url:

The Remaining Url value is stored in the Url.Query property. The Query property is read-only so in order to update an existing Url object you need to create a new Url object, set the Url.Path property value of the new Url to path value + query value and then replace the existing Url object with the new one. The Query property of the new Url will be automatically set by Episerver upon saving. The next time you access the Url programmatically the Query property will be set to the value you gave it, and the editors will see the value in the Remaining Url field in Edit View.

var itemsUsingLinkAddition = List<MyBlock>; //Pre-populated list of content items of type MyBlock that are using our custom property HeadingLinkAddition to add parameters to the URL.

foreach (var i in itemsUsingLinkAddition)
{
    var clone = i.CreateWritableClone() as MyBlock;
    try
    {
        if (!string.IsNullOrEmpty(clone.GetPropertyValue("HeadingLinkAddition")))
        {
            clone.HeadingLink = new Url(clone.HeadingLink.Path + clone.GetPropertyValue("HeadingLinkAddition"));
            clone.Property["HeadingLinkAddition"].Value = null;
        }

         _contentRepository.Save((IContent)clone, EPiServer.DataAccess.SaveAction.Publish, AccessLevel.Read);
    }
    catch (Exception)
    {
        //Log exception
    }
}

UrlFragment in XHtmlString:

There is no built-in method for this. You need to traverse the fragments and check if the fragment is of type UrlFragment. The UrlFragment is read-only so in order to update it you need to replace it with a new UrlFragment rather than update the existing. (In our case the value I wanted to get at was in the Title-fragment of the Url so I had to extract that from the previous fragment).

This is an example of how I updated xhtmlstring properties in pages. The same approach applies to Blocks, the only difference then is the need to cast to IContent before saving:

// Update the pages and publish/save depending on original status.
// pagesUsingUrl is a List<IContent> that I have filtered out prior to this step
foreach (var p in pagesUsingUrl)
{
    var contentItem = p.CreateWritableClone();

    try
    {
        for (var i = 0; i < contentItem.Property.Count(); i++)
        {
            if (contentItem.Property[i].Value != null && contentItem.Property[i].Value is XhtmlString)
            {
                XhtmlString xhtmlString = (XhtmlString)contentItem.Property[i].Value;
                for (var b = 0; b < xhtmlString.Fragments.Count; b++)
                {
                    if (xhtmlString.Fragments[b].GetType() == typeof(UrlFragment))
                    {
                        if (xhtmlString.Fragments[b] != null)
                        {
                            var originalUrlFragment = xhtmlString.Fragments[b].InternalFormat;
                            var thetitle = GetUrlFragmentTitle(xhtmlString.Fragments[b - 1].InternalFormat);

                            if (thetitle.StartsWith("?") || thetitle.StartsWith("#"))
                            {
                                // Update the url to have the title value as parameter
                                xhtmlString.Fragments[b] = new UrlFragment(originalUrlFragment + thetitle);

                                // Remove the original title value from previous fragment
                               var modifiedFragment = RemoveTitleFromFragment(xhtmlString.Fragments[b - 1].InternalFormat);

                               xhtmlString.Fragments[b - 1] = new StaticFragment(modifiedFragment);
                               xhtmlString.IsModified = true;
                           }
                       }
                   }
                }
            }
        }

        switch (contentItem.Status)
        {
            case VersionStatus.CheckedIn:
                _contentRepository.Save(contentItem, SaveAction.CheckIn, AccessLevel.NoAccess);
                break;
            case VersionStatus.DelayedPublish:
                _contentRepository.Save(contentItem, SaveAction.Schedule, AccessLevel.NoAccess);
                break;
            case VersionStatus.Published:
                _contentRepository.Save(contentItem, SaveAction.Publish, AccessLevel.NoAccess);
                break;
            case VersionStatus.Rejected:
                _contentRepository.Save(contentItem, SaveAction.Reject, AccessLevel.NoAccess);
                break;
            case VersionStatus.AwaitingApproval:
            case VersionStatus.CheckedOut:
            case VersionStatus.NotCreated:
            default:
                _contentRepository.Save(contentItem, SaveAction.Save, AccessLevel.NoAccess);
                break;
        }
    }
    catch (Exception)
    {
         // Log exception
    }
}

private string GetUrlFragmentTitle(string fragment)
{
    // This fragment should be something like '<span><a title=\"?blahblabh\" href=\"', as when you dissect the xhtmlstring it looks like 'dkasjdkasjdkl <a title="?blahblabh" href="~/link/43284yrhf3ff3y437hgfgt3fgt.aspx">'

    var titleValue = string.Empty;
    var strTitleStartMatch = "<a title=\"";
    var strTitleEndMatch = "\"";

    if (fragment != null)
    {
        if (fragment.Contains(strTitleStartMatch))
        {
            var startOfTitle = fragment.LastIndexOf(strTitleStartMatch, StringComparison.Ordinal) + strTitleStartMatch.Length;
            var endOfTitle = fragment.IndexOf(strTitleEndMatch, startOfTitle, StringComparison.Ordinal);

            titleValue = fragment.Substring(startOfTitle, endOfTitle - startOfTitle);
        }
    }
    return titleValue;
}

Image UrlFragment.png

LinkItem:

The Remaining Url value of a LinkItem is stored in the Href property. It is not read-only hence can be set just by updating linkItem.Href to href value + query value and saving the page/block. The editors will see the value of the query parameters in the Remaining Url field in Edit View, Episerver is managing that for you.

var clone = p.CreateWritableClone(); //where p is a content of a type that inherits PageData

for (var i = 0; i < clone.Property.Count(); i++)
{
    if (clone.Property[i] is EPiServer.SpecializedProperties.PropertyLinkCollection)
    {
        var propertyLinkCollection = clone.Property[i].Value as LinkItemCollection;
        foreach (LinkItem linkitem in propertyLinkCollection)
        {
            if (!string.IsNullOrEmpty(linkitem.Title) && (linkitem.Title.StartsWith("?") || linkitem.Title.StartsWith("#")))
            {
                // Set the Remaining Url attribute to the value of Title
                linkitem.Href = linkitem.Href + linkitem.Title;

                _contentRepository.Save(clone as IContent, EPiServer.DataAccess.SaveAction.Publish, EPiServer.Security.AccessLevel.NoAccess);
            }
        }
    }
}


Feb 12, 2018

Comments

Please login to comment.
Latest blogs
Make Global Assets Site- and Language-Aware at Indexing Time

I had a support case the other day with a question around search on global assets on a multisite. This is the result of that investigation. This co...

dada | Jun 26, 2025

The remote server returned an error: (400) Bad Request – when configuring Azure Storage for an older Optimizely CMS site

How to fix a strange issue that occurred when I moved editor-uploaded files for some old Optimizely CMS 11 solutions to Azure Storage.

Tomas Hensrud Gulla | Jun 26, 2025 |

Enable Opal AI for your Optimizely products

Learn how to enable Opal AI, and meet your infinite workforce.

Tomas Hensrud Gulla | Jun 25, 2025 |

Deploying to Optimizely Frontend Hosting: A Practical Guide

Optimizely Frontend Hosting is a cloud-based solution for deploying headless frontend applications - currently supporting only Next.js projects. It...

Szymon Uryga | Jun 25, 2025

World on Opti ID

We're excited to announce that world.optimizely.com is now integrated with Opti ID! What does this mean for you? New Users:  You can now log in wit...

Patrick Lam | Jun 22, 2025

Avoid Scandinavian Letters in File Names in Optimizely CMS

Discover how Scandinavian letters in file names can break media in Optimizely CMS—and learn a simple code fix to automatically sanitize uploads for...

Henning Sjørbotten | Jun 19, 2025 |