Introduction

EPiServer.DataFactory.GetPage and EPiServer.DataFactory.GetChildren always return read-only PageData instances. EPiServer CMS developers need to create writable clones (e.g. EPiServer.Core.PageReference.CreateWritableClone in order to modify the retrieved objects.

Why?

The reasons for this implementation are:

Read-only handling has been added for the EPiServer.Core.PageData class and all contained classes. All classes that have the read-only support implements the IReadOnly<T> interface which is defined as follows:

CopyC#
public interface IReadOnly
{
    void MakeReadOnly();
    bool IsReadOnly
    {
        get;
    }
}

public interface IReadOnly<T> : IReadOnly
{
    T CreateWritableClone();
}

To give some background to what is happening behind the scenes, the life cycle of a typical PageData object looks like this:

  1. A PageData object is created as mutable (i.e. writeable).
  2. The PageData object is populated with properties.
  3. A call is made to EPiServer.Core.PageData.MakeReadOnly. This ensures that any contained objects are made read-only. From here on, the object is now immutable for the remainder of its lifetime.
  4. The object is added to the DataFactory cache.

When you request a page you will get a reference to the immutable object from the cache. If you need to make changes to the page or call EPiServer.DataFactory.Save(PageData, SaveAction), you must call EPiServer.Core.PageData.CreateWritableClone to retrieve a deep copy read/write version of the object.

Related Changes

The introduction of immutable PageData objects also made it necessary to introduce some other subtle changes that may affect the behavior of application code. GetPage/GetChildren previously added dynamic properties and "fetch-data-from" properties to the copied PageData object before passing it back to application code, but since we are reusing the same immutable PageData object we cannot add properties to the object.

To keep the dynamic property/fetch-data-from functionality with immutable PageData the logic for EPiServer.Core.PropertyDataCollection (the data type behind Pagedata.Property) has been modified to look for dynamic properties at request time rather than at PageData construction time.

This means that when you do a Page.Property["SomeProperty"] the indexer on EPiServer.Core.PageDataCollection will be called. This indexer will use the static delegate GetHandler on PageDataCollection to retrieve the requested property. The default Get handler is implemented by the DefaultPropertyHandler method of the EPiServer.Core.PropertyGetHandler class. This implementation enables an application to set the GetHandler property and replace the default algorithm with custom behaviour. Do note that such a change may easily result in very obscure bugs that can be hard to find.

The dynamic nature of the index Get method allows the retrieval of properties that wouldn't be returned from enumerating through the PropertyDataCollection. Dynamic properties are only returned by GetHandler, not present in the collection.
A similar issue is that there is no guarantee that the Set method of the index can be used to change something that has been retrieved with the Get method.

PageReference Changes

The LoadRawData property on PageReferences has been used to load PageData from DataFactory without adding dynamic properties/fetch-data-from. This property has been removed since it no longer serves any purpose.

If you want to get raw information from a page the recommended method is to call PageData.Property.Get("propertyname") rather than calling PageData.Property["propertyName"]. The Get method will bypass the GetHandler delegate and simply return data from the collection.

Similarly on PageData you also have an indexer, PageData["propertyName"], that directly returns the Value of a PropertyData. It will be affected by dynamic properties etc. To get the "raw" values you need to call PageData.GetValue("propertyName").