AndyButland / UmbracoMapper

Mapper for Umbraco using convention based methods for mapping from Umbraco and other sources to custom view models
MIT License
41 stars 8 forks source link

Umbraco Mapper

Background

Umbraco Mapper has been developed to support a more pure MVC approach to building Umbraco applications.

It supports Umbraco versions 7 and 8.

With MVC in Umbraco, there are broadly three approaches:

  1. As with 'traditional' Umbraco using WebForms, place a lot of data access and business logic in the views
  2. Within the view used for the page template, place an @Html.Action or @Html.Partial, to call other templates or controller actions.
  3. Hijack the routes, and map the Umbraco (and other) content information to custom view models which are passed to the view.

Some may find 3) requires a bit more work up front, but the advantage of this approach is that the view code is exceptionally clean, and in fact need have no dependency on Umbraco at all. It also follows a more traditional MVC route, where the controller is responsible for passing the information to the view in the particular form it requires it.

This work up front though to map the content from Umbraco and other sources is where Umbraco Mapper comes in.

Summary of Operations

Umbraco Mapper provides an interface IUmbracoMapper and implementation UmbracoMapper that provides a number of methods for mapping various types of content to a custom view model. Methods are chainable so a number of mapping operations can be requested using a fluent interface type syntax.

The operations supported are:

Conventions are used throughout the mapping process. For example it's expected when mapping document type properties to custom view model fields that they will have the same name, with the former camel-cased (e.g. 'bodyText' will map to 'BodyText'). All methods contain an optional parameter though where these conventions can be overridden.

A couple of base types are provided to help create the necessary view models in your application. It's not essential to use these, but they may provide some useful properties.

It is also possible to define custom mapping functions for property types not included in the default mapper. This could be used, for example, if you have custom Media types with properties not included in the default MediaFile class, or for mapping complex data types like the Google Maps type.

Package Contents

The package has been provided as two separate downloads:

Expected Use Cases

Where we utilise and test Umbraco Mapper we are following the model of Umbraco MVC application development that uses route hijacking.

We also implement an IoC container such as Ninject as described here.

Neither of these steps are strictly necessary though, if preferred you can just instantiate an instance of UmbracoMapper directly.

Examples

Mapping Operations

From IPublishedContent

Given an instance of UmbracoMapper you can map a the properties of a particular page to a custom view model using conventions like this:

var mapper = new UmbracoMapper();
var model = new UberDocTypeViewModel();
mapper.Map(CurrentPage, model);

This will map all view model properties it can find an exact name match for, either from the standard IPublishedContent properties (like Id, Name etc.), or from the document type fields.

From version 4.0 onwards - supporting Umbraco 8 - you can optionally provide a culture string, in order to map view model properties to the language variant indicated by the culture string:

mapper.Map(CurrentPage, model, "en-GB");

To override conventions for property mapping, you can provide a Dictionary of property mappings via the propertyMappings parameter (or with more recent versions, use attributes - see 'Mapping Using Attributes' below). In this example we are mapping a document type field called 'bodyText' to a view model field called 'Copy':

mapper.Map(CurrentPage, model,
  new Dictionary<string, PropertyMapping> 
  { 
    { 
      "Copy", new PropertyMapping 
        { 
          SourceProperty = "bodyText", 
        } 
    }, 
  });

The propertySet is a simple enum based flag to allow to you map just all native properties of IPublishedContent (Id, Name etc.) OR just all custom properties (all the ones put on document types). The default if not provided is to map both.

From Collections of IPublishedContent

To map a collection use the following method. This example maps the child nodes of the current page to a custom collection called 'Comments' on the view model, again using the default name mapping conventions.

mapper.MapToCollection(CurrentPage.Children, model.Comments);

You can also override here both the property names as before, and the level at which the mapping is made. So if for example you have one property on your view model that you want to get from the parent node, you can do this:

mapper.MapCollection(CurrentPage.Children, model.Comments, 
  new Dictionary<string, PropertyMapping> 
  { 
    { 
      "ParentPage", new PropertyMapping 
        { 
          SourceProperty = "Name",
          LevelsAbove = 1,
        } 
    }, 
  });   

You can also map any other collection of IPublishedContent, e.g. that built up from a node picker and query:

