Ed Andersen

Software Developer and Architect in Japan

ASP.NET MVC Basics Part 2: ViewModel to Model Mapping and Editing

Ed Andersen Avatar

by

in

In Part 1, I walked through creating a simple form with a backing ViewModel and Validation. In Part 2, I’ll walk through creating a backing Model and Edit functionality.

To start off from here, load up the code from part1: https://github.com/edandersen/mvcformtutorial/tree/part1

The final code for Part 2 will be uploaded here: https://github.com/edandersen/mvcformtutorial/tree/part2

Model Mapping flow

viewmodel-model-mapping

When editing a ViewModel, we need to prepopulate the view with real data from a domain model. This can be a database table mapped to an object via an ORM or data from another source. In the diagram above, we can see the GET Edit action requesting Model with an ID of 123 from the Repository holding the model data, creating a ViewModel that represents the Model and passing it onto the View to render. POSTing the data back is similar to the Create POST method in Part 1, except we load the existing Model from the repository, update it with the validated data from the ViewModel and update the model in the Repository.

Creating the Model and Repository

Lets continue from Part 1 and create a Book Model to match the BookViewModel we already have. Again, this is a simple POCO which you can decorate with attributes. Create the following class in the Models folder:

using System;
using System.ComponentModel.DataAnnotations;

namespace MVCFormsExample.Models
{
    public class Book
    {
        [Key]
        public Guid Id { get; set; }

        [Required]
        public string Name { get; set; }

        [Required]
        public string Author { get; set; }

        [Required]
        public DateTime Published { get; set; }

        [Required]
        public int Rating { get; set; }
    }
}

Next up is a data store. In a real project you would want to put this class in another project as a shared library, but for the purposes of this exercise, create a new folder called “Repositories” and create an interface for the BookRepository called IBookRepository.cs:

using System;
using System.Collections.Generic;
using MVCFormsExample.Models;

namespace MVCFormsExample.Repositories
{
    public interface IBookRepository
    {
        Book GetById(Guid id);
        void Upsert(Book book);
        IEnumerable<Book> All { get; }
    }
}

This very simple interface is all we need. Lets implement an in-memory, static BookRepository. Create a file called StaticBookRepository.cs:

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using MVCFormsExample.Models;

namespace MVCFormsExample.Repositories
{
    public class StaticBookRepository : IBookRepository
    {
        private static readonly ConcurrentDictionary<Guid, Book> Books = new ConcurrentDictionary<Guid, Book>();

        public Book GetById(Guid id)
        {
            return !Books.ContainsKey(id) ? null : Books[id];
        }

        public void Upsert(Book book)
        {
            Books[book.Id] = book;
        }

        public IEnumerable<Book> All { get { return Books.Values; } }
    }
}

The list of Books in this Repository will only persist while the application is running (or until the AppPool is restarted) so is useless for real projects. It will however suffice for now. If you want to migrate this to an Entity Framework ORM-backed Repository later, you can reimplement IBookRepository.

Updating the Create action to save the Book

Back in the BookController, we had a TODO to save the book. Now is the time to do it, but we have to first map the validated ViewModel to the Model. Create a new method on the BookController:

        private void UpdateBook(Book book, BookViewModel viewModel)
        {
            book.Id = viewModel.Id;
            book.Author = viewModel.Author;
            book.Name = viewModel.Name;
            book.Published = viewModel.DatePublished.GetValueOrDefault();
            book.Rating = viewModel.Rating;
        }

In real projects and definitely for larger models, you’ll want to use something like AutoMapper. This and other mapping methods could either be in the Controller class or as methods on the ViewModel itself. For very simple cases you can skip the ViewModel step all together and pass the Model directly to the View, but you’ll quickly find cases where this becomes impractical – in addition, if your model has public Properties that should not be set by the user, a specially crafted POST could cause the default ModelBinder to set public Properties on your Model – an “Insecure Direct Object Reference”. Github fell foul to this problem a while ago and was hacked due to database Models being updated directly from POST requests.

