I am currently working on a project where it was necessary to conditionally inject some content into certain properties on some pages. Dynamic Content didn't really help me because what I wanted to do was replace entire properties with content from a property on another page and do it in a nice way for the editor. Linking the page with a shortcut to another page didn't help me because it needed to be only a partial replace and also conditional.
Here's an example - and I know there are better ways to do this but I want an example that won't compromise the Intellectual Property of what I am actually doing :)
Lets say you have a news article at http://myepisite/News/SomeArticle. The article has a nice big image on it and you want to be able to swap the image out with a different one (lets forget the fact that you'd probably just do this with two properties). You have a sub-page storing the smaller image in a property at http://myepisite/News/SomeArticle/LowImagery, which is hidden from the site. The way that you trigger the load of the low image is by URL QueryString, e.g. http://myepisite/News/SomeArticle?bandwidth=lo. (If you want some more tips on achieving this URL neatly, see my posts or other posts on URL rewriting.) Anyway, you could write a custom property type etc. to do this, but that's a fair whack of work and you'll need to create a new custom property for every content type that you might want to replace.
So how do I achieve this in a neat, generic way? EPiServer provides a nice way, but it's not the most well documented route and neither do they really recommend it. Still, I think it can work well if properly implemented. The way I implemented to achieve this was using a custom Property Get Handler.
When EPiServer asks for a page, it builds a PropertyDataCollection of all the properties on that page, along with their values. This PropertyDataCollection contains all the user-defined properties along with some that EPiServer add in as well such as PageLink, PageLanguageBranch and others. These are absolute lifesavers, although they are not well documented. The PropertyDataCollection has a default indexer which, when called, calls to a helper method to get the actual PropertyData for a property. The default helper method is a static method called 'DefaultPropertyHandler' on EPiServer.Core.PropertyGetHandler. This helper method looks for the the actual property data in four stages:
- It looks at the property on the current page and the current language
- If that is empty, it checks to see whether the property is language specific and, if it is, whether the current language is the master language. If not, then it grabs the master language page and returns the value from that property.
- If language isn't an issue, then it tries to get the property value from the linked page, if any.
- If there is not data on a linked page, then it tries to return a dynamic property value with that property name.
This works well and is the default that probably fits 99% of EPiServer installations. However, EPiServer kindly allow us to replace this default Property Get Handler. All you need to do is write your own method that matches the delegate for the handler, and then set an EPiServer property like so:
EPiServer.Core.PropertyDataCollection.GetHandler = new GetPropertyDelegate(Test.BandwidthPropertyGetHandler.BandwidthPropertyHandler);
I'm sure you can set this in most places where you need it, but I just put it into the Application_Start of Global.asax.cs. So we can see how easy it is to override the property handling, but what does our custom method actually look like? The following code is incomplete, untested and partially commented out, but it should give the idea. The two parameters coming in are the property name being queried for data and the property data collection for the current page/language. Notice that rather than use the default indexer for properties, when asking for a property data value I am explicitly calling the 'Get' method - this prevents nasty infinite loops where my custom handler keeps calling itself.
namespace Test
{
public static class BandwidthPropertyGetHandler
{
// Methods public static PropertyData BandwidthPropertyHandler(string name,
PropertyDataCollection properties)
{
// check querystring to see if we need to replace property (where possible) HttpRequest Request = System.Web.HttpContext.Current.Request; if (Request.QueryString["bandwidth"] == "lo")
{
// we need to swap low bandwidth if it's the right property name // you could drive this any way you like - don't hardcode it in production :) if (name == "ArticleImage")
{
// lets reach out and grab the data from the sub-page PageReference ThisPageReference = (PageReference)properties.
Get("PageLink").Value;
PageDataCollection ChildPages = EPiServer.DataFactory.Instance.
GetChildren(ThisPageReference, LanguageSelector.MasterLanguage());
foreach (PageData ChildPage in ChildPages)
{
// find an article subpage if (ChildPage.PageTypeName.ToLower() == "article subpage")
{
// grab our property and return it return ChildPage.Property.Get("ArticleImageLow");
}
}
}
}
// just return the default handler return PropertyGetHandler.DefaultPropertyHandler(name, properties);
}
}
}
In my actual implementation I handle languages properly, error handling, null property values and all sorts of other things, but it should be fairly obvious how that would work. In essence, this is a very simple way of customising properties to make them do fairly clever things without customising property types themselves. In fact, this would be a nice way to retro-fit a feature such as bandwidth tailoring or other targeted content to an existing type. Just remember that if it's not a special case you should be handling, call the default handler to do it's stuff with that property! Also, it might be best to try to keep your injected property types the same as the property type on the owner page. Not sure what would happen if you messed with that.