var countryIds = CurrentPage.GetPropertyValue<string>("countries");
var countryNodes = Umbraco.TypedContent(countryIds.Split(','));
mapper.MapCollection(countryNodes, model.Countries);

Or if you are also using the Umbraco Core Property Editor Converters, more simply like this:

var countryNodes = CurrentPage.GetPropertyValue<IEnumerable<IPublishedContent>>("countries");
mapper.MapCollection(countryNodes, model.Countries);    

Further Property Mapping Overrides

Another PropertyMapping field allows you to map properties from related content. Say for example your document type contains a content picker - the value of this will be an integer representing the Id of another IPublished content instance (or the IPublishedContent itself if you have the Umbraco Core Property Editor Converters installed). You can provide an override here to tell the mapper to map from a particular property of that instance instead.

The following example maps a string property on the view model called 'LinkToPage' to the 'Url' property of an IPublishedContent picked using a content picker for the current page.

mapper.Map(CurrentPage, model,
  new Dictionary<string, PropertyMapping> 
  { 
    { 
      "LinkToPage", new PropertyMapping 
        { 
          SourceRelatedProperty = "Url", 
        } 
    }, 
  });

Yet another couple of PropertyMapping fields allow you to concatenate two or more source properties to a single string property on your view model. You pass through an array of properties to map to and a separation string like this. The following example would map the firstName ("Fred") and lastName ("Bloggs") properties to a single concatenated string ("Bloggs, Fred"):

mapper.Map(CurrentPage, model,
  new Dictionary<string, PropertyMapping> 
  { 
    { 
      "Name", new PropertyMapping 
        { 
            SourcePropertiesForConcatenation = new string[] { "firstName", "lastName" },
            ConcatenationSeperator = ", ",
        } 
    }, 
  });

Similarly you can coalesce (take the first non null, empty or whitespace) source property from a list:

mapper.Map(CurrentPage, model,
  new Dictionary<string, PropertyMapping> 
  { 
    { 
      "Title", new PropertyMapping 
        { 
            SourcePropertiesForCoalescing = new string[] { "heading", "Name" },
        } 
    }, 
  });  

The MapIfPropertyMatches field allows you to define a condition for when the mapping operation occurs. In this example, we want to map a string containing a URL to a related page, only if the page is intended to be linked to:

mapper.Map(CurrentPage, model,
  new Dictionary<string, PropertyMapping> 
  {
    {
        "LinkToPage", new PropertyMapping
            {
                SourceProperty = "relatedPage",
                SourceRelatedProperty = "Url",
                MapIfPropertyMatches = new KeyValuePair<string, string>("allowPageLink", "1"),
            }
    },        
  });     

The StringValueFormatter is a field that can be set to a function to transform the mapped value. For example, you could use this to format a date field to a string with a particular date format. This simple example shows how to apply a transformation function that converts the mapped value to upper case:

mapper.Map(CurrentPage, model,
  new Dictionary<string, PropertyMapping> 
  {
    {
        "Heading", new PropertyMapping 
            { 
                StringValueFormatter = x => {
                    return x.ToUpper();
                }
            } 
    },        
  });   

DefaultValue is a field that if set will provide a value to any properties that aren't mapped. It works by checking the value of the property after the mapping operation is complete. If it's null or the default value for the type (e.g. 0 for an integer), and a default value has been provided, the property value will be set to this default.

Ignore can be added to a field and if set to true, it will not be mapped and retain it's default or previously set value

DictionaryKey can be added with a string value of a dictionary key, which will be mapped to it's value

