Handle property fallbacks

Member since: 2007

Hi,

What is your best approach to handle property fallbacks in your Webforms projects? This is very easy to handle in an MVC project, just override the get method in your content type model. However, this doesn't work with Webforms since the property webcontrol doens't use the model to fetch the property value. Instead it work against the PropertyDataCollection directly.
This is how I usually handle it when I want the PageHeading to have PageName as fallback:

[PagePlugIn(
    "Property fallbacks",
    "Handles fallbacks for properties on a global basis.")]
public class PropertyFallbacks
{
    private static Hashtable FallbackProperties { get; set; }

    public static void Initialize(int optionFlags)
    {
        PropertyDataCollection.GetHandler = FallbackPropertyHandler;

        FallbackProperties = new Hashtable { { "PageHeading", "PageName" } };
    }

    public static PropertyData FallbackPropertyHandler(string name, PropertyDataCollection properties)
    {
        var data = properties.Get(name);

        if (data != null && (!data.IsNull || data.IsMetaData))
        {
            return data;
        }

        var dataFromOtherPage = PropertyGetHandler.FetchDataFrom(name, properties);

        if (dataFromOtherPage != null && dataFromOtherPage.Value != null)
        {
            return dataFromOtherPage;
        }

        if (FallbackProperties.ContainsKey(name))
        {
            var dataFallback = FallbackPropertyHandler(FallbackProperties[name].ToString(), properties);

            if (dataFallback != null && dataFallback.Value != null)
            {
                return dataFallback;
            }
        }

        return DynamicPropertyCache.DynamicPropertyFinder.FindDynamicProperty(name, properties) ?? data;
    }
}


But now I've run into some issues. E.g. when using the Language add-on and when the editor choose to copy the English content to another language. Then the PageHeading property will get a value, even though the English version didn't have a value in the PageHeading propery.

#146744 Mar 22, 2016 13:15
  • Member since: 2005

    I use a separate viewmodel in many (often older) webforms projects where I collect everything special special that should be displayed on page including css classes etc and regard the logic necessary as part of presentation logic. So in my case it would go in my GetDefaultViewmode() method where I create my viewmodel. If it's a common problem on many pages, I would add an interface to pagetypes with GetHeading or similar and use that instead for getting of heading.  My viewmodels normally don't contain currentpage default properties, I use currentpage straight off for that to save some typing. If I need to add some logic to it or get from external data sources I gather everything to a viewmodel kind of object and use that instead. Been a while now since I had to get dirty with webforms though :) 

    #146751 Mar 22, 2016 15:12
  • Member since: 2007

    Still, that approach doesn't work with the property webcontrol and on-page-edit. Otherwise I could simply do:

    <%: CurrentPage.PageHeading ?? CurrentPage.Name %>

    #146752 Mar 22, 2016 15:18
  • Member since: 2005

    For dope support and webforms, custom renderers is an option

    http://world.episerver.com/Blogs/Linus-Ekstrom/Dates/2012/10/Custom-renderers-for-properties/

    or simply connect your control to your property with 

    someControl.ApplyEditAttributes<StartPage>(x => x.Links);

    in code behind. Depends on what version you are using...

    To be honest, I usually skip that part for some properties like that. Depends on the customer of course. EPiServer 6 and before wasn't very focused on dope support and I never heard a complaint about it.

    From EPiServer 7 it's more or less required though :)

    #146755 Edited, Mar 22, 2016 16:45
  • Member since: 2007

    Creating a custom rendering control sure does the job, I think. But that won't solve the problem if a devloper is trying to access the property from an indexer, e.g. CurrentPage["PageHeading"]. But that might not be a problem.

    We already have a bunch of custom rendering controls, so one more won't hurt :)

    #146756 Mar 22, 2016 17:00
  • Member since: 2005

    :)

    #146757 Mar 22, 2016 17:17
  • Member since: 2007

    This is how I solved it. You need to decorate the property in the content type with the UI hint UIHints.Heading (a constant with the value 'heading'). I also added an extra guard and checked the property name, which probably is a bit overkill and not so nice to have hard-coded.

    [TemplateDescriptor(
        Default = true,
        Inherited = true,
        TagString = UIHints.Heading)]
    public class HeadingControl : PropertyStringControl, IRenderTemplate<string>, IRenderTemplate
    {
        protected override bool ShouldCreateDefaultControls()
        {
            // We should always create default controls if we're rendering
            // PageHeading, since we will fall back on PageName which always
            // has a value.
            if (this.IsPageHading())
            {
                return true;
            }
    
            return base.ShouldCreateDefaultControls();
        }
    
        public override void CreateDefaultControls()
        {
            this.Controls.Add(this.GetControl());
        }
    
        public override void CreateOnPageEditControls()
        {
            this.Controls.Add(this.GetControl());
    
            if (this.PropertyIsEditableForCurrentLanguage())
            {
                this.ApplyEditAttributes();
            }
        }
    
        private Control GetControl()
        {
            var tag = this.CustomTagName ?? "h1";
    
            var value = this.ToWebString();
    
            // Fall back on PageName if PageHeading is null
            if (string.IsNullOrEmpty(value) && this.IsPageHading())
            {
                var page = this.CurrentContent as PageData;
    
                if (page != null)
                {
                    value = page.Name;
                }
            }
    
            var control = new HtmlGenericControl(tag)
            {
                InnerHtml = value
            };
    
            this.CopyWebAttributes(control);
    
            return control;
        }
    
        private bool IsPageHading()
        {
            return this.PropertyData.Name.Equals("PageHeading");
        }
    }

    #146759 Mar 22, 2016 17:51
  • Member since: 2005

    Looks good!

    #146765 Mar 23, 2016 9:54