valdis
Jan 21, 2012
  4246
(2 votes)

“Stringly typed” Composer functions

When you need to create EPiServer Composer block that requires some properties we usually use Composer functions that basically are defined as page types. Ted has a really good blog post on step-by-step walkthrough how to create Composer functions using PageTypeBuilder.

Story today behind Composer functions is about so called “stringly-typed interface” approach :)

Somewhere up in your inheritance path you will have a base class called Dropit.Extensions.Core.BaseContentFunction which will give you possibility to access designed properties and its assigned value on the Composer page. Code for accessing property value usually looks like this:

 

var count = (int)ContentFunctionData["MyFunctionItemCount"];

 

It would be cool to write something like this:

 

var count = FunctionData(f => f.MyFunctionItemCount);

 

This is pretty simplified sample code. Anyway to follow more defensive programming practices we should after receiving value back we should check for null, if return value is not null, then we can cast it to PropertyData and check Value property to finally get access to property value.

 

if (count != null)
{
    var pd = count as PropertyData;
    if (pd != null)
    {
        var finalValue = ((PropertyData)pd).Value;
    }
}

 

The issue here is around indexer accessor that we use to access property data.

It would be great to have approximately the same experience as accessing page properties when using PageTypeBuilder.

This can be easily implemented using lambda expressions.

We should implement some kind of base class to be used for providing lambda expression approach when accessing property data from composer block.

Class will be inheriting from BaseContentFunction.

 

public class ComposerContentFunctionBase<T> : BaseContentFunction where T : ComposerFunctionBase

 

Class ComposerFunctionBase is base class mentioned in Ted’s blog which is required if building Composer functions with page type builder.

 

public abstract class ComposerFunctionBase : TypedPageData
{
    /// Required by Composer - do not remove
    [PageTypeProperty(Type = typeof(ExtensionFunctionProperty), DefaultValueType = DefaultValueType.None, DefaultValue = "",
        SortOrder = 101, DisplayInEditMode = false, Searchable = false, UniqueValuePerLanguage = true, Tab = typeof(ComposerTab))]
    public virtual string ExtensionContentFunctionProperty { get; set; }
    /// Required by Composer - do not remove
    [PageTypeProperty(UniqueValuePerLanguage = false, DefaultValueType = DefaultValueType.None, DefaultValue = "false", SortOrder = 122,
        Searchable = false, DisplayInEditMode = false, Tab = typeof(ComposerTab))]
    public virtual bool NeverUsedProperty { get; set; }
}

 

So after implementing base class we are able to derive some child and make our Composer user control.

 

public partial class MyComposerControl : ComposerContentFunctionBase<MyComposerFunction>

 

Next thing is to write implementation of FunctionData method in ComposerContentFunctionBase class to accept lambda expression for accessing composer content function data.

 

public R FunctionData<R>(Expression<Func<T, R>> expression)
{
    if (expression == null)
    {
        throw new ArgumentNullException("expression");
    }

    object result = null;
    var expressionBody = expression.Body;
    if (expressionBody.NodeType == ExpressionType.MemberAccess)
    {
        var propertyName = ((MemberExpression)expressionBody).Member.Name;
        result = ContentFunctionData[propertyName];
    }

    // if using PageType builders actual expression type to access PageType property will be substituted by Castle proxy
    if (expressionBody.NodeType == ExpressionType.Convert)
    {
        if (((UnaryExpression)expressionBody).Operand.NodeType == ExpressionType.MemberAccess)
        {
            var propertyName = ((MemberExpression)((UnaryExpression)expressionBody).Operand).Member.Name;
            result = ContentFunctionData[propertyName];
        }
    }

    if (result == null)
    {
        return default(R);
    }

    if ((result as PropertyData) == null)
    {
        return default(R);
    }

    if (((PropertyData)result).Value == null)
    {
        return default(R);
    }

    return (R)((PropertyData)result).Value;
}

 

So what we are doing here? We are waiting for incoming parameter to be Expression<Func<T, R>>. Let’s split up into smaller pieces:

1. T is the Composer function data type we typed when implementing user control. So this will be incoming parameter of lambda expression.

2. R is returning type from the lambda expression or data type of which function data property is defined.

 

So in this expression: (f => f.MyFunctionItemCount)

f –> T (this is of type MyComposerFunction)

MyFunctionItemCount –> R (function property is defined as int).

 

3. Then we are investigating given expression and looking for member access node. NB! This implementation is pretty simplified and does not handle more complex expressions that result in member access somehow.

4. Expression investigation has 2 if statements. Second (when node type is of type Convert) is required because Castle proxies are behind the scene when using PageTypeBuilder and we need to dig deeper into expression to find member access expression. NB! This implementation handles only 1 level within expression tree. Probably not the best solution anyway.

5. Then we are investigating collected result and returning the return value.

 

Now we can write expressins like this in out Composer function user control and get proper type casting also.

 

int count = FunctionData(f => f.MyFunctionItemCount);

 

If you would like to get precise info whether editor supplied value for the function property or not we can always use Nullable types to get new method to work properly when returning default(R).

 

 

Pretty simple solution but most probably not 100% bulletproof.

Hope this helps!

Jan 21, 2012

Comments

Please login to comment.
Latest blogs
Customizing Property Lists in Optimizely CMS

Generic property lists is a cool editorial feature that has gained a lot of popularity - in spite of still being unsupported (officially). But if y...

Allan Thraen | Oct 2, 2022 | Syndicated blog

Content Delivery API – The Case of the Duplicate API Refresh Token

Creating a custom refresh provider to resolve the issues with duplicate tokens in the DXC The post Content Delivery API – The Case of the Duplicate...

David Lewis | Sep 29, 2022 | Syndicated blog

New Optimizely certifications - register for beta testing before November 1st

In January 2023, Optimizely is making updates to the current versions of our certification exams to make sure that each exam covers the necessary...

Jamilia Buzurukova | Sep 28, 2022

Optimizely community meetup - Sept 29 (virtual + Melbourne)

Super excited to be presenting this Thursday the 29th of September at the Optimizely community meetup. For the full details and RSVP's see the...

Ynze | Sep 27, 2022 | Syndicated blog