MapRecursively can be added to a field and if set to true, the property will be mapped recursively. It will use Umbraco default camel-case naming convention (i.e. if assigned to a view model property called 'StarRating', it'll look for an Umbraco property called 'starRating').

FallbackMethods has been added from version 3 onwards, supporting the more flexible fall-back methods provided in version 8. If provided, this will override any setting provided on MapRecursively.

To fall-back recursively, you would provide:

mapper.Map(CurrentPage, model,
  new Dictionary<string, PropertyMapping> 
  { 
    { 
      "BodyText", new PropertyMapping 
        { 
          FallbackMethods = new List<int> { 2 }, 
        } 
    }, 
  });

Via fall-back language:

mapper.Map(CurrentPage, model,
  new Dictionary<string, PropertyMapping> 
  { 
    { 
      "BodyText", new PropertyMapping 
        { 
          FallbackMethods = new List<int> { 3 }, 
        } 
    }, 
  });

And via language first, then recurively:

mapper.Map(CurrentPage, model,
  new Dictionary<string, PropertyMapping> 
  { 
    { 
      "BodyText", new PropertyMapping 
        { 
          FallbackMethods = new List<int> { 3, 2 }, 
        } 
    }, 
  });

The "magic numbers" can (and should) be replaced by those defined in Umbraco 8's Umbraco.Core.Models.PublishedContent.Fallback struct, and the are also defined in the package, at `UmbracoMapper.Common.Constants, e.g.

mapper.Map(CurrentPage, model,
  new Dictionary<string, PropertyMapping> 
  { 
    { 
      "BodyText", new PropertyMapping 
        { 
          FallbackMethods = Fallback.ToLanguage.ToArray() 
        } 
    }, 
  });

MapFromPreValue will map a single-value property from a prevalue (for example a radio button list). Without this the prevalue's numeric Id will be mapped; by setting to true, the label from the prevalue will be mapped instead (normally more useful). In Umbraco V8 this setting has no effect as the prevalue labels are mapped automatically.

Mapping Using Attributes

A newer feature that has been added to the package is the ability to configure your mappings using attributes on the view model, instead of passing in these overrides to the default mapping behaviour via the Dictionary parameter of the Map() method.

There's a single attribute called PropertyMapping that has properties that can be set to configure most of the property mappings described above.

So for example instead of configuring a mapping like this:

mapper.MapCollection(CurrentPage.Children, model.Comments, 
  new Dictionary<string, PropertyMapping> 
  { 
    { 
      "ParentPage", new PropertyMapping 
        { 
          SourceProperty = "Name",    
          LevelsAbove = 1,
        } 
    }, 
  });   

You can do this on your view model:

[PropertyMapping(SourceProperty = "Name", LevelsAbove = 1)]
public string ParentPage { get; set; }  

Simplifying the mapping call to:

mapper.MapCollection(CurrentPage.Children, model.Comments);   

To map recursively up the ancestors of the tree, use:

[PropertyMapping(MapRecursively = true)]
public int StarRating { get; set; }

Or - to support the more flexible fall-back methods provided via version 8:

[PropertyMapping(FallbackMethods = new[] { Fallback.Ancestors })]
public int StarRating { get; set; }

"Auto-mapping" related content

Version 1.5.0 introduced a new feature that would auto-map related and ancestor content to avoid having to explicitly make secondary mapping calls. It works when you have a view model that itself contains a property that is a complex type - i.e. an instance of a class with one or more properties - or a collection of complex types.

If in the standard mapping operation that property is mapped to a single or multiple content/node picker or an instance of nested content, AND you are either running V8 or have the Umbraco Core Property Editor Converters installed (required so we get back an IPublishedContent or IEnumerable), OR you use the LevelsAbove property mapping attribute field to indicate that the mapping should be made from a parent node, Umbraco Mapper will automatically make further mapping operations for that related or ancestor content to the complex type on your view model.

To take an example illustrating all three types of auto-mapping (single related content, multiple related content and parent content), say you have a view model that looks like this:

public class NewsLandingPageViewModel
{
    public NewsLandingPageViewModel()
    {
        NewsCategory = new Category();
        TopStory = new NewsStory();
        OtherStories = new List<NewsStory>();
    }

    public string Heading { get; set; }

    [PropertyMapping(LevelsAbove = 1)]
    public Category NewsCategory { get; set; }

    public NewsStory TopStory { get; set; }

    public IEnumerable<NewsStory> OtherStories { get; set; }

    public class Category
    {
        public string Title { get; set; }       
    }       

    public class NewsStory
    {
        public string Headline { get; set; }        

        public DateTime StoryDate { get; set; } 

        public IHtmlString BodyText { get; set; }           
    }
}   

And you were mapping from a content node based on document type that contained the following properties:

And that those picker fields were selecting fields of a document type containing:

And that the content node had a parent that indicated the news category and was based on a document type containing:

You could map the whole lot with a single call to:

var model = new NewsLandingPageViewModel();
mapper.Map(CurrentPage, model);

Note: there's a small backward compatibility issue introduced with this feature. Given mapping related content previously required a second call to a mapping operation, if those calls are in place those related fields will be mapped twice - once by the explicit call and once by the auto-mapping. In the case of mapping to a collection you would end up in that case with twice as many values in the collection as you'd expect, with each one repeated. The explicit call can of course now be removed which would resolve this issue.

If that wasn't done though, to avoid the unwanted doubling behaviour, any call to map a collection has been set by default to clear the destination collection before carrying out the mapping. In most cases that's likely what is required. However if you have a case where you do want a collection to be left intact before mapping - perhaps it having been part-populated from another source - you can set the newly introduced optional parameter clearCollectionBeforeMapping to false.

From Other Sources

Some Umbraco 6 data types store XML and Umbraco 7 ones JSON. This can be mapped to a custom collection on the view model. The example below uses the related links data type from version 6. Note the need to provide an override here to ensure the correct root node is passed to the mapping method.

var sr = new StringReader(CurrentPage.GetPropertyValue<string>("relatedLinks"));
var relatedLinksXml = XElement.Load(sr);
mapper.MapCollection(relatedLinksXml, model.RelatedLinks, null, "link")

You can also map XML, JSON and Dictionaries that may have come from other sources. This example maps a JSON array:

var json = @"{ 'items': [{ 'Name': 'United Kingdom' }, { 'Name': 'Italy' }]}";
mapper.MapCollection(json, model.Countries);

Similar to the use of the PropertyMapping.SourceRelatedProperty property for IPublished content, you can pass an override to map to the immediate child of the XML or JSON, thus allowing you to flatten your view model. The property is called SourceChildProperty.

Chaining Mapping Operations

All mapping methods return an instance of the mapper itself, meaning operations can be chained in a fluent interface style. E.g.

mapper.Map(CurrentPage, model)
      .MapToCollection(CurrentPage.Children, model.Comments);

Further Examples

For more examples, including details of how the controllers are set up, see the controller class UberDocTypeController.cs in the test web application. Or the file UmbracoMapperTests.cs in the unit test project.

Custom Mappings

For additional flexibility when you want to map to a custom view model type that's been created in your project, it is possible to add custom mapping functions to the mapper, e.g.:

mapper.AddCustomMapping(typeof(Image).FullName, CustomMappings.GetImage);
...
public static object GetImage(IUmbracoMapper mapper, IPublishedContent contentToMapFrom, string propName, bool isRecursive) {}

The custom mapping method you pass must be a delegate that matches one of the following two signatures.

Firstly for mapping from IPublishedContent:

delegate object CustomMapping(IUmbracoMapper mapper, IPublishedContent content, string propertyName, bool recursive);

And secondly when mapping from a dictionary object value:

delegate object CustomObjectMapping(IUmbracoMapper mapper, object value);

When working with Umbraco 8 (Umbraco Mapper package version 4.0 and above), the first signature is slightly different, as we have support of more flexible fall-back methods (such as via language variant, in addition to recursive retrieval up the content free) and we can map from IPublishedElement (e.g. as used with Nested Content):

delegate object CustomMapping(IUmbracoMapper mapper, IPublishedElement content, string propertyName, Fallback fallback);

For V8, if you need to access properties available on IPublishedContent, but not IPublishedElement, you can cast to the former like this:

var publishedContent = contentToMapFrom as IPublishedContent;
if (publishedContent != null)
{
    // Can now use e.g. publishedContent.Name;
}

Here'an example, mapping from the Google Maps data type (which stores it's data as three values - lat, long, zoom - in CSV format):

