...

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

...

Hack-Your-Future Copenhagen - Awesome Concept!

In the center of Copenhagen a team of volunteer IT professionals are teaching groups of passionate, dedicated and talented refugees, asylum seekers and immigrants coding skills, to help them land jobs in an IT industry starving for skilled workers. It's working - and it's an awesome concept!

...

Heading to Helsinki Dev Meetup

Thursday, November 8th I'm really excited to once again have been invited to join the Episerver Developer Meetup in Helsinki!

...

Design Pattern: Tag Pages instead of Categories

Episerver categories is one way to deal with taxonomy on a web site. But often I find that I prefer a simpler, more transparent approach of having Tag Pages replace them. Here's how.

...

FB2PDF - How Abandonware Gets Distributed

A long time ago I did a small weekend project to fix a problem my wife was having with her e-reader. I shared it on my blog and then forgot all about it. Until now, that is.

...

CMS Audit Update - Visitor Groups included

Earlier this year Nicola Ayan released a nice little plugin that I instantly liked, the CMS Audit tool. It's a great way to get an easy overview over what is being used where in your CMS. In a talk about my favorite addons I showed it at Episerver Ascend Copenhagen and straight away got a question from the audience: Can we use this tool to see where visitor groups are being used? Well, now you can.

...

Webcast: Addons In Real Life @ CodeArt

Last week I did a couple of talks at Episerver User Group meetings in Denmark about how I've tweaked my Episerver installation at codeart.dk in order to work as a great blogging platform. I also showcase a few of the addons I'm currently working on. Now I recorded the talk, so if you have a 23 minutes to spare, then grab a coffee and make some popcorn and have a look.

...

Custom Views for an Interface

Ever played around with adding custom views in Episerver CMS? It's a really powerful way to extend the UI. But why does it work when you register your view for a model class, but not for an interface implemented by that model? I had a look and found out.

...

Admin Mode Plugin to Manage Content Type Suggestions

If you have a site with a lot of different content types, it can be a good idea to help Episervers Automatic Content Type suggestion feature along. Here is a basic Admin mode tool - in good old webforms (yes, I washed my hands after I made it) that will let administrators / and super-editors configure exactly which content types to suggest when.

...

Auto Tagging Using Search

You don't always have to go the full AI route to get AI like results. In this blog post I'll describe an approach I've used several times (and for multiple purposes) with pretty decent results. Instead of classification algorithms, deep learning or neural networks I'll just simply query my favorite search engine.

...

Which Dojo Topics are published in the Episerver CMS UI?

If you, like me are venturing into the mythical and magical world of dojo-in-episerver-ui, where elfs and wizards rule, you might also find this list useful.

...

Storage Performance Aftermath - ElasticSearch Joins the Fight

In 3 previous blog posts I compared various azure storage technologies with regards to performance and scalability in typical web usage scenarios. I was actually done with the series, but with all that interesting data, I decided to throw my current favorite search/storage/no-sql technology into the mix to get an idea about how it all compares. So - ElasticSearch enters the competition!

...

Azure Storage Performance Showdown (Post 3)

This is the 3rd post in my Azure Storage Performance comparison. So far we've examined the typical scenario of storing/retrieving data that most dynamic websites of today deal with. In this post, we'll take a closer look at Update and Delete - and finally review the financial aspects.

Post Comments()