Tuesday, 25 October 2011

Using View Model with AutoMapper in MVC

Tools used when writing this post: MVC3, MvcScaffolding 1.0.0, EntityFramework 4.1.10331.0 and AutoMapper 2.0.0.

In this post, we will see a simple example of how to map a view model to a model class with AutoMapper. To understand more about view model, you may want to read http://rachelappel.com/use-viewmodels-to-manage-data-amp-organize-code-in-asp.net-mvc-applications. For more information about AutoMapper, see "AutoMapper - Getting Started".

Say we have a model class called Arena that is used by a database context class for Entity Framework. Then we have a view model for this class called ArenaViewModel that is created to handle slightly different form validation requirements from the original model class. Below are the model class and its view model:
public class Arena
{
    public int ArenaId { get; set; }
        
    [StringLength(150)]
    public string Name { get; set; }

    [StringLength(350)]
    public string Location { get; set; }
        
    public int NumberOfSeats { get; set; }
}

public class ArenaViewModel
{
    public int ArenaId { get; set; }

    //input requirement: field is required and can only have maximum 10 characters length 
    [Required]
    [StringLength(10)]
    public string Name { get; set; }

    //input requirement: field is required and can only have maximum 10 characters length
    [Required]
    [StringLength(10)]
    public string Location { get; set; }
                
    //input requirement: allow empty value (as this will be translated to 0 when saving to database)
    public int? NumberOfSeats { get; set; }
}

Then to set up AutoMapper, first copy and paste the class below to our project. Specify the mapping profile with a correct profile class name that we are going to create (line 6).
public class AutoMapperConfiguration
{
    public static void Configure()
    {
        // specify the mapping profile
        Mapper.Initialize(x => x.AddProfile<ViewModelProfile>());

        // Put this in unit testing later!
        // verify mappings
        Mapper.AssertConfigurationIsValid();
    }
}

Next, create the profile class. A profile class is used to centralized many mapping configurations in one place. A different profile can have different mapping configurations and formatting rules of similar entities in the other profile.
public class ViewModelProfile : Profile
{
    public override string ProfileName
    {
        get { return "ViewModel"; }
    }

    protected override void Configure()
    {
        // specify all mapping configurations here

        CreateMap<ArenaViewModel, Arena>();
    }
}
Note that this class is derived from Profile base class.

Then call
AutoMapperConfiguration.Configure();
from Application_Start() method on Global.asax.cs.

Say we already have a view called AddArenaInfo.cshtml that uses ArenaViewModel to add a new arena information:
@model MvcScaffoldTest.ViewModels.ArenaViewModel
@using (Html.BeginForm())
{
    @Html.ValidationSummary(true)
    <div class="editor-label">
        @Html.LabelFor(model => model.Name)
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => model.Name)
        @Html.ValidationMessageFor(model => model.Name)
    </div>

    <div class="editor-label">
        @Html.LabelFor(model => model.Location)
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => model.Location)
        @Html.ValidationMessageFor(model => model.Location)
    </div>

    <div class="editor-label">
        @Html.LabelFor(model => model.NumberOfSeats)
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => model.NumberOfSeats)
        @Html.ValidationMessageFor(model => model.NumberOfSeats)
    </div>

    <p>
        <input type="submit" value="Create" />
    </p>
}

Then we would be able to do this on our controller:
[HttpPost]
public ActionResult AddArenaInfo(ArenaViewModel arenaVwMdl)
{
    if (ModelState.IsValid)
    {
        //map source to destination
        var arenaMdl = Mapper.Map<ArenaViewModel, Arena>(arenaVwMdl);
        context.Arenas.Add(arenaMdl);
        context.SaveChanges();
        return RedirectToAction("Index");
    }

    return View(arenaVwMdl);
}
AutoMapper will map those two objects automatically. We don't need to specify extra mapping configurations in this case because both of the classes have similar properties that can be easily matched by AutoMapper.

References:
http://mhinze.com/2009/07/06/automapper-in-nerddinner/
http://elegantcode.com/2009/10/06/automapper-introduction-and-samples-from-netdug/

No comments: