Sunday, 17 August 2008

EPiServer, MultipageProperty -- don't use SelectedPages property!

I don't know how it works for you, but I can't imagine life without MulipageProperty. I use it in most of our projects and that is great because I love flexibility which it offers. Recently while checking MulitpageProperty source code I found something worrying -- if you check how PropertyMultiPage.SelectedPages property is implemented then you will find that for each internal page following method is invoked:



private PageData GetInternalPage(PageData pd, MultipageLinkItem multipageLinkItem)
{
//make sure pagedata object is writeable
pd = pd.CreateWritableClone();

// We must have detected a change, and have a link text
if (multipageLinkItem.EditorHasChangedLinkText == true &&
string.IsNullOrEmpty(multipageLinkItem.LinkText) == false &&
multipageLinkItem.LinkText.Trim().Length > 0)
{
AddPropertyHelper(pd, "PageName", new PropertyString(multipageLinkItem.LinkText));
// We should also be able to test if the link text has been changed
// from the outside, so we add a flag that indicates that the PageName
// has changed
AddPropertyHelper(pd, "LinkTextHasChanged", new PropertyBoolean(true));
}


PropertyFrame frameProp = new PropertyFrame();
frameProp.FrameName = multipageLinkItem.Target;
AddPropertyHelper(pd, "PageTargetFrame", frameProp);

AddPropertyHelper(pd, "PageLinkToolTip", new PropertyString(multipageLinkItem.Tooltip));

// It needs to have published status, or it may be filtered away
AddPropertyHelper(pd, "PageWorkStatus", new PropertyNumber((int)VersionStatus.Published));

// It should look like something we just fetched
pd.IsModified = false;

return pd;
}



What is the problem with that method? Problem is here:


pd = pd.CreateWritableClone();


Do we really need to create writable version of PageData for each page? Well, as you can see in above code, page is "extended" with additional properties, which you may know from MulipageProperty's dialog. Probably, without creating writable version of page, "extending" is not possible. It's of course quite cool feature but you have to be aware that there are performance consequences of calling CreateWritableClone() method.

So what should I do?

I'm far from saying that we should rewrite this piece of functionality or stop using MulitpageProperty at all ... that would be crazy!
But I would like to suggest using SelectedLinkItems instead of SelectedPages property. PropertyMultiPage.SelectedLinkItems property returns MultipageLinkItemCollection object, single MultipageLinkItem object contains all necessary page related data including URL, link target, link text etc.

Take a look on PropertyMultiPage.SelectedPages property implementation to see how you can use MultipageLinkItem object to get everything what you need:



PageDataCollection SelectedPages
{
get
{
....

// All link items
MultipageLinkItemCollection linkItems = this.SelectedLinkItems;

foreach (MultipageLinkItem multipageLinkItem in linkItems)
{

PageReference pageref = PageReference.ParseUrl(multipageLinkItem.Url);
PageData page = null;

// ParseUrl also work for fully qualified urls (http://...) which
// we will never have for our own pages. To qualify as an internal
// EPiServer page, the parse must be successful, and the url must
// start with "/". If we cannot load these pages, they will be
// removed from the collection altogether.
if ( ! PageReference.IsNullOrEmpty(pageref) && multipageLinkItem.Url.StartsWith("/"))
{
// get the page with error handling for
// access denied or deleted page
try
{
// Could be language sensitive
if (EPiServer.Configuration.Settings.Instance.UIShowGlobalizationUserInterface)
{
// First we check if we have a specific language to load
if (string.IsNullOrEmpty(multipageLinkItem.LanguageId) == false)
{
// Load page, with specific language
page = EPiServer.DataFactory.Instance.GetPage(
pageref, new LanguageSelector(multipageLinkItem.LanguageId));
}
else
{
// Load page, with master language fallback
page = EPiServer.DataFactory.Instance.GetPage(
pageref, LanguageSelector.AutoDetect(true /* enableMasterLanguageFallback */));
}
}
else
{
page = DataFactory.Instance.GetPage(pageref);
}

}
catch (PageNotFoundException notFoundEx)
{
...
}
}

....
}
return _selectedPages;
}
set
{
_selectedPages = value;
}
}


I hope that it gives you a good idea how to use MultipageLinkItem class and how to improve performance of your applications. Maybe that will also inspire someone to spend some spare time tweaking PropertyMultiPage.SelectedPages implementation to not use CreateWritableClone() method.

1 comment:

Lars said...

Hi there! have you found any reason why the MultipageLinkItem.Url does not contain eplanguage= the selected language code?. I'm using the SelectedLinkItems quite often and i find it kind of strange that you need to insert the eplanguage stuff yourself if you are using the MultipageLinkItemCollection in datalists etc when using expressions like Eval(Url)