Thursday, 13 November 2014

Umbraco Mapper with Nested Archetypes

Following a previous blog post I got a comment asking about dealing with mapping nested Archetypes to a view model using Umbraco Mapper. As I noted in response, it's not that straightforward - the flexibility of the Archetype package means really you have to build something fairly custom for your particular type you are mapping to. Rather than squeeze too much into a comment though, thought the topic worth a post in itself.

As before, looking for a simple example, I've modelled a football match but I've changed it a little to use a nested Archetype. There's a top-level type that contains a single field set to which a text field and two nested score types can be added. The score type has fields for the team and the score.

From the content editor's perspective it looks like this:

I'm looking to map this to a view model that looks like this:

    public class FootballMatch
    {
        public FootballMatch()
        {
            Scores = new List<FootballMatchScore>();
        }

        public string Venue { get; set; }

        public IList<FootballMatchScore> Scores { get; set; }
    }

    public class FootballMatchScore
    {
        public string Team { get; set; }

        public int Score { get; set; }
    }

Which can then be used in the views like this:

    <h3>Today's Match at @Model.TodaysMatch.Venue</h3>

    @foreach (var score in Model.TodaysMatch.Scores)
    {
        <div>@score.Team @score.Score</div>
    }        

The trick as before is to register a custom mapping for the top-level type that will be mapped, like this:

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

And the implementation looks like this:

    public static object MapFootballMatch(IUmbracoMapper mapper, IPublishedContent contentToMapFrom, string propName, bool isRecursive)
    {
        // Get the strongly typed ArchetypeModel from the given property
        var archetype = contentToMapFrom.GetPropertyValue<ArchetypeModel>(propName, isRecursive);
        if (archetype != null)
        {
            // Pull out the first fieldset
            var fieldset = archetype.FirstOrDefault();
            if (fieldset != null)
            {
                // Instantiate an instance of type we are mapping to
                var result = new FootballMatch();

                // Convert the fieldset to a dictionary and map the top-level properties
                var fieldsetAsDictionary = fieldset.Properties.ToDictionary(
                     k => FirstCharToUpper(k.Alias),
                     v => v.Value);
                mapper.Map(fieldsetAsDictionary, result);

                // Given the known aliases for the nested type, loop through and map all the properties of each
                // of these too
                var aliases = new string[] { "homeTeam", "awayTeam" };
                foreach (var alias in aliases)
                {
                    var scoresModel = fieldset.GetValue<ArchetypeModel>(alias);

                    foreach (var innerFieldSet in scoresModel)
                    {
                        var innerFieldsetAsDictionary = innerFieldSet.Properties.ToDictionary(
                            k => FirstCharToUpper(k.Alias),
                            v => v.Value);
                        var score = new FootballMatchScore();
                        mapper.Map(innerFieldsetAsDictionary, score);
                        result.Scores.Add(score);
                    }
                }

                return result;
            }
        }

        return null;
    }

As I say, it would be nice to come up with something more generic to solve this type of problem, and we have managed this to an extent on a recent project that used a number of Archetypes. The flexibility of Archetype does perhaps come at a cost here from a developer perspective. It does seem fairly unavoidable that you'll have to do something quite custom depending on what exactly you are mapping from and too.

Nonetheless once done and set up as a custom mapping within the mapper, any instance of your view model type will be automatically mapped from the given Archetype.

Sunday, 2 November 2014

Using Umbraco Mapper with the Umbraco Grid

Like most people in the Umbraco community I've been keen to download and have a play with Umbraco 7.2, released in beta last week. There's a lot of new features, but the shiniest new toy is without doubt the grid.

This allows a developer to set up a very flexible interface for the editor to manage their content in a layout that closely reflects what they'll see on the website.

When it comes to rendering out the content of the grid, it's very simple. In your view you can just make a call to CurrentPage.GetGridHtml("myGridAlias") and this will spit out the HTML wrapped with bootstrapped elements. You can pass in overrides to use a different template, and create your own.

Nothing wrong with this at all but my preferred approach when rendering out Umbraco content is to create strongly typed view models and, via a hijacked route, map the Umbraco content to this model with the help of the Umbraco Mapper package. Once done, this provides a very clean view model for front-end developers to work with, avoiding the need to work with the more complex Umbraco APIs and dynamics in the views.

Clearly the output of the grid, even if strongly typed, is still going to be relatively "loose" in that we need to be able to handle all that we allow our editors to throw at it. Nonetheless, it seemed to a useful exercise to see how Umbraco Mapper could be used to handle mapping from the Grid data type - and am pleased to say it looks to be rather straightforward.

