This repository contains a blazingly-fast, really simple to use, convention-based published content mapper for Umbraco.
UmbMapper maps IPublishedContent
instances from the Umbraco Published Content Cache to strongly typed classes. It does so in a very efficient manner with very little overhead.
So far it's made up of the following libraries
- UmbMapper - The main mapping library, Maps all default Umbraco dataTypes and almost anything that uses a
PropertyValueConverter
to POCO equivalents. - UmbMapper.ArcheType - Allows the mapping of ArcheType models to POCO equivalents.
- UmbMapper.NuPickers - Allows the mapping of NuPicker models to POCO equivalents.
- UmbMapper.PublishedContentModelFactory - Allows the mapping of models using the Umbraco PublishedContentFactory.
- UmbMapper.Vorto - Allows the mapping of Vorto models to POCO equivalents.
Nightlies are available on Myget with battle tested releases available on Nuget.
Samples project login (Check it out!)
- Username : admin
- Password : umbmapper!
A POCO should be exactly that. - Rudyard Kipling
Models are clean without any inheritance requirements. They require no additional attribution to determine mapping logic.
Here's an example class, nothing fancy...
public class SimplePublishedItem
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual DateTime CreateDate { get; set; }
public virtual string Url { get; set; }
// RTE
public virtual IHtmlString BodyText { get; set; }
// Media Picker
public virtual ImageCropDataSet Image { get; set; }
// Content Picker
public virtual ComplexPublishedItem Target { get; set; }
}
You'll have noticed the virtual
keyword there. It's only required when we want to lazy map a property so it's not essential. (more on that later)
Since this class requires no specialization, and, you've used appropriate convention to ensure property names match the property aliases in the document type, mapping is super simple.
// For simple classes you don't need to create a mapper.
// The registry will automatically create one based on the default conventional logic.
// Mappers added this way automatically use lazy mapping for `virtual` properties.
UmbMapperRegistry.AddMapperFor<SimplePublishedItem>;
Convention-based mapping like that will work most of the time but sometimes you need more granular control.
This more complex class demonstrates that. In this example i've added an enum, an additional property requiring an alias, and also a property that does not exist on the document type.
public class ComplexPublishedItem : SimplePublishedItem
{
// Not an editable property in the backoffice, mapped from the "name" property
public virtual string Slug { get; set; }
// This wil fallback to "createDate" is there have been no updates
public virtual DateTime UpdateDate { get; set; }
// An enum, your type would be a dropdown
public virtual PlaceOrder PlaceOrder { get; set; }
}
For this class, due to specialization, you need to create a mapping class to tell UmbMapper what to do. This class inherits from UmbMapperConfig<T>
where T
is the class you want to map to.
public class ComplexPublishedItemMap : UmbMapperConfig<ComplexPublishedItem>
{
public ComplexPublishedItemMap()
{
// Map all the properties as lazy properties
this.MapAll().ForEach(x => x.AsLazy());
// Map from the resolved "name" property in the class
this.AddMap(p => p.Slug).MapFromInstance((instance, content) => instance.Name.ToLowerInvariant());
// Try "updateDate" and fallback to "createDate"
this.AddMap(p => p.UpdateDate).SetAlias(p => p.UpdateDate, p => p.CreateDate);
// Set our enum mapper (built in)
this.AddMap(p => p.PlaceOrder).SetMapper<EnumPropertyMapper>();
}
}
Registering a custom mapper is as simple as follows.
// Add the mapper we created to the registry
UmbMapperRegistry.AddMapper(new ComplexPublishedItemMap());
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 the properties in the class.MapFromInstance()
Instructs the mapper to map from the givenFunc<T, IPublishedContent, object>
whereT
is the current object instance.Ignore()
Removes a property from the collection of property maps.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 specificIPropertyMapper
implementation to use for mapping the property. All properties are initially automatically mapped using theUmbracoPropertyMapper
.SetCulture()
Instructs the mapper what culture to use when mapping values. Defaults to the current culture contained withing theUmbracoContext
.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.
Available IPropertyMapper
implementations all inherit from the PropertyMapperBase
class and are as follows:
UmbracoPropertyMapper
The default mapper, maps directly from Umbraco's Published Content Cache viaGetPropertyValue
. Runs automatically.EnumPropertyMapper
Maps to enum values. Can handle both integer and string values.DocTypeFactoryPropertyMapper
Allows mapping from mixedIPublishedContent
sources sharing common properties. InheritsFactoryPropertyMapperBase
.CsvPropertyMapper
Allows mapping of comma separated string values to arrays of strings, integrals, and real types. Values are automatically clamped and rounded.UmbracoPickerPropertyMapper
Maps from all the Umbraco built-in legacy pickers. Not required for any of the pickers from v7.6+
These mappers handle most use cases since they utilize Umbraco's PropertyValueConverter
API. Additional mappers can be easily created though. Check the source for examples.
Specialist mappers for Archetype, NuPickers, and Vorto are available also via installing the additional packages.
ArchetypeFactoryPropertyMapper
Maps all Archetype propertiesNuPickerPropertyMapper
Maps NuPicker propertiesNuPickerEnumPropertyMapper
Maps from a NuPicker value to an enumVortoPropertyMapper
Maps all Vorto properties
There are four extension methods that have been added to the IPublishedContent
interface providing compile-time and run-time creation and mapping variants. Their signatures are as follows:
// Map a collection of a compile-time known type instances.
public static IEnumerable<T> MapTo<T>(this IEnumerable<IPublishedContent> content)
where T : class
// Map a collection of a run-time known type instances.
public static IEnumerable<object> MapTo(this IEnumerable<IPublishedContent> content, Type type)
// Map a single compile-time known type instance.
public static T MapTo<T>(this IPublishedContent content)
where T : class
// Map a single run-time known type instance.
public static object MapTo(this IPublishedContent content, Type type)
It's also possible to map to existing classes.
// Map to a single compile-time known type instance.
public static void MapTo<T>(this IPublishedContent content, T destination)
where T : class
// Map to a single run-time known type instance.
public static void MapTo(this IPublishedContent content, object destination)
Note: It's recommended that you use the following method to create your target instances as this allows the creation of proxy classes which allow lazy loading.
// Creates an empty instance of the given type.
public static T CreateEmpty<T>()
where T : class
// Creates an empty instance of the given type passing the published
// content to the constructor.
public static T CreateEmpty<T>(IPublishedContent content)
where T : class
As of v7.1.4, Umbraco ships with using a default model factory for IPublishedContent
.
For more information about the IPublishedContentModelFactory please see the "Zbu.ModelsBuilder" wiki:
UmbMapper comes with an implementation that can be configured as following.
using UmbMapper.PublishedContentModelFactory;
public class ConfigurePublishedContentModelFactory : ApplicationEventHandler
{
protected override void ApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
{
// Important! This has to occur after mapper registration
var factory = new UmbMapperPublishedContentModelFactory();
PublishedContentModelFactoryResolver.Current.SetFactory(factory);
}
}
Note: It's recommended that you use lazy property mapping when using the
UmbMapperPublishedContentModelFactory
as it ensures that anyPropertyValueConverter
implementations that haveUmbracoContext
based requirements may fail otherwise.
UmbMapper is blazingly quick. No other measurable mapper can match it's performance. The underpinning logic is simple also and requires very little run-time work as the rules are already determined at compile-time.
Additional performance boosting can be delivered using lazy mapping.
Check out the benchmarks in the solution.
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.
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 terribly complicated Reflection.Emit
) this means that we can map large collections with very limited overheads.