Strongly Typed Models (4) - Models Factory

Tuesday, July 14, 2015 10:20 AM umbraco

This post is part no. 4 of a series that contains:

In the previous posts, we've looked at models, property value converters, and all the gory technical details. At that point, we want to go back to the original problem, which was... making things simple.

From what we have seen so far, provided that we have written the proper model classes, we can simplify our views:

@inherits UmbracoViewPage
  var content = new MyModel(Model);

Or (using a mapper tool, such as Ditto):

@inherits UmbracoViewPage
  var content = Model.As<MyModel>();

If your way of thinking favors creating multiple small POCO models, and mapping the original IPublishedContent to exactly the model you need for a specific purpose, then the second syntax is probably fine for you. If, on the other hand, you would rather work with a one-to-one model for each content type, then you would prefer the first syntax. And in that case... we want to simplify it further so it becomes:

@inherits UmbracoViewPage<MyModel>


The above code would require that Umbraco magically handles the creation of the model objects. And that is exactly the purpose of the models factory. The IPublishedModelFactory interface exposes only one method, CreateModel, which accepts an IPublishedContent instance (the one the cache would return) and returns another IPublishedContent instance (your model).

Once a factory is active, every content that comes from the cache first goes through the factory. Meaning that the cache suddenly returns your models. To views, controllers, anything. Out of the box.

There is no factory enabled by default, and you are free to implement your own. Umbraco ships with a factory, PublishedContentModelFactory, which needs to be explicitly activated (eg with an ApplicationEventHandler) with code looking like:

IEnumerable<Type> types = ...; // your model types
var factory = new PublishedContentModelFactory(types);

It matches the content types to model classes by name (ie for content type alias MyContent it will look for class MyContent) unless otherwise specified using the PublishedContentModelAttribute:

public class MyModel
{ ... }

And that is basically it: it then all just works. The original problem we introduced in part 1 is solved—at a cost, because model classes still need to be created. But at least, our views are now fully strongly-typed and clean.

Does the factory make sense with mapper tools such as Ditto? Technically, it can work. However, the way I understand it, Ditto offers better control over finer-grained models, and you probably want to map explicitly. That is, content may be of type BlogPost, but you want to map it to ContentWithImage in one place (to render the image), and ContentWithTextBody in another place. At least, that is how I think it works.

Models graph = StackOverflow

The built-in factory is not re-entrant. The creation of a model (ie, its constructor) should not trigger the creation of other models—it can work, but only until it loops and tries to create a model that it is already creating, and enters an infinite loop that ends with a StackOverflow exception.

Implementing a factory that detects and supports model graphs is an exercise left for the reader, and it is probably not that complicated. However, in my mind it sorts of defeats the purpose of the content models. Again: a content model is a local, domain model for one content. Of course, properties can return other models (eg for a content picker), but there is no reason a model constructor should pre-build lists of content.

The most common source of confusion is when people want to pass "menu items" to their master view. When rendering, say, a blog post, what the view will get is a BlogPost model, and then they would like to have a BlogPost.MenuItems property containing all the menu items. This is a BadThing™ as menu items are not a property of the blog post.

Model lifecycle

With the current Xml-based content cache, new models are created for every request. Which means that a model lives for the duration of the request and then gets collected, and models are not shared. However, this is not guaranteed and another cache implementation may cache models for as long as it thinks it is possible, and share the same model objects between many requests.

This means that models should be stateless, with regards to a given request. As seen in part 3, this does not prevent a property from returning a different value for each request, depending on the value converter cache level. But if your model class has, say, an instance-level property that you modify when processing a request... this is not thread-safe. It will work with today's Xml-based content cache, but it will fail on other caches such as NuCache.

Creating Models

As mentioned above, one last thing stands in the way to simplicity: creating the model classes. Although some people love to write code, I would love to have my classes created automatically. In fact, I would love Umbraco to create classes for me anytime a content type is modified.

The good thing with the "models" approach is that this is an entirely orthogonal question. How you answer the model classes creation question has little to do with how you use these models, making it easier to address.

There are various solutions around. The next post will focus on my own Zbu.ModelsBuilder solution, explain how it works, and why it is not part of Umbraco Core.

comments powered by Disqus