Strongly Typed Models (3) - Value Converters
Posted on July 6, 2015 in umbraco
This post is part no. 3 of a series that contains:
In the previous posts, we have explained what the original problem was, how models could help us and what types of model existed. To gain a better understanding at models, we need to understand properties, and property conversion.
GetPropertyValue<T>(string alias)
What happens exactly when that method is invoked? Roughly:
var property = content.GetProperty(alias);
return property == null ? null : property.GetValue<T>();
GetProperty
returns an IPublishedProperty
object, and its actual implementation depends on the cache implementation: the original Xml cache relies on a FirstOrDefault(x => x.Alias == alias)
which might not be optimal, performance-wise, whereas the NuCache performs an IDictionary<string, int>
lookup to obtain an index from the alias, then retrieves the property from an array of properties.
GetValue<T>
retrieves property.Value
and, if it is not already of type T
, tries to convert using type converters.
property.Value
is where the converters magic take place. In its most basic implementation, getting that property would:
- Obtain the converter from the property type
- Run the converter's methods:
ConvertDataToSource
to obtain the "source" from the "data"ConvertSourceToObject
to obtain the "value" from the "source"
The "value" is what you want, and the "data" is what is in the database. The "source" is an intermediate value—just ignore it for the time being.
How does that impact models?
If you use wrapper models, with properties looking like:
public IHtmlString TextBody
{
get { return this.GetPropertyValue<IHtmlString>("textBody"); }
}
you will pay a small price every time you get the value, because that property needs to be retrieved somehow. Not a big price, but nevertheless. If you use POCO models, with properties looking like:
public IHtmlString TextBody
{
get; set;
}
getting the value will be as cheap as it can be. However, conversion will run for all properties at the time your model is created.
In other words: with POCO models, you pay the full conversion cost once for every properties, and then getting a property value is cheap. With wrapped models, you pay the full conversion cost (once) only for those properties you actually get, but getting a property value is a (little) bit more expensive.
Is one better than the other? Impossible to tell. Performance will probably depends on many, many factors: how you code, what your models look like, what you views look like... Benchmarking only could tell for sure—and only in some precisely defined situations.
Conversion, and caching
More about the "conversion cost": conversion is what turns a CSV string into a list of integers, a Google Maps position, or any meaningful object. Conversion does not run anytime the value of a property is retrieved—there is some cache involved.
Each property value converter can specify a property cache level for the values it produces:
None
- do not cache the valueRequest
- cache the value for the duration of the current requestContentCache
- cache the value for as long as no content (nor media) changesContent
- cache the value alongside the content itself
The Content
cache level is appropriate for eg converting a CSV string into a list of integers. As long as the input string does not change (ie as long as the content item does not change), the converted value does not change either.
The ContentCache
cache level is appropriate for eg converting an identifier into an IPublishedContent
(think content picker). As long as nothing changes, the converted value does not change either. As soon as something changes, it becomes hard to tell (and we do not want to track cache dependencies at that level), so better convert again.
The Request
cache level is appropriate for eg converting an RTE content into HTML. We have no idea of what will happen during conversion, really. That RTE content could contain macro code, that could render the current date and time... We probably want it stable during the current request, and that is all.
At that point, it is easier to understand why we have the "source" intermediate value: it is possible to specify that the source value is cached at Content
level (eg, CSV string to list of integers), and the final value is cached at ContentCache
level (eg, list of integers to list of IPublishedContent
).
Help! You're losing me!
This may all sound quite complex. But the high-level conclusion is:
Umbraco already does its best to ensure that conversions run in the most efficient way, and the purpose of strongly typed models is not to add an extra cache layer. Unless you have a real, benchmarked, performance concern that cannot be addressed by fixing Umbraco, you most probably do not want to code your own cache layer.
You can write the following code, and know that the conversion from the CSV string to the GoogleMapLocation
object will run only once.
<div>@Model.GetPropertyValue<GoogleMapLocation>("map").Latitude</div>
<div>@Model.GetPropertyValue<GoogleMapLocation>("map").Longitude</div>
<div>@Model.GetPropertyValue<GoogleMapLocation>("map").Zoom</div>
Or, with a model:
<div>@Model.MapLocation.Latitude</div>
<div>@Model.MapLocation.Longitude</div>
<div>@Model.MapLocation.Zoom</div>
Oh, and with the current Xml cache implementation, that would be only once per request, because new IPublishedContent
objects are created for each request. But with the NuCache, that would be only once... at all, because the IPublishedContent
object is cached for as long as it does not change.
The point here was to give a detailed and technical overview of the various concepts to have in mind when dealing with content models, so that you can make the best decision based upon your project, your coding style, and how your brain works.
Next time we will talk about how we can make things simple again.
There used to be Disqus-powered comments here. They got very little engagement, and I am not a big fan of Disqus. So, comments are gone. If you want to discuss this article, your best bet is to ping me on Mastodon.