Friday, 8 August 2014

Using Umbraco Mapper with Archetype (Part 2)

In the previous post I discussed a method of mapping property values from the Archetype package to a view model using Umbraco Mapper. There was still a remaining issue of how to handle picked content.

With the version of Umbraco Mapper 1.4.7 (just updated on our and NuGet) this is now possible with just some changes from the method noted in the previous post.

Firstly, you will need to have the Umbraco Core Property Value Converters package installed. If you are using Archetype you'll likely be using this already, but it's needed here to convert the values returned from Archetype node picker properties to either IPublishedContent (for single items from a content picker) or IEnumerable (for multiple items from a multi-node tree picker).

Then extending our previous example, let's say we've added fields to our "Football Match" archetype to have a content picker for the match report, and a multi-node tree picker for match reports from previous games.

Our view model then looks like this:

    public class FootballMatchesPageViewModel
    {
        public string Name { get; set; }

        public IEnumerable<FootballMatch> TodaysMatches { get; set; }
    }

    public class FootballMatch
    {
        public FootballMatch()
        {
            MatchReport = new MatchReportTeaser();
            ReportsFromPreviousGames = new List<MatchReportTeaser>();
        }

        public string HomeTeam { get; set; }

        public string HomeTeamScore { get; set; }

        public string AwayTeam { get; set; }

        public int AwayTeamScore { get; set; }

        public MatchReportTeaser MatchReport { get; set; }

        public IList<MatchReportTeaser> ReportsFromPreviousGames { get; set; }
    }

    public class MatchReportTeaser
    {
        public string Name { get; set; }

        public string Url { get; set; }
    }

Note it's important to have the constructor set the complex type and collection to an instantiated object, as the mapper won't handle mapping to null values.

The custom mapping we set up in the previous example needs some minor amends too, it's now:

    public class ArchetypeMapper
    {
        public static object MapFootballMatch(IUmbracoMapper mapper, IPublishedContent contentToMapFrom, string propName, bool isRecursive) 
        {
            var result = new List<FootballMatch>();

            var archetypeModel = contentToMapFrom.GetPropertyValue<ArchetypeModel>(propName, isRecursive, null);
            if (archetypeModel != null)
            {
                var archetypeAsDictionary = archetypeModel
                    .Select(item => item.Properties.ToDictionary(m => m.Alias, m => GetTypedValue(m), StringComparer.InvariantCultureIgnoreCase))
                    .ToList();
                mapper.MapCollection(archetypeAsDictionary, result);
            }

            return result;
        }

        private static object GetTypedValue(ArchetypePropertyModel archetypeProperty)
        {
            switch (archetypeProperty.PropertyEditorAlias)
            {
                case "Umbraco.ContentPickerAlias":
                    return archetypeProperty.GetValue<IPublishedContent>();
                case "Umbraco.MultiNodeTreePicker":
                    return archetypeProperty.GetValue<IEnumerable<IPublishedContent>>();
                default:
                    return archetypeProperty.Value;
            }            
        }
    }

Again an important point to note here is the StringComparer.InvariantCultureIgnoreCase argument to the ToDictionary call - this makes it case insensitive for key look-ups, which is handy when dealing with property aliases that are usually camel cased in Umbraco aliases but Pascal cased in C# classes.

With the latest release of Umbraco mapper, there's a small update to the routine that maps from a dictionary as we are using here. It checks on the types, and if they are found to be IPublishedContent or IEnumerable the mapping routine those types is run for that property - thus allowing them to be mapped in exactly the same way as if we were just mapping the content directly.

4 comments:

  1. I need to use Umbraco Mapper on nested Archetypes. I'm able to build nested dictionaries, but can not see how to get them to map to models properties.

    ReplyDelete
  2. Yes, it's an interesting challenge to say the least. Can't say I've got a method I'm completely happy with, as you have to do some quite custom things depending on your specific Archetype. But that well be the nature of it. Once done though, the code is all nicely packaged away inside a custom mapping, and after than any use of the matching property on your view models will be automatically mapped.

    I'll put up another blog post illustrating this in the next day or so and update with comment here when done.

    ReplyDelete
  3. As promised - see what you think at: http://web-matters.blogspot.it/2014/11/umbraco-mapper-with-nested-archetypes.html

    ReplyDelete
  4. this might help also... https://bitbucket.org/dascoba/dascoba-common/src/5317d653f2ad88191fe7fdf99125364559076315/Dascoba.Common.Umbraco.V7/Extensions/ArchetypeExtensions.cs?at=master

    ReplyDelete