Domain entities vs presentation model objects, part 2: mapping

Domain entities vs presentation model objects, part 2: mapping

This is the second half of a two-part article. Read the first half here: Domain entities vs presentation model objects.

In my last post, I wrote about the difference between domain entities and presentation model objects. Remember my two task classes — the transactional domain entity and the UI presentation object? They’re very similar, and this could lead to a lot of ugly hand-written plumbing code mapping fields on one to the other.

// Task domain entity.public class Task{    public int Id;    public string Name;    public DateTime? DueDate;    // ...etc.}// Task presentation model object.public class TaskView{    public int Id;    public string Name;    public string DueDate;    public bool IsUnscheduled;    public bool IsOverDue;    public long SortIndex;}

Instead, I’m using an open-source .NET library called AutoMapper by Jimmy Bogard — an object-object mapper (OOM) that sets values from one type to another.

Setting up a map from one type to another is dead simple — AutoMapper will automatically match fields with the same name. For other stuff we use lambda expressions, or delegate to another class. Here’s what my Task-to-TaskView mapping looks like:

// Set up a map between Task and TaskView. Note fields with the same names are// mapped automagically!Mapper.CreateMap<Task, TaskView>()    .ForMember(dest => dest.DueDate, opt => opt.AddFormatter<DueDateFormatter>())    .ForMember(dest => dest.SortIndex, opt => opt.ResolveUsing<SortIndexResolver>());

That was easy! Note I’m using a custom value formatter for the DueDate:

public class DueDateFormatter : IValueFormatter{    public string FormatValue(ResolutionContext context)    {        DateTime? d = context.SourceValue as DateTime?;        if (d.HasValue)            return d.Value.ToString("dddd MMM d");        else            return "Anytime";    }}

…and a custom resolver for the sort index (an integer derived from the task’s due date). Note that, while a formatter transforms one field by itself; a resolver examines the whole object to derive a value:

public class SortIndexResolver : IValueResolver{    public ResolutionResult Resolve(ResolutionResult source)    {        Task t = source.Value as Task;        DateTime sortDate = t.DueDate.HasValue ?            t.DueDate.Value : DateTime.MaxValue;        long sortIndex =             Convert.ToInt64(new TimeSpan(sortDate.Ticks).TotalSeconds);        return new ResolutionResult(sortIndex);    }}

With dedicated classes for formatting and resolving values, tests become very easy to write (although I did write my own ShouldFormatValueAs() test helper extension method):

[TestFixture]public class When_displaying_a_due_date{    [Test]    public void Should_display_null_values_as_anytime()    {         new DueDateFormatter().ShouldFormatValueAs<DateTime?>(null, "Anytime");    }    [Test]    public void Should_format_date()    {        new DueDateFormatter().ShouldFormatValueAs<DateTime?>(                new DateTime(2009, 02, 28), "Saturday Feb 28");    }}

Putting it to practice, it becomes a one-liner to create a new TaskView instance given a Task.

// Grab a Task from the repository, and map it to a new TaskView instance.Task task = this.tasks.GetById(...);TaskView taskView = Mapper.Map<Task, TaskView>(task);