Strongly Typed Models (2) - Models

Monday, June 29, 2015 9:24 AM umbraco

This post is part no. 2 of a series that begins here.

In the previous post, we have explained what the original problem was, and how dynamics could help address it—but only to some extend. Time to look for other solutions. What we basically want is to extend IPublishedContent with our own properties.

PublishedContentModel

The PublishedContentModel class is an abstract base class defined in Umbraco, which basically wraps any IPublishedContent instance. In its most basic usage, you could create your own class:

public class MyModel : PublishedContentModel
{
  public MyModel(IPublishedContent content)
    : base(content)
  { }
}

And create your own object:

IPublishedContent content = ...;
var myModel = new MyModel(content);

and use myModel everywhere you would use content, in the same way. It has the same properties, methods, everything—in fact, it implements IPublishedContent. Totally useless, but cool anyway. Things become more interesting when you start adding your own properties to that class:

public class MyModel : PublishedContentModel
{
  // ctor omitted

  public IHtmlString TextBody
  {
    get { return this.GetPropertyValue<IHtmlString>("textBody"); }
  }
}

Because then the following works:

@{
  var myModel = new MyModel(Model.Content);
}
<div>@myModel.TextBody</div>

Woohoo, problem solved! Or—to a point. We now have to write those model classes for each of our content types, and we have to create new objects in our views. And here, people go into two directions, depending on how their brain works:

Two types of models

Both wrapper and POCO models are classes that inherit from PublishedContentModel and implement some extra properties. They differ in how they implement these properties. A wrapper model wraps calls to GetPropertyValue:

public IHtmlString TextBody
{
  get { return this.GetPropertyValue<IHtmlString>("textBody"); }
}

Whereas a POCO model simply implements auto-properties:

public IHtmlString TextBody { get; set; }

A wrapper model tends to be verbose, and tedious to write wheras A POCO model is much simpler and easier to understand. A wrapper model is "self contained", ie once instanciated it can be immediately used, whereas a POCO model requires some preparation as by default the properties would be empty.

That preparation can either be done from within the model's constructor, which then becomes slightly more complicated:

public MyPocoModel(IPublishedContent content)
{
  this.TextBody = this.GetPropertyValue<IHtmlString>("textBody");
}

Or via a mapper:

var model = PrepareModel<MyPocoModel>(content);

At that point, to fully understand the implications of both model types, we need to talk about property value converters: it will be the topic of our next post.

More about models

But before we move on, let's disgress. If you are even remotely familiar with MVC, you know that the "M" stands for "model". View model, actually. The object that contains all data you need to render your view. In the "pure" MVC way of doing things, you would never, ever, retrieve some content from the cache in a view. Should you need to navigate the tree to build up the menu, you would do that in a controller, and pass the result to the view.

And this is where things become confusing. What Umbraco normally passes to the view is an IPublishedContent object representing the content that is being rendered, ie the page you are trying to display. As soon as we start talking about models the way we have done it in this post, people think... "ah, now we are passing a model to the view, so let's add the menu data to that model".

Wrong. The models we are talking about here are domain models, they are a richer representation of one item of content, and nothing more. It is, of course, perfectly OK to complement that representation with additional calculated properties. Imagine that, for whatever reason, the surface of images is something important to you. You could add the following property to your image model:

public int Surface
{
  get
  {
    return this.GetPropertyValue<int>("umbracoWidth")
      * this.GetPropertyValue<int>("umbracoHeight");
  }
}

On the other hand, a complete menu structure does not belong to one specific content item. Basically, anything that references other content or media items does not belong to the models we are talking about. You do still need to create proper view models, if you are so inclined, which would embed proper content models.

The distinction is important. Almost everything we are dealing with in this series of posts assumes that a "model" is a small, local, representation of a content or media item—and could become quite complicated, or simply break, if that was not the case.

comments powered by Disqus