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.
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;
}
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);
}
}
}
}
Comments