mapper.AddCustomMapping(typeof(GeoCoordinate).FullName, CustomMappings.MapGeoCoordinate);
...
public class GeoCoordinate
{
    public decimal Longitude { get; set; }
    public decimal Latitude { get; set; }
    public int Zoom { get; set; }
}   
...
public class CustomMappings
{
    public static object MapGeoCoordinate(IUmbracoMapper mapper, IPublishedContent contentToMapFrom, string propertyName, bool recursive) 
    {
        return GetGeoCoordinate(contentToMapFrom.GetPropertyValue<string>(propertyName, recursive, null));
    }

    private static GeoCoordinate GetGeoCoordinate(string csv)
    {
        if (!string.IsNullOrEmpty(csv))
        {
            var parts = csv.Split(',');
            if (parts != null && parts.Length == 3)
            {
                return new GeoCoordinate
                {
                    Latitude = decimal.Parse(parts[0]),
                    Longitude = decimal.Parse(parts[1]),
                    Zoom = int.Parse(parts[2]),
                };
            }
        }

        return null;
    }
}   

A custom mapping method can be restricted to a single property, rather than all properties of the given type, by passing the propertyName parameter to the AddCustomMapping method.

For the finest control, it's also possible to provided a custom mapping method via the optional Dictionary object provided for a mapping operation. If this is done, the provided mapping will be used in preference to any globally registerd custom mapping method that would otherwise match the type and/or property name.

