...

Good ol' Dynamic Properties

There was a time, when men were made of steel, ships made of wood, Episerver was spelled with a weird capitalization and the CMS had something called Dynamic Properties that was usually misused. They've been gone for a while, but I miss them, so here's yet another attempt at solving the property inheritance challenge.

To those of you, my dear readers, that have no clue what Dynamic Properties were all about, let me begin with enlightening you:

Once upon a time, there was a mythical feature, loved by some and hated by many called Dynamic Propeties. Dynamic Properties were properties that were not set on the content itself, but rather could be inherited across all content types, throughout the content hierachy. A good example would be a dynamic property called something like "SecondLevelMenuRoot" that would be used to know which child objects should be listed in the sidebar menu. Usually, a super-user editor that could find his/her way into the secret edit menu for dynamic properties would then set the property to the each of the first level items. That way, the property would inherit down and all leaf nodes would show the second level menu corresponding to their place. And the world was a better place.

Sadly, evil forced tended to abuse these great powers and often ended up making many, many dynamic properties and use them for stuff like a LogoImageLink or SearchButtonText that really should have been a site setting instead. Since Dynamic properties had to be resolved dynamically (hence the name) that tended to slow down the sites quite a bit.

But, surely we are smarter now and ready to once again unleash this power, right? 

In any case, I needed some inheritance badly when building this blog. Why?

Well, here is a good use-case: On each blog post I have a sidebar content area. Usually I'll have the same blocks there on all blocks - but there'll probably one or two where I'll want something different placed there. I don't want to have to remember to put the same blocks in there for every single blog post. And I don't want it as 'Default' value upon creation, cause what happens when I decide to change the blocks?

