IPublishedContentModelFactory

    Thinking about writing your own model classes or extending those generated by Models Builder? Make sure you read the Content Factory & Content Cache section below, in order to fully understand what to do, and not to do.

    The IPublishedContentModelFactory has been made public with Umbraco 7.1.4. It defines one single method:

    public interface IPublishedContentModelFactory
    {
      IPublishedContent CreateModel(IPublishedContent content);
    }
    

    Anytime the content cache has to create an object representing a content (media, or member), it will by default use an internal class implementing IPublishedContent (e.g. XmlPublishedContent for content coming from the XML cache). When a models factory is enabled, the content object will be passed to the factory, so it can convert/map it to an object of a different type. It is this object that is then returned by the cache.

    The most basic factory would do nothing, e.g.:

    public IPublishedContent CreateModel(IPublishedContent content)
    {
      return content;
    }
    

    The returned object must implement the IPublishedContent interface, which can be complex. In order to facilitate this, Core supplies some base classes (see below PublishedContentModel, PublishedContentExtended and PublishedContentWrapped).

    The returned object must only depend on the original content object. Content models are not view models and must be stateless, with regards to the current activity (current request, current culture...). In other words, the factory should not use information about the current request, current culture, whatever, to alter the returned object. Another way to say it: It should be possible for the factory to generate the model when Umbraco boots, and the model could be kept in memory for as long as the underlying content is not edited.

    Factories should not cache models, i.e. a factory should create a new object each time it is asked for one. Reason: At the moment we want models to inherit from PublishedContentModel so they implement IPublishedContentExtended and we need a new one each time we create a model. Currently looking into whether we can drop that constraint for future versions of Umbraco.

    Enabling a factory

    As of 7.1.4, Umbraco ships with no factory enabled. There is a default factory in Umbraco, but it is not mandatory to use it. Below you can see how a factory can be implemented.

    As with everything in Umbraco, a factory is enabled via a resolver. This is how the default factory is enabled:

    public class ConfigurePublishedContentModelFactory : ApplicationEventHandler
    {
      protected override void ApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
      {
        var types = PluginManager.Current.ResolveTypes<PublishedContentModel>();
        var factory = new PublishedContentModelFactory(types);
        PublishedContentModelFactoryResolver.Current.SetFactory(factory);
      }
    }
    

    When you install Models Builder, the default factory is automatically enabled. If you do not want to use Models Builder and yet you want to use the default factory (with your own model classes), you will need to explicitly enable it.

    Using the default factory

    The default factory must be initialized with a list of types to be used as strongly typed models. These types need to obey the following rules:

    • Each type must inherit from PublishedContentModel
    • The factory will try to map the type class name to a content type alias, unless the type is marked with the [PublishedContentModel("alias")] attribute

    Implementing IPublishedContent

    Implementing the IPublishedContent interface from scratch can be complex. Umbraco ships with various classes that can help:

    • PublishedContentWrapped: An abstract class that wraps an existing IPublishedContent without adding any extra functionality. Use the Unwrap() method to access the inner content. This is the base class for all classes that need to extend existing content.
    • PublishedContentExtended: Inherits from PublishedContentWrapped and provides functionality to "extend" a content, i.e.: 1) add some properties to it and 2) support content sets.
    • PublishedContentModel: Inherits from PublishedContentExtended without adding any functionality. Is used to distinguish models from other extended content.

    Note: Need to understand and document why PublishedContentModel inherits from PublishedContentExtended and not directly from PublishedContentWrapped?

    Should you want to implement your own models classes, the easiest solution is to inherit from one of those three classes.

    Content Factory & Content Cache

    As of Umbraco 7.1.4, anytime a content is retrieved from the cache, a new IPublishedContent object is created, passed through the factory, and returned. This is how the current content cache works. Which means that that content is local to the current request.

    This is not best in terms of efficiently. It implies that a lot of objects are created, each time parsing the XML content in the cache. It means that each request runs the property value converters, etc. In fact, depending on how the cache is queried, several copies of the same object could be created.

    Ultimately, the idea is to cache the content objects themselves. Which would mean that the content returned by the cache is global to the entire application, and that the same content model object is returned to all requests for as long as that content does not change. Because that object is shared by requests, property values are converted once, which is more efficient.

    Which prompts rule number 1: Content models must be stateless with regards to the current request. If you add a property to a model, and the value of that property depends on the current request, then you must not store that value as a field. It must be re-calculated each time, or stored in a cache at the request's level.

    A cache that would cache the content objects, would refresh (replace) the object corresponding to a content item, when that content item is modified (published). So, a new object is created, property values will be converted again, etc.

    Things become more complicated when a content references another content. Say, content A contains a content picker that has been used to pick content B, and that picker has a value converter that directly returns a content object. If content B is unpublished then... content A does not change yet the value returned by the property must change.

    The content cache would take care of that situation for converted properties automatically (using hints given by the converter via the PropertyValueCacheAttribute attribute). But anything else, any custom reference stored, e.g. in a field, would not be refreshed and therefore would be out-of-sync.

    Which prompts rule number 2: Content models must not keep references to other contents in local variables. If you want to implement properties or methods that return references to other contents, you can:

    • Re-run the query each time
    • Cache the IDs in a field and re-get the content objects each time
    • Implement your own caching mechanism

    The idea is that such a cache would provide easy-to-use infrastructure for you to cache values either at the request's level, or "for as long as no other content is changed". The same that would be used internally for the cache to manage the converted values.

    This is not an issue today but it will become an issue if/when a new cache comes. Better understand what's going on before creating models that would not work with a new cache.