mapper.Map(CurrentPage, model,
  new Dictionary<string, PropertyMapping>
  {
      {
        "GeoCoordinate", new PropertyMapping
            {
                CustomMapping = CustomMappings.MapGeoCoordinate
            }
      },
  });

This can also be achieved using an attribute on the view mode, which, if in place, means the Dictionary object doesn't need to be passed:

[PropertyMapping(
    CustomMappingType = typeof(CustomMappings), 
    CustomMappingMethod = nameof(CustomMappings.MapGeoCoordinate))]
public GeoCoordinate GeoCoordinate { get; set; }

mapper.Map(CurrentPage, model);

Using IMapFromAttribute

Custom mappings allow you to define one way to map a given complex type on your view model. But there's another, even more generic approach you can take from version 1.6.0 thanks to a pull request from Robin Herd.

To explain with an example, suppose we have two document types: Article Page and Author. An article has two content pickers: one for the author of the article, and another for a related article. We want to map all the properties from those content items onto the view model for our article page.

View models look like this:

public class ArticlePageViewModel : BaseNodeViewModel
{
    [MapFromContentPicker]
    public ArticlePageViewModel RelatedArticle { get; set; }

    [MapFromContentPicker]
    public AuthorModel Author { get; set; }

    public IHtmlString ArticleText { get; set; }
}

public class AuthorModel : BaseNodeViewModel
{
    public string Title { get; set; }

    public string PhoneNumber { get; set; }
}

We can implement IMapFromAttribute as follows:

[AttributeUsage(AttributeTargets.Property)]
public class MapFromContentPickerAttribute : Attribute, IMapFromAttribute
{
    public void SetPropertyValue<T>(object fromObject, PropertyInfo property, T model, IUmbracoMapper mapper)
    {
        var method = GetType().GetMethod("GetInstance", BindingFlags.NonPublic | BindingFlags.Instance);
        var genericMethod = method.MakeGenericMethod(property.PropertyType);
        var item = genericMethod.Invoke(this, new[] { fromObject, mapper });
        property.SetValue(model, item);
    }

    private T GetInstance<T>(object fromObject, IUmbracoMapper mapper)
        where T : class
    {
        T instance = default(T);
        if (fromObject != null)
        {
            // Check first if already IPublishedContent (as core converters installed)
            var content = fromObject as IPublishedContent;
            if (content == null)
            {
                // Otherwise handle if Id passed
                int id;
                if (int.TryParse(fromObject.ToString(), out id))
                {
                    var umbracoHelper = new UmbracoHelper(UmbracoContext.Current);
                    content = umbracoHelper.TypedContent(id);
                }
            }

            if (content != null)
            {
                instance = Activator.CreateInstance<T>();
                mapper.Map(content, instance);
            }

        }

        return instance;
    }
}

...and we can apply this attribute to any property of any type which we wish to map in the same way.

Just to reiterate the difference between these two methods: custom mappings define a mapping for a particular C# type; IMapFromAttribute implementations define a mapping for a particular Umbraco data type and are reusable across multiple C# property types.

Working with Vorto

Vorto is an Umbraco package that supports 1:1 translations in Umbraco. It works by wrapping standard Umbraco data types and you retrieve values for IPublishedContent using those types not with GetPropertyValue but with a custom extension method GetVortoValue.