I could perhaps make 1 giant Block to rule all blocks, that would contain another content area with the other blocks....But blocks-in-blocks are kind of ugly in my eyes and should be avoided whenever possible (yes I know, I've done blocks in blocks both with Forms and Self Optimizing Block - but those were exceptions, ok).

Enough talk, let's see some code. I decided that one of the best approaches would be if we could let the developers decide which properties should be inherited and how it should work. Something like this:

//Sidebar
[Inherit(InheritIfNullOrEmpty =true, ParentPropertyToInheritFrom ="DefaultChildSideBar", SearchAllAncestors =true)]
public virtual ContentArea SideBar { get; set; }

What's happening here is basically just that on my Blog Post content type, I'm telling it that this property value should be inherited, if it's not set (null or empty). It should try to inherit from the parent page, if the parent page has a property called "DefaultChildSideBar". If I hadn't specified that, it would simply look for a parent property of the same name as the current property. I also tell it to search all ancestors - so if the parent doesn't have that property set, it should look to the grandparent and so on.

Sometimes you might like to let the editor decide if a value should be inherited or not - in that case, you could add another boolean property on the page and specify it's name as "SwitchPropertyName" in the attribute.

I haven't yet decided on a good strategy for when to populate the properties - so for now, I've let it be up to the developers - they basically have to call an extension method on IContent that will attempt to populate the needed inherited properties.

public static T PopulateInheritedProperties<T>(this T Content) where T : PageData
        {
            var rt = (Content as IReadOnly).CreateWritableClone() as PageData;
            var props = Content.GetPropertiesWithAttribute(typeof(InheritAttribute));
            bool modified = false;
            foreach (var prop in props)
            {
                var attr = prop.GetCustomAttribute<InheritAttribute>(true);

                if (
                    (!String.IsNullOrEmpty(attr.SwitchPropertyName) && ((bool)Content.GetType().GetProperty(attr.SwitchPropertyName).GetValue(Content))) ||
                    ((attr.InheritIfNull || attr.InheritIfNullOrEmpty) && (prop.GetValue(Content) == null)) ||
                    (attr.InheritIfNullOrEmpty && ((prop.PropertyType == typeof(ContentArea)) && (prop.GetValue(Content) as ContentArea).Count == 0))
                    )
                {
                    //Resolve Inherited Properties
                    var repo = ServiceLocator.Current.GetInstance<IContentRepository>();
                    foreach(var a in repo.GetAncestors(Content.ContentLink).Take((attr.SearchAllAncestors)?1000:1))
                    {
                        var parentprop = (a as IContentData).Property[attr.ParentPropertyToInheritFrom ?? prop.Name];
                        if (parentprop!=null && !parentprop.IsNull)
                        {
                            prop.SetValue(rt, parentprop.Value);
                            modified = true;
                            break;
                        }
                    }
                }
            }
            if (modified)
            {
                rt.MakeReadOnly();
                return rt as T;
            }
            return Content;

        }

The attribute presents these options:

    public class InheritAttribute : Attribute
    {
        /// <summary>
        /// Name of Boolean property that indicates if this property should be inherited
        /// </summary>
        public string SwitchPropertyName { get; set; }

        /// <summary>
        /// Inherit this value if it's null
        /// </summary>
        public bool InheritIfNull { get; set; }

        /// <summary>
        /// Inherit this value if it's null or empty
        /// </summary>
        public bool InheritIfNullOrEmpty { get; set; }

        /// <summary>
        /// Name of property on parent content to inherit from. Default is same name.
        /// </summary>
        public string ParentPropertyToInheritFrom { get; set; }

        /// <summary>
        /// Keep searching ancestors until Root
        /// </summary>
        public bool SearchAllAncestors { get; set; }

    }

You can see the entire Gist below.

A word of caution

This is in no way a done solution - in fact, it's a very first draft. I just figured I'd share it here to get some feedback (which is very welcome in the comments below).

There are still many pieces to the puzzle missing. For example:

  • What about Blocks? What will they inherit from?
  • Should we also try to resolve inheritance recursively on parents with inherited properties?
  • When should we resolve? Currently I resolve inherited properties by calling the method in my Controller.
  • How can we alter the UI to make the editor aware that a certain property is begin inherited - and from where it's being inherited?

 

Blog Posts

...

Publicwww - searching for interesting Episerver CMS use patterns

I recently discovered publicwww.com a cool service that lets you search for any text in the html/css/js of all it's 550 million (2019-05-09) indexed web pages, including the cookies sent out and the http header. In this post I put my Episerver goggles on and had some fun with this data.

...

The Curious Case of Content Modelling

Having the right content model (the structure of your content types) is very important in order to end up with good, usable (and reusable) content. I believe that is something that most content management aficionados can agree on. But what is a good content model? And who should be modelling your content? In this blog post I will try to discuss a few opinions on this topic.

...

Episerver Content Provider Resilience Strategies

Content Providers for Episerver is a powerful tool with huge potential for integrations. But how should you handle fault resilience when dealing with a real time connection to an external system? As part of the integration to Digizuite DAM I have helped build, I have given this a great deal of thought.

...

Routing in Episerver Addon Modules

When your build your own addons, modules and extensions for Episerver CMS, you often want to include controllers - and naturally you want to call these controller - but which url should you use? I always forget this, so here is a little reminder.

...

Distributed Content: Delivering an Episerver Web Experience with Contentful Content

The move in the market towards headless could also be seen as a tendency towards a deeper decoupling between content and experience delivery. Inspired by a few discussions, I've tried my hands on an uncommon combination: Contentful providing content delivered through an Episerver web experience layer.

...

Annotate The Web - Collaborate with comments about websites

As a little experiment I just launched a basic annotation service, that let's you put comments on screenshots of a web page in order to collaborate during creation.

...

Episerver Advance Recommendations on CodeArt

Some times you have so much great content on your website that you just wish you had a librarian to let your visitors know what to read next. And with Episerver Advance (Content Recommendations) you can at least have something that comes pretty close. I have been lucky enough to try it out on my site.

...

Sneak Peek - Digizuite DAM for Episerver

In this blog post I'm sharing a little sneak peek of the editor experience working with an enterprise DAM like Digizuite integrated into Episerver. Early February 2019, at the Episerver Partner Close-up in Stockholm, you can visit Digizuite stand to get an in-depth demo.

...

On Page Edit for Images

The default image edit mode in Episerver CMS is a little bit boring - it's nothing but an image tag with the actual image. Why not offer a richer on page edit experience for image media as you typically get for pages and blocks?

...

Content Provider or Content Replication

When integrating external content into Episerver, a classic dilemma is whether you should replicate it in, or setup a content provider to pull it in real-time. As part of the Digizuite Integration I have once again given some thought to the dilemma - and here are some pro's and cons.

...

Content Report Generator

I helped a client with a cool little report generator that can give them an easy overview of all their content - and related metadata, that can be opened in excel and easily sorted, filtered and aggregated. Here it is.

...

Content Approval: Show Content Info box with Reviewers

Content approval in Episerver CMS has been around for a while now, but coding examples using it are still fairly hard to find. Here is a simple one that might come in handy.

Post Comments()