The BookController now needs access to the BookRepository. Add a new member variable to reference the newly created StaticBookRepository:

    public class BookController : Controller
    {
        private IBookRepository bookRepository = new StaticBookRepository();

In most projects you’ll see the actual variable value injected in using Dependency Injection. Note how the type of the variable is an interface, so you can swap in a different BookRepository implementation and the rest of the code will be none the wiser. This is coding to contracts and you’ll need to do it to keep your code testable and modular.

Lets update the Create (POST) action to actually now save the book.

        [HttpPost]
        public ActionResult Create(BookViewModel viewModel)
        {
            if (ModelState.IsValid)
            {
                viewModel.Id = Guid.NewGuid();
                var book = new Book();
                UpdateBook(book, viewModel);
                bookRepository.Upsert(book);
                return RedirectToAction("Index");
            }

            return View(viewModel);
        }

Stepping through the new code inside the ModelState.IsValid conditional, we see that we set the Id of the ViewModel to a new Guid (because it will be Guid.Zero at the moment), create a new Book model object, update the values of it from the ViewModel and Insert it into the Repository with the Upsert method. If you get creative with breakpoints, you can now see the book has been inserted into the repository but for now you won’t see anything different if you run the app.

Displaying the list of Books

To see the results of our handiwork, we have to update the Index action to show the list of Books. Updating the Index action is easy:

        public ActionResult Index()
        {
            return View(bookRepository.All);
        }

This simply passes all the books through to the View as the view’s backing model. To display the data, update the Index view to use the new Model type (IEnumerable<Book>) and to contain a table with the data – while we are at it, we’ll include a link to the Edit action that we will create shortly:

@model IEnumerable<MVCFormsExample.Models.Book>

@{
    ViewBag.Title = "Books";
}

<h2>@ViewBag.Title</h2>

@Html.ActionLink("Create", "Create")

<table>
    <thead>
        <tr>
            <th>Name</th>
            <th>Author</th>
            <th>Date Published</th>
            <th>Rating</th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        @foreach (var book in Model)
        {
            <tr>
                <td>@book.Name</td>
                <td>@book.Author</td>
                <td>@book.Published.ToShortDateString()</td>
                <td>@book.Rating</td>
                <td>@Html.ActionLink("Edit","Edit", new {id = book.Id})</td>
            </tr>
        }
    </tbody>
</table>

Now lets test out our new form! You will at first see no data:

01-empty-grid

Click “Create” and fill in the form:

02-sample-data

You’ll see the table populated with the Book you just created. Awesome!

03-sample-data-grid

Edit functionality

With the groundwork for Create laid down, we can reuse most of our existing code to create the Edit functionality outlined in the diagram at the top of this post. Lets create the View first, Edit.cshtml alongside Create.cshtml:

@model MVCFormsExample.ViewModels.BookViewModel

@{
    ViewBag.Title = "Edit Book";
}

<h2>@ViewBag.Title</h2>

@using (Html.BeginForm())
{
    @Html.Partial("_Form")

    <button type="submit">Edit</button>
}

You’ll notice that we have not included all of the form elements in the Edit view, and we haven’t copy and pasted all the elements from the Create view, as that would not be DRY. Lets extract the Form elements out of Create.cshtml into a new Partial view, “_Form”. Create “_Form.cshtml” alongside Edit.cshtml and copy/paste the form elements from Create.cshtml. Don’t forget to set the model type of the view to BookViewModel:

@model MVCFormsExample.ViewModels.BookViewModel

@Html.HiddenFor(m => m.Id)

<div class="editor-label">
    @Html.LabelFor(m => m.Name)
</div>
<div class="editor-field">
    @Html.EditorFor(m => m.Name)
    @Html.ValidationMessageFor(m => m.Name)
</div>
<div class="editor-label">
    @Html.LabelFor(m => m.Author)
</div>
<div class="editor-field">
    @Html.EditorFor(m => m.Author)
    @Html.ValidationMessageFor(m => m.Author)
</div>
<div class="editor-label">
    @Html.LabelFor(m => m.DatePublished)
</div>
<div class="editor-field">
    @Html.EditorFor(m => m.DatePublished)
    @Html.ValidationMessageFor(m => m.DatePublished)
</div>
<div class="editor-label">
    @Html.LabelFor(m => m.Rating)
</div>
<div class="editor-field">
    @Html.EditorFor(m => m.Rating)
    @Html.ValidationMessageFor(m => m.Rating)
</div>

Also update the Create.cshtml view to use the new Partial View instead. Your project should now look like this:

04-project-layout

Test your Create action again to make sure it all still works. With the Edit view done, we can work on the Controller. First, add a method to the BookController for creating a new ViewModel from an existing Book:

        private BookViewModel ViewModelFromBook(Book book)
        {
            var viewModel = new BookViewModel
                {
                    Id = book.Id,
                    Name = book.Name,
                    Author = book.Author,
                    DatePublished = book.Published,
                    Rating = book.Rating
                };
            return viewModel;
        }

This is the counterpart to the UpdateBook method, mapping in the other direction. Finally, add two new Actions for Edit, one for GET and one for POST, very similar to the Create methods:

        public ActionResult Edit(Guid id)
        {
            var book = bookRepository.GetById(id);
            if (book == null) return new HttpNotFoundResult();
            return View(ViewModelFromBook(book));
        }

        [HttpPost]
        public ActionResult Edit(BookViewModel viewModel)
        {
            if (ModelState.IsValid)
            {
                var existingBook = bookRepository.GetById(viewModel.Id);
                UpdateBook(existingBook, viewModel);
                bookRepository.Upsert(existingBook);
                return RedirectToAction("Index");
            }

            return View(viewModel);
        }

Lets walk through these two methods. The first is the GET method, which gets a book by the id parameter that was mapped from the route /Edit/{id}. It then returns a 404 Not Found result if the BookRepository returned null. Finally, it returns the Edit view with a new BookViewModel containing data from the book, mapped by the ViewModelFromBook method. The POST method first checks that the ViewModel is valid using the standard Validation rules, gets the existing book from the BookRepository, updates the book values from the ViewModel, updates the book in the Repository with the Upsert method and finally redirects to the Index method.

Now time to test it! Fire up the app and create a book as before. Or create two books!

04-twobooks

Click the Edit button and the Edit (GET) action will load up. Note in the screenshot below that the Id of the Book you selected is part of the MVC route – this was mapped to the id parameter of the Edit method. Change some data:

05-bookupdate

Clicking the Edit button will update the Book model and you’ll see the updated list:

06-book-update-complete

And there you have it! You now have a very primitive in-memory database of books.

Summary

In this part of the tutorial, we looked at creating a Model, a Repository interface with a static, in-memory implementation, mapping between the ViewModel and Model, updated the Index view to show all the Books, updated the Create action to actually save the data and created an Edit action and View. Phew! In Part 3, I’ll look at creating a separate list of Authors and creating a Drop down <select> list to select from when editing a Book.

Ed Andersen Avatar

About me

Hi! 👋 I’m a Software Developer, Architect and Consultant living in Japan, building web and cloud apps. Here I write about software development and other things I find interesting. Read more about my background.

Ed’s “Newsletter”

Get the latest blog posts via email ✌️


Comments

19 responses to “ASP.NET MVC Basics Part 2: ViewModel to Model Mapping and Editing”

  1. In your method declaration in the controller :
    public ActionResult Create(BookViewModel viewModel)
    Could you have also passed in a Book instead of a BookViewModel and gotten the same result? I think the automatic binding would match up the form fields to the Book object that you pass in. Is there any reason to use one versus the other?

    1. The same result would have likely worked, but you’d lose the separation between the UI model and the database model.

  2. Brian Gullo Avatar
    Brian Gullo

    Great series, looking forward to part 3. I like the continued emphasis on the decoupling between the UI and Database in the articles. I think it’s the natural tendency to make your “Model” in MVC your domain model, when really that’s not the intent at all. The Model, like the ViewModel in your article can be something shaped totally different than your Domain model.

  3. I M NOT GETTING THE StaticBookRepository.cs CLASS’S WHOLE CODE MAINLY private static readonly ConcurrentDictionary Books = new ConcurrentDictionary();
    I WAS DONE MY SMALL PROJECT WITH public class ClassContext : DbContext
    {
    public ClassContext()
    : base(“class1”)
    {
    }

    public DbSet Posting { get; set; }

    }THAT CODE IN MODEL CLASS

  4. so plz explain me what is the use and benefit to use of StaticBookRepository.cs

  5. Nice job bro!!

  6. Marek Avatar

    Hi, so far the best tutorial I could find on net … and I was looking for days! I do not want to push anything, but is there a part 3 yet ? Thanks

  7. @ANU To the best of my knowledge, the only difference between using the DBContext and using the StaticBookRepository.cs is that the DBContext is saved even after the application quits meanwhile the StaticBookRepository exists only during the lifetime of the application or until the app-pool is refreshed. In order words, they are both database implementations and using either is fine for the scope of this tutorial

  8. Also, Great Job Ed, I really enjoyed your tutorial

  9. Where's the definition of BookViewModel? Avatar
    Where’s the definition of BookViewModel?

    Nice example but without the definition of BookViewModel shown we have to guess what it looks like.

  10. Jpeters Avatar

    Very good.. it seems very few people understand how viewmodel binding really works…. once you understand how MVC does it then it’s a tad easier to tailor one’s code. Not understanding it and Not using the HTML.Helpers for strongly typed views gets one into deep trouble fast……and there’s no way to debug it because from MVC’s view it’s all working perfectly!

  11. nice job well done … we get understand MVC …thanks bro

  12. Great tutorial Ed! Thanks!
    Are you still working on Part 3?

  13. Hey Ed. What if we want to add a detail, like a list of Authors. Could you give me some reference?

  14. Hello Sir,how to get checkbox multiple values from database using view specific model using mapping

  15. I have read many books, whatched many videos and take some online courses, but none managed to make me finnally understand View model concept.
    Thank you!

  16. Vincent Saelzler Avatar
    Vincent Saelzler

    Loved the adherence to best practices and leverage of the framework.

    Specifically learned about these
    [DataType(DataType.Date)]
    [Required]
    [Display(Name = “Date published”)]

    Also the situation was a great clarification of howto use @Html.Partial(“_Form”)

  17. Awesome!! really helped me in understanding the back and forth of data between view and controller

  18. Your tutorial is great! But could you please make a tutorial about creating a repository project sharing its contents with another web API project as a shared library and retrieving the data from an existing database(code-first)? I tried searching the web for examples but all are creating a new database along with it.

Leave a Reply

Your email address will not be published. Required fields are marked *