As of version 2.0.3 we've provided a way of replacing the default Umbraco method of retrieving property values that Umbraco Mapper uses with custom one, that can use this Vorto specific method. Of course this technique can also be used for any other property editor that has a similar requirement to amend how the raw content values from Umbraco are retrieved.

If you have just a few properties on your view model that you wish to use a custom method for, you can decorate your properties like this:

[PropertyMapping(PropertyValueGetter = typeof(MyPropertyValueGetter))]
public string MyProperty { get; set; }

On the other hand, if you wanted to use this method for all mapping operations, you can set the DefaultPropertyValueGetter property on your UmbracoMapper instance itself:

mapper.DefaultPropertyValueGetter = new MyPropertyValueGetter();

Or provide the value in the overloaded constructor:

var mapper = new UmbracoMapper(new MyPropertyValueGetter());

The type you use here must implement IPropertyValueGetter. Here's an example we've used for working with Vorto, that falls back to the standard means of retrieving property values if the particular field is not a Vorto model:

public class VortoPropertyGetter : IPropertyValueGetter
{
    public object GetPropertyValue(IPublishedContent content, string alias, bool recursive)
    {
        if (content.HasVortoValue(alias))
        {
            return content.GetVortoValue(alias, recursive: recursive);
        }

        return content.GetPropertyValue(alias, recursive);
    }
}

If using this technique when working with a version of the package supporting Umbraco 8, the signature is slightly different:

object GetPropertyValue(IPublishedContent content, string alias, string culture, string segment, Fallback fallback)

Classes, Properties and Methods

IUmbracoMapper / UmbracoMapper

The primary mapping component.

Properties

AssetsRootUrl (string) - If set allows the population of mapped MediaFile's DomainWithUrl property with an absolute URL. Useful only in the context where a CDN is used for distributing media files rather than them being served from the web server via relative links.

DefaultPropertyValueGetter (IPropertyValueGetter) - If set uses the provided type for retrieving property values from Umbraco, instead of the default which uses standard Umbraco GetPropertyValue() calls

Methods

Full signature of delegates are as follows:

object CustomMapping(IUmbracoMapper mapper,
                     IPublishedContent content,
                     string propertyName,
                     bool recursive)

And for versions targetting Umbraco 8:

object CustomMapping(IUmbracoMapper mapper,
                     IPublishedElement content,
                     string propertyName,
                     Fallback fallback)         

Full signature of mapping methods are as follows:

IUmbracoMapper AddCustomMapping(string propertyTypeFullName,
                                CustomMapping mapping,
                                string propertyName = null);

IUmbracoMapper Map<T>(IPublishedContent content, 
    T model, 
    string culture,     // versions supporting Umbraco 8 only
    Dictionary<string, PropertyMapping> propertyMappings = null,
    PropertySet propertySet = PropertySet.All);

IUmbracoMapper Map<T>(IPublishedElement content, 
    T model, 
    string culture,     // versions supporting Umbraco 8 only
    Dictionary<string, PropertyMapping> propertyMappings = null,
    PropertySet propertySet = PropertySet.All);

IUmbracoMapper Map<T>(XElement xml, 
    T model,
    Dictionary<string, PropertyMapping> propertyMappings = null);

IUmbracoMapper Map<T>(Dictionary<string, object> dictionary,
    T model,
    Dictionary<string, PropertyMapping> propertyMappings = null);

IUmbracoMapper Map<T>(string json,
    T model,
    Dictionary<string, PropertyMapping> propertyMappings = null);

IUmbracoMapper MapCollection<T>(IEnumerable<IPublishedContent> contentCollection, 
    IList<T> modelCollection,
    string culture,     // versions supporting Umbraco 8 only
    Dictionary<string, PropertyMapping> propertyMappings = null,
    PropertySet propertySet = PropertySet.All, 
    bool clearCollectionBeforeMapping = true) where T : new();

IUmbracoMapper MapCollection<T>(IEnumerable<IPublishedElement> contentCollection, 
    IList<T> modelCollection,
    string culture,     // versions supporting Umbraco 8 only
    Dictionary<string, PropertyMapping> propertyMappings = null,
    PropertySet propertySet = PropertySet.All, 
    bool clearCollectionBeforeMapping = true) where T : new();

