Some gotchas with PropertyGroups

Property group classes must have an empty constructor

I think this is a bug or perhaps rather a missing feature. But in it’s current state you can’t inject constructor arguments even when you use the StructureMapTypedPageActivator. I’ve opened a pull request that “solves/add” this feature so you can get a general idea of what is needed at the github page.

Programatically saving a page that has property groups

Given the following simple page with a property group:

[PageType]
public class TestPage : TypedPageData
{
    [PageTypePropertyGroup]

    public virtual OneTest OneTest { get; set; }
}

public class OneTest : PageTypePropertyGroup
{
    [PageTypeProperty]
    public virtual string Phone { get; set; }
}

Let’s say we want to edit the Phone property on a page programmatically. Seems simple enough.

var page = DataFactory.Instance.GetPage(new PageReference(175)).CreateWritableClone() as TestPage;
page.OneTest.Phone = "asd";
DataFactory.Instance.Save(page, SaveAction.ForceCurrentVersion, AccessLevel.NoAccess);

And as expected this gives… a… System.NotSupportedException: The property OneTest-Phone is read-only. Wat?

CreateWritableClone and TypedPageData

This unexpected result boils down to the fact that CreateWriteableClone is not virtual (could this be a change for a reunion tour of the virtual boys?). If we look at the implementation of this method on the TypedPageData class we can notice two important things:

 
public new PageData CreateWritableClone()
{
    PageData page = base.CreateWritableClone();
    IEnumerable<PropertyInfo> properties = page.GetType().GetPageTypePropertyGroupProperties();
    new TypedPageActivator().CreateAndPopulateNestedPropertyGroupInstances(page as TypedPageData, page, properties, string.Empty);
    return page;
}

On line 1 we can se that since the PageData implmenation of CreateWriteableClone is not virtual we have to use the new keyword. We can also see that there’s some special handling regarding property groups.

So what’s the problem?

We we call CreateWriteableClone on an object that’s typeed as PageData the PageData implemenation of the method will be executed (even though the page is technically of type TypedPageData or something deriving from it). When you load a page via the standard DataFactory.GetPage(…) it will return a PageData and thus execute the standard CreateWriteableClone mthod which does not make sure that the proeprty group properties are writeable.

For this to work we must make sure that the type of the page has been casted to TypedPageData (or derived) BEFORE creating the writeable clone. In slightly horriffic code that would mean something like this

var page = (TestPage)((TestPage)DataFactory.Instance.GetPage(new PageReference(175))).CreateWritableClone();
page.OneTest.Phone = "asd";
DataFactory.Instance.Save(page, SaveAction.ForceCurrentVersion, AccessLevel.NoAccess);

You could clean it up a bit with an extension method

var page = DataFactory.Instance.GetPage(new PageReference(175)).ToWriteable<TestPage>();

public static T ToWriteable<T>(this PageData page) where T : TypedPageData
{
    return ((T) page).CreateWritableClone() as T;
}