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);

Awesome!

13 thoughts on “Domain entities vs presentation model objects, part 2: mapping

  1. Awesome posts. Funny how developers across the world can be working on the same things. :)

  2. Can you provide an example of how to unit test a controller which uses a ViewModel class that get’s automapped?

    I am struggling to work out how i can Mock an automapped ViewModel to test my controllers actions.

    Thanks,
    Paul

  3. In a case where a business entity has a collection of another business entity. How would I manage the presentation entity. In this case I can’t have a flattered presentation entity.

  4. Piggybacking on Vaibhav’s question: Suppose you have a collection that you are passing to the view to populate a dropdown list. Your view takes in a collection/list but the view should return a selected value from that list. How do you handle getting back a single selected value in your view model?

    Whenever I have dropdown lists, it seems I can pass a model to the view but I can’t use the model to get the postback data as the fields that are the lists return null values. Help me Obiwan, you’re my only hope!

  5. I guess the challenge is what does that model look like where you pass in a list to the view for a particular field but then get only a single value posted from that same field. I’m not sure how the HTML looks for that field in the view.

    Maybe its me. I am I the only one on the planet that uses dropdown lists as input fields on a form? In all of the examples lifted up in the MVC community, no one seems to address it in a way that a newbie like myself can grasp the approach. Thanks for your prompt response Richard.

  6. Not at all. It’s just the fact you’re throwing lots of data at the view to populate it then only interested in reading one or two values back. So you can’t use the same model for both.

  7. @Richard, should we use different model for populating data on the form, and a different model for getting the data from the form.
    @Darryl, Thanks for asking this question.

  8. I think I may have found an approach that will work with dropdownlists and reading the information back in the controller. Check out this Validating The Contact Manager example. Although it doesn’t directly address dropdowns, it has one in the example.

    It would appear that you could retrieve data back from the view using the model but you need to exclude the dropdown fields as part of the parameters of the Action definition. You can then specificially get the values back from the view using Viewdata and the name of the field in the view. I haven’t exactly tried this approach but it makes sense and it would allow me to use viewmodels that have lists in them. I hope this is helpful.

    Richard: Have you done any blog posts on cascading dropdownlists where the contents of one list is filtered by a selection in a preceeding list?

Comments are closed.