Thursday, 7 August 2014

Using Umbraco Mapper with Archetype

A post came up today on the Umbraco forum discussing use of the Umbraco Mapper package I've built with colleagues at Zone, and everyone's favourite new package, Archetype. We haven't had chance to work with Archetype as yet, but are planning to use it on an upcoming project, so it seemed a good idea to work out the best way for these two packages to play together.

It's a little tricky to begin with as Umbraco Mapper is primarily about mapping from IPublishedContent - single Umbraco nodes or collections picked from a node picker or pulled together via a node query - to strongly typed view models.

However it also supports mapping from a simple dictionary of strings and objects - Dictionary<string, object> - and that, along with the ability to define custom mappings for a particular view model type, means this can be done fairly cleanly.

Note: haven't found a clean way to handle picked content yet - e.g. from content picker or multi-node tree picker... will update if and when we do.. This is supported now - see the follow-up post.

To take an example, let's say I'm creating a page to display a list of football results, and so have created an archetype to represent a series of matches:

I've then created a view model to represent this information:

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

        public IEnumerable TodaysMatches { get; set; }
    }

    public class FootballMatch
    {
        public string HomeTeam { get; set; }

        public int HomeTeamScore { get; set; }
            
        public string AwayTeam { get; set; }

        public int AwayTeamScore { get; set; }
    }

Which will be rendered in my view template like this:

    <h3>Today's Matches</h3>

    @foreach (var match in Model.TodaysMatches)
    {
        
@match.HomeTeam @match.HomeTeamScore v @match.AwayTeamScore @match.AwayTeam
}

As it stands, Umbraco Mapper won't know how to handle mapping to the IEnumerable<FootballMatch> as it's a type it knows nothing about. We can rectify this by setting up a custom mapping. You add this code just after where you have instantiated the mapper, probably best in a base controller so all custom mappings can be initialised in one place.

    Mapper.AddCustomMapping(typeof(IEnumerable<FootballMatch>).FullName, 
        ArchetypeMapper.MapFootballMatch);

And then we need to write the function that will handle the mapping for the given type:

    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 => FirstCharToUpper(m.Alias), m => m.Value));
                mapper.MapCollection(archetypeAsDictionary, result);
            }

            return result;
        }

        private static string FirstCharToUpper(string text)
        {
            return text.Substring(0, 1).ToUpper() + text.Substring(1);
        }
    }

The trick here as you can see is that we are taking the strongly type ArchetypeModel, converting it to a dictionary and the utilising the mapper to map to the view model.

With that in place, the view model will be mapped as required and the football match results will be displayed in the view.

Thanks to Shinsuke and Raffaele for raising the questions and coming up with most of the answers!

See part 2 for details on handling mapping of content pickers used in an Archetype.

Another possible approach

What's also interesting here is that we are using Umbraco Mapper to effectively map from one strongly typed model to another - albeit via a dictionary. And that rings some bells for me - i.e. AutoMapper. This is a tool I've used on many projects outside of Umbraco for mapping from domain models to view models and between other types - it was trying to replicate this within Umbraco that led to the development of Umbraco Mapper.

So if preferred, you could certainly utilise AutoMapper here, to go from the Archetype model to the view model. For me I'm OK with going via the dictionary as above, and perhaps having two mapping components in one project could get a bit confusing. But given we are moving into the world of strongly typed models with Umbraco these days via the property value converters, it's certainly another approach worth considering.

1 comment:

  1. I've built hierarchal archetype mapping using UmbracoMapper.. it's pretty nice if anyone wants it, let me know

    ReplyDelete