-
Notifications
You must be signed in to change notification settings - Fork 49
IPublishedContentModelFactory
WARNING: Thinking about writing your own model classes or extending those generated by the 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
(eg XmlPublishedContent
for content coming 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 that object that is then returned by the cache.
The most basic factory would do nothing, eg
public IPublishedContent CreateModel(IPublishedContent content)
{
return content;
}
The returned object must implement the IPublishedContent
interface, and that 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.
WARNING: Factories should not cache models, ie 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.
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. Implementing a factory is fairly simple -- though there are constraints on models (see below).
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 the models builder, the default factory is automatically enabled. If you do not want to use the models builder and yet you want to use the default factory (with your own model classes), you will need to explicitely enable it.
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 the IPublishedContent
interface from scratch can be complex. Umbraco ships with various classes that can help:
-
PublishedContentWrapped
: A very simple abstract class that just wraps an existingIPublishedContent
without adding any extra functionnality. Use theUnwrap()
method to access the inner content. This is the base class for all classes that need to extend existing content. -
PublishedContentExtended
: Inherits fromPublishedContentWrapped
and provides functionnality to "extend" a content, ie 1) add some properties to it and 2) support content sets. -
PublishedContentModel
: Inherits fromPublishedContentExtended
without adding any functionnality. 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.
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 just 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 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 eg 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 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.