Try our conversational search powered by Generative AI!

limo
Feb 12, 2018
  5346
(2 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
Optimizely and the never-ending story of the missing globe!

I've worked with Optimizely CMS for 14 years, and there are two things I'm obsessed with: Link validation and the globe that keeps disappearing on...

Tomas Hensrud Gulla | Apr 18, 2024 | Syndicated blog

Visitor Groups Usage Report For Optimizely CMS 12

This add-on offers detailed information on how visitor groups are used and how effective they are within Optimizely CMS. Editors can monitor and...

Adnan Zameer | Apr 18, 2024 | Syndicated blog

Azure AI Language – Abstractive Summarisation in Optimizely CMS

In this article, I show how the abstraction summarisation feature provided by the Azure AI Language platform, can be used within Optimizely CMS to...

Anil Patel | Apr 18, 2024 | Syndicated blog

Fix your Search & Navigation (Find) indexing job, please

Once upon a time, a colleague asked me to look into a customer database with weird spikes in database log usage. (You might start to wonder why I a...

Quan Mai | Apr 17, 2024 | Syndicated blog