Strong Typed Umbraco Mapping with UmbMapper

Greetings and Salutations!

It's not often I write code I'm immediately happy with and proud of but this happened to me just recently so I thought I'd share it with the community.

As I'm sure you know, dear reader, I'm a big fan of a pretty sweet CMS made by some fantastic friends of mine in Denmark called Umbraco. If you haven't played around with it, please do, it stands out, in my opinion, as easily the most usable, flexible, and fun to work with CMS in the NET ecosystem by a country mile.

I'm always looking for inventive ways to work with it and to make it even easier for others to do so as well so when I had a flash of inspiration lying, flu-ridden on my couch I had to pop a couple of pills, crank open the laptop, and furiously tap away to produce what I'm sharing.

A Little History

I've been writing mapping code for Umbraco for a few years now with two of the big guns in the Umbraco package scene, Lee and Matt. You might have heard of the package we produced together called Ditto

I'm proud of what we achieved with Ditto. I think we've managed to push attribute based mapping about as far as it can go, crafting a very performant, very flexible system that has served me well (and I think many others) over the years.

I even wrote an article about it a couple of years ago in the fantastic Skrift magazine.

As my skill-set has grown though and my tastes have changed I have found myself curious about how far and fast I can push mapping.

Introducing UmbMapper

Not the most exciting name ever but definitely some of the most exciting code I've come up with.

UmbMapper is a new, convention-based mapper that I've written for mapping IPublishedContent to strong-typed POCO classes. Strong-typed? Let's quote my own words from a couple of years ago.

Opens dictionary… Strong typing involves the strict enforcement of type rules with no exceptions, allowing the detection of incorrect type usage either at run time or at compile time. Having strong typing catches more type errors than weak typing, resulting in fewer hard errors. It can cut down on many hours of painful debugging.

Umbraco offers what I would call "Stringly Typing". You can get your property type out of the API but you have to pass a string in order to do so. That’s pretty damn cool but it doesn’t quite feel natural and easy enough for me to use as a developer. I’m lazy and the last thing I want to do it type lots and learn how to spell things.

Clued up? Great, let's carry on.

The Elevator Pitch

Why use it? Well, let me explain.

UmbMapper works in a completely different way to Ditto and ModelsBuilder. It uses the concept of convention-based mapping configuration classes to do the work for you which allows you to perform fine-grained mapping with very little overhead.

I want my POCO's to be super lean and you should too. Imagine getting to write code like this in your Umbraco project to represent your document types.

public class PublishedItem
{
    public int Id { get; set; }

    public string Name { get; set; }

    public DateTime CreateDate { get; set; }

    public DateTime UpdateDate { get; set; }

    public MyEnum MyEnumType{ get; set; }

    public Image Image { get; set; }

    public RelatedLink Link { get; set; }
}

Ain't that pretty? No attributes, no inheriting of particular interfaces, nothing but pure, good old fashioned POCO goodness! This makes it great for larger projects (As well as small ones) where you can apply design patterns like DDD to maintain flexibility and help your application scale.

So how do we map it? Here's where the magic happens.

public class PublishedItemMap : MapperConfig<PublishedItem>
{
    public PublishedItemMap()
    {
        this.AddMap(p => p.Id);
        this.AddMap(p => p.Name);
        this.AddMap(p => p.CreateDate);
        this.AddMap(p => p.UpdateDate).SetAlias(p => p.UpdateDate, p => p.CreateDate);
        this.AddMap(p => p.MyEnumType).SetMapper<EnumPropertyMapper>();
        this.AddMap(p => p.Image).SetMapper<UmbracoPickerPropertyMapper>();
        this.AddMap(p => p.Link);
    }
}

Look at that API!! How crisp, how clean, how marvellous! But wait... It gets better.

For simpler classes, there are more terse mapping methods available

public class PublishedItemMap : MapperConfig<PublishedItem>
{
    public PublishedItemMap()
    {
        this.AddMappings(
            x => x.Id,
            x => x.Name,
            x => x.DocumentTypeAlias,
            x => x.Level).ForEachIndexed((x, i) => x.AsLazy());

        this.AddMappings(
            x => x.SortOrder,
            x => x.CreateDate,
            x => x.UpdateDate).ForEach(x => x.AsLazy());
    }
}