In order to have something to map too we need our own custom class. The following has been constructed to follow the structure of the JSON format that the grid data is stored in the database:

namespace MyApp.Models
{
    using System.Collections.Generic;
    using System.Web.Mvc;
    using Newtonsoft.Json;

    public class GridContent
    {
        public IList<GridSection> Sections { get; set; }

        public class GridSection
        {
            public int Size { get; set; }

            public IList<GridRow> Rows { get; set; }

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

                public IList<GridArea> Areas { get; set; }

                public class GridArea
                {
                    public IList<GridControl> Controls { get; set; }

                    public class GridControl
                    {
                        public object Value { get; set; }

                        public GridEditor Editor { get; set; }

                        public object TypedValue 
                        {
                            get
                            {
                                switch (Editor.Alias)
                                {
                                    case "headline":
                                    case "quote":
                                        return Value.ToString();
                                    case "rte":
                                        return new MvcHtmlString(Value.ToString());
                                    case "media":
                                        return JsonConvert.DeserializeObject<GridContentMediaValue>(Value.ToString());
                                    default:
                                        return string.Empty;
                                }
                            }
                        }

                        public class GridEditor
                        {
                            public string Alias { get; set; }
                        }
                    }
                }
            }
        }
    }
}

A key property to note is TypedValue. This is used to convert the raw value saved for the grid control element into a typed value appropriate for the content that is stored. This might be a simple string or something more complex like a media item. The implementation of this property would need to be extended to handle all the types we allow our editors to use.

For example here's a complex type representing a media item, again following the JSON structure of the saved information:

namespace MyApp.Models
{
    public class GridContentMediaValue
    {
        public int Id { get; set; }

        public string Image { get; set; }

        public string Thumbnail { get; set; }
    }
}

Then for a given page, I've created a view model that looks like this, following the structure of my document type that uses the grid data type.

namespace MyApp.Models
{
    public class GridPageViewModel
    {
        public string Heading { get; set; }

        public GridContent Grid { get; set; }
    }
}

Umbraco Mapper has a handy feature called custom mappings. These can be set up when the mapper is instantiated - usually in a single location like a base controller - to effectively say, "whenever you are given a given type on a view model to map to, use this function".

With that feature, I'm able to do this in my controller:

namespace MyApp.Controllers
{
    using System.Web.Mvc;
    using Newtonsoft.Json;
    using Umbraco.Core.Models;
    using Umbraco.Web;
    using Umbraco.Web.Models;
    using Umbraco.Web.Mvc;
    using Umbraco72a.Models;
    using Zone.UmbracoMapper;

    public class GridPageController : RenderMvcController
    {
        public override ActionResult Index(RenderModel model)
        {
            var mapper = new UmbracoMapper();
            mapper.AddCustomMapping(typeof(GridContent).FullName, MapGridContent);

            var vm = new GridPageViewModel();
            mapper.Map(CurrentPage, vm);

            return View("Grid", vm);
        }

        public static object MapGridContent(IUmbracoMapper mapper, IPublishedContent contentToMapFrom, string propName, bool isRecursive)
        {
            var gridJson = contentToMapFrom.GetPropertyValue<string>(propName, isRecursive);
            return JsonConvert.DeserializeObject<GridContent>(gridJson);
        }   
 }
}

Which finally allows me to render my grid content in a very clean, strongly typed way, like this:

@model MyApp.Models.GridPageViewModel

<div>
 <h1>@Model.Heading</h1>

 @foreach (var section in Model.Grid.Sections)
 {
  <div>
   @foreach (var row in section.Rows)
   {
    <div>
     @foreach (var area in row.Areas)
     {
      <div>
       @foreach (var control in area.Controls)
       {
        <div class="@control.Editor.Alias">
         @switch (control.Editor.Alias)
         {
          case "media":
           var media = control.TypedValue as GridContentMediaValue;
           <img src="@media.Image" />
           break;
          default:
           <span>@control.TypedValue</span>
           break;
         }                                        
        </div>
       }
      </div>   
     }
    </div>
   }
  </div>
 }
</div>

For me this looks to provide a nice balance between allowing the editor the flexibility that the grid editor provides whilst retaining the strongly typed view model and mark-up generation code in the view. Would love to hear comments from anyone else playing with the grid editor and similarly considering best practices for using it.