limo
Feb 12, 2018
  5658
(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
Dynamic packages in Commerce Connect

In Optimizely Commerce Connect, you can group different items using packages and bundles. Package: A package has one or more versions of a product...

K Khan | Nov 1, 2024

Efficient Catalog Metadata Management and Product Updates Using DTOs in Optimizely Commerce

This post explores ways to manage and update catalog metadata in Optimizely Commerce by utilizing Data Transfer Objects (DTOs). DTOs provide a...

Sujit Senapati | Oct 31, 2024

Effortlessly Resize Images with Cloudflare's On-the-Fly Solution

Resizing images in C# has traditionally been a complex and time-consuming task, often requiring intricate code and handling various image processin...

Manoj Kumawat | Oct 31, 2024 | Syndicated blog

XSS Vulnerabilities Patched with TinyMCE 6.8.4

Two different XSS vulnerabilities were fixed in the latest update of the NuGet package EPiServer.CMS.TinyMce. Update today!

Tomas Hensrud Gulla | Oct 30, 2024 | Syndicated blog

Opticon 2024 London - Key Highlights and Insights

Welcome to Onederland. Discover the key highlights from the Opticon Conference 2024 in London, including the NetSpring acquisition, advancements in...

Raghavendra Murthy | Oct 30, 2024 | Syndicated blog

Mastering clearing Cache in Optimizely CMS with ISynchronizedObjectInstanceCache & MemoryCache

In the fast-paced world of web development, efficient memory management is essential for optimizing application performance and enhancing user...

Sujit Senapati | Oct 28, 2024