IUmbracoMapper MapCollection<T>(XElement xml, IList<T> modelCollection, 
    Dictionary<string, PropertyMapping> propertyMappings = null, 
    string groupElementName = "Item", 
    bool createItemsIfNotAlreadyInList = true, 
    string sourceIdentifyingPropName = "Id", 
    string destIdentifyingPropName = "Id") where T : new();

IUmbracoMapper MapCollection<T>(IEnumerable<Dictionary<string, object>> dictionaries, 
    IList<T> modelCollection, 
    Dictionary<string, PropertyMapping> propertyMappings = null, 
    bool createItemsIfNotAlreadyInList = true, 
    string sourceIdentifyingPropName = "Id", 
    string destIdentifyingPropName = "Id") where T : new();

IUmbracoMapper MapCollection<T>(string json, IList<T> modelCollection, 
    Dictionary<string, PropertyMapping> propertyMappings = null,
    string rootElementName = "items", 
    bool createItemsIfNotAlreadyInList = true, 
    string sourceIdentifyingPropName = "Id", 
    string destIdentifyingPropName = "Id") where T : new();     

PropertyMapping

Class defining the override to the mapping convention for property to a particular type.

Properties

SourceProperty (string) - The name of the property on the source to map from. If not passed, exact name match convention is used.

LevelsAbove (int) - Defines the number of levels above the current content to map the value from. If not passed, 0 (the current level) is assumed. Only for IPublishedContent mappings.

SourceRelatedProperty (string) - If passed, the source property is assumed to be a structure that has related content (e.g. a Content Picker that contains an integer Id for another IPublishedContent). The mapping is then done from the named property of that child element. Only for IPublishedContent mappings.

SourceChildProperty (string) - If passed, the source property is assumed to be a structure that has child content. The mapping is then done from the named field of that child element. Only for XML and JSON mappings.

SourcePropertiesForConcatenation (string[]) - This property can contain a string array of multiple source properties to map from. If the destination property is a string the results will be concatenated.

ConcatenationSeperator (string) - Used in conjunction with SourcePropertiesForConcatenation to define the separating string between the concatenated items.

SourcePropertiesForCoalescing (string[]) - This property can contain a string array of multiple source properties to map from. If the destination property is a string the result will be the first non null, empty or whitespace property found..

MapIfPropertyMatches (KeyValuePair<string, string>) - if provided, mapping is only carried out if the property provided in the key contains the value provided in the value.

StringValueFormatter (Func<string, string>) - If provided, carries out the formatting transformation provided in the function on the mapped value.

DefaultValue (object) - If provided, sets a default value for a property to be used if the mapped value cannot be found.

Ignore (bool) - can be added to a field and if set to true, it will not be mapped and retain it's default or previously set value

DictionaryKey (string) - if set property will be mapped from the given Umbraco dictionary key

PropertyValueGetter (Type) - a type that must implement IPropertyValueGetter to be used when retrieving the property value from Umbraco. A use case for this is to use Vorto, where we want to call GetVortoValue instead of GetPropertyValue.

CustomMappingType (Type) and CustomMappingMethod (string) - a type that must implement be of delegate CustomMapping, and a method to be used in preference to any named or unnamed custom mapping that might be registered globally.

BaseNodeViewModel

Class representing an Umbraco node that can be used as the basis of any page view models in the client product.

MediaFile

Class representing an Umbraco media item that can be used within page view models in the client product.

Supported Umbraco Versions

The earliest version of Umbraco this package has been tested with is 6.1.6 although the expectation is that all versions from 6.0 should be supported.

The last version of the package strictly supporting out of the box Umbraco version 6 is 1.6.1.

After that the references to Umbraco Core were updated to version 7. The package still works though with Umbraco 6, so long as the JSON.Net dependency is updated to the version that ships with Umbraco 7, 6.0.8. This can be done with a NuGet command:

PM> Install-Package Newtonsoft.Json -Version: 6.0.8

With that dependency updated Umbraco 6 appears to me to work unaffected, which is borne to some extent out by other discussion.

Version History

Credits

Thanks to Ali Taheri, Neil Cumpstey and Robin Herd at Zone for code, reviews and testing.

License

Copyright © 2016-22 Andy Butland, Zone and other contributors

Licensed under the MIT License