Or for really, really simple classes

public class LazyPublishedItemMap : MapperConfig<LazyPublishedItem>
{
    public LazyPublishedItemMap()
    {
        this.MapAll().ForEachIndexed((x, i) => x.AsLazy());

        this.MapAll().ForEach(x => x.AsLazy());
    }
}

Registering a mapper is as easy as follows

MapperConfigRegistry.AddMapper(new LazyPublishedItemMap());

Now that's what I call a nice API; super simple to use but powerful with a multitude of other mapping instructions available.

Configuration

The AddMap() method and subsequent methods called in the mapper constructor each return a PropertyMap<T> where T is the class you want to map to. This allows us to use a simple fluent API to configure each property map.

The various mapping configuration options are as follows:

  • AddMap() Instructs the mapper to map the property.
  • AddMappings() Instructs the mapper to map the collection of properties.
  • MapAll() Instructs the mapper to map all the properties in the class.
  • SetAlias() Instructs the mapper what aliases to look for in the document type. The order given is the checking order. Case-insensitive.
  • SetMapper() Instructs the mapper what specific IPropertyMapper implementation to use for mapping the property. All properties are initially automatically mapped using the UmbracoPickerPropertyMapper.
  • SetCulture() Instructs the mapper what culture to use when mapping values. Defaults to the current culture contained within the UmbracoContext.
  • AsRecursive() Instructs the mapper to recursively traverse up the document tree looking for a value to map.
  • AsLazy() Instructs the mapper to map the property lazily using dynamic proxy generation. You need to mark your property with the virtual keyword for that to work.

Available IPropertyMapperimplementations all inherit from the PropertyMapperBase class and are as follows:

  • UmbracoPickerPropertyMapper The default mapper. Maps directly from Umbraco's Published Content Cache via GetPropertyValue. Runs automatically.
  • EnumPropertyMapper Maps to enum values. Can handle both integer and string values.
  • UmbracoPickerPropertyMapper Maps from all the Umbraco built-in pickers.
  • DocTypeFactoryPropertyMapper Allows mapping from mixed IPublishedContent sources like Nested Content. Inherits FactoryPropertyMapperBase.

These mappers handle most use cases since they rely initially on Umbraco's PropertyValueConverter API. Additional mappers can be easily created by inheriting the PropertyMapperBase class which give you all the properties you need.

I've already built you mappers for Archetype and NuPickers though so you don't need to worry about those :)

In Summary

I'm hoping by now you can see how beneficial UmbMapper can be in your projects. It's fast, easy to use and should help you build flexible, scalable Umbraco websites giving you the freedom to spend less time boiler-plating and more time concentrating on the fun stuff.

Please visit the Github repo and download the packages from Nuget and if you have any questions, just holler and I'll do my best to help you out.

Thanks for reading!

Update

I was asked by my good friend Mads on Twitter to elaborate on how lazy mapping works so I thought I best do so.

Any sufficiently advanced technology is indistinguishable from magic. - Gandalf the Grey

If a mapper is configured using the AsLazy() instruction and the class contains properties using the virtual keyword, the mapper will generate a dynamic proxy class at run-time to represent the type we are mapping to. This class actually inherits our target class and you'll be able to see it by attaching a debugger.

The class looks something like this:

public class LazyPublishedItemProxy : LazyPublishedItem, IProxy
{
    /// <summary>
    /// Gets or sets the <see cref="IInterceptor"/> for intercepting 
    /// <see cref="System.Reflection.MethodBase"/> calls.
    /// </summary>
    IInterceptor Interceptor { get; set; }

    // Property interceptors are written below using Reflection.Emit
}

Any properties configured with lazy mapping are not actually mapped until you specifically call the getter on the property. (Via means of MethodInfo interception using the terribly complicated Reflection.Emit API).

This means that we can map large collections with very little overhead. MEGAWHOOP!

It was really interesting code to write so I encourage you to look through the source to see how it was all put together, it's very similar to how Entity Framework used to lazy map navigation properties.

When to use it?

I recommend using this when mapping any pickers or anything that requires significant work when mapped via the Property Value Converter API. You'll most likely be able to use it on all properties but I would always load test any large sites to strike the right performance balance.

comments powered by Disqus