PureLive Models Introduction

Tuesday, February 16, 2016 11:12 AM , edited Monday, November 7, 2016 12:32 PM umbraco

Starting with version 7.4, Umbraco ships with PureLive Models enabled by default. This post tries to give a broad overview of what that means.

Before we begin, note that models are not enabled during upgrades. And, should anything go wrong, or in case you simply do not want models at all (eg because you would rather use another solution such as Ditto), models can be entirely disabled. Models are controlled by two essential appSettings entries (in web.config):

<add key="Umbraco.ModelsBuilder.Enable" value="true" />
<add key="Umbraco.ModelsBuilder.ModelsMode" value="PureLive" />

The first one acts as a giant kill-switch and is required for models to function at all. Remove it, or set its value to false, and models are entirely disabled. Umbraco.ModelsBuilder models, that is: it has no impact on other solutions, such as the previous Zbu.ModelsBuilder solution.

To activate models on an upgraded site, all you need to do is to copy those two lines.

Working with models

Assuming you have a content type with alias "product" and properties with alias "description" and "price", this is how you would render a content of that type in a template, without models:

@inherits UmbracoTemplatePage
<div>
  @Model.Content.GetPropertyValue("description")
  costs
  @Model.Content.GetPropertyValue("price")
</div>

Or maybe:

@inherits UmbracoTemplatePage
<div>
  @Umbraco.Field("description")
  costs
  @Umbraco.Field("price")
</div>

Once PureLive models are enabled, the views can be immediately simplified as:

@inherits UmbracoTemplatePage<Product>
<div>@Model.Content.Description costs @Model.Content.Price</div>

Or, even better:

@inherits UmbracoViewPage<Product>
<div>@Model.Description costs @Model.Price</div>

The class names (here, Product) derive from the content type aliases, and the property names (here, Description and Price) derive from the property type aliases. In most cases the property type (CLR object type) should match the property editor: a Date Time Picker property value should be a true System.DateTime value.

The CSharp code for the classes and properties is generated, compiled and loaded into the application automatically. Whenever content types are updated, the code is updated—yet the application does not restart. That, basically, is it: it just works.

How it works

The models builder creates the ~/App_Data/Models folder and manages some files in there:

Any *.cs file in that directory that is not a *.generated.cs file is parsed by the Models Builder, looking for attributes and instructions that can control how models are generated. Then, models are generated in models.generated.cs. Finally, models and all custom files are merged into one all.generated.cs file that is compiled in-memory as a dynamic assembly by ASP.NET BuildManager.

The "Eureka" moment I mentionned in my previous post was when we realized that one could keep asking BuildManager to recompile code that would redefine some CLR types, and the BuildManager would happily forget about the old type definitions and start using the new ones.

Being compiled in-memory means that there is no DLL, and models do not exist outside of the ASP.NET views. They cannot be used in custom code, controllers... and if you edit a template in Visual Studio, VS will complain about the non-existing model types. See Beyond PureLive below.

Because all model classes are partial (have a look at models.generated.cs) and the custom files are compiled along with the models, these custom files can be used to add properties to models.

Whenever changes are made to content types, models code is re-generated and re-registered in the application. When the application restarts, the current document type definitions are checked against the hash code in models.hash in order to bypass models generation if possible.

When things go wrong

In most cases everything works fine. However, code generation can be tricky. One example is names collisions. Imagine content type with alias "product" has a property with alias "product"—a perfectly fine situation, as far as Umbraco is concerned. The generated code would however need to look like:

public class Product
{
  public string Product
  {
    get { return this.GetPropertyValue<string>("product"); }
  }
}

Unfortunately, that code does not compile, as C# does not support members (the property) with names identical to the name of their enclosing type (the class). When models cannot be generated, or fail to compile, an exception is reported in the log file and models are ignored.

This way, the Umbraco backend continues to work, though obviously the website frontend probably fails, complaining about non-existing model types. Time to inspect the log file.

Beyond PureLive

PureLive models exist to provide an immediate, out-of-the-box experience of models. However, as explained above, they only exist within the context of views, and any other bit of code cannot see them: code you could put in ~/App_Code, controllers, Visual Studio... anything that is not a view. In addition, they inevitably cause some amount of runtime overhead, much like compiling code in ~/App_Code.

For all these reasons, you might hit a point where PureLive models will not be enough. The Models Builder provides other modes that give you greater control over models generation, such as building models in a DLL on disk or directly in Visual Studio—which we will cover in another post!

Edit: the "another post" is there!

comments powered by Disqus