ASP.NET MVC Basics Part 1: View Model binding

I’m going to walk through the basics of Form submission with ASP.NET MVC, showing some best practices. This set of tutorials will be useful for developers moving away from ASP.NET WebForms into ASP.NET MVC or even Rails developers curious about how we do things in .NET.

You can download the code for Part 1 at: https://github.com/edandersen/mvcformtutorial/tree/part1

Form submission flow

If you have come from WebForms, you’ll be used to being able to pull form values out in the code behind by simply referencing variables in your code behind. These magically map to elements on the page and most of the time you are blissfully unaware how the data gets there. With MVC, we don’t have the same abstraction. Whereas you can access POSTed variables directly with FormsCollection (or params in Rails) but with the ViewModel pattern, we can simulate the binding that ASP.NET provides and access our form variables in a strongly typed manner.

aspnet-form-submission-1

Starting the project

Above is a brief overview of how this pattern works. Starting in the top left, the user accesses the Create action. The Action method provides a new CreateViewModel object to the Create.cshtml view, which renders the Form using the data in the ViewModel. When POSTing the form back to the Controller, the default ASP.NET MVC DataBinder maps the POSTed Form values to Properties on the ViewModel class and provides it as a parameter to the HTTP POST overload of the Create action. Depending on the validity of the model, the POST overload of the Create action returns the same form back to the user (showing the validation errors), or redirects to another page.

Lets build a simple version of the above flow. First, we create a new “Basic” ASP.NET MVC 4 project in Visual Studio 2012:

01-basic-project

Which gives us an empty project layout:

02-basic-project-start

We need to add some files to this project. Lets first create the ViewModel class under a new folder called “ViewModels” – BookViewModel.cs. We’ll be creating a form for some very simple Book information. The class looks like this:

<pre class="csharpcode"><span class="kwrd">using System;

<span class="kwrd">namespace MVCFormsExample.ViewModels
{
    <span class="kwrd">public <span class="kwrd">class BookViewModel
    {
        <span class="kwrd">public Guid Id { get; set; }
        <span class="kwrd">public <span class="kwrd">string Name { get; set; }
        <span class="kwrd">public <span class="kwrd">string Author { get; set; }
        <span class="kwrd">public DateTime DatePublished { get; set; }
        <span class="kwrd">public <span class="kwrd">int Rating { get; set; }
    }
}

Note how at this stage, this is a simple POCO class with no business logic. This could be a domain model in the Models folder and map to an ORM directly but for all except the most simple use cases, preparing a ViewModel for presentation purposes works out to be more flexible in the long run and less coupled to your database design, which might be a legacy database.

Next, add a Controller, BookController.cs in the Controllers folder. Following the flowchart above, we create two actions, Index and Create, with a Post overload for the Create action that accepts a BookViewModel parameter. This method must be marked with [HttpPost] to tell the MVC framework to use this for POST requests (otherwise the route would be ambiguous). As above, this checks that the ModelState is Valid, redirects to Index if it is and returns the same ViewModel to the View to render validation errors if not.

<pre class="csharpcode"><span class="kwrd">using System.Web.Mvc;
<span class="kwrd">using MVCFormsExample.ViewModels;

<span class="kwrd">namespace MVCFormsExample.Controllers
{
    <span class="kwrd">public <span class="kwrd">class BookController : Controller
    {
        <span class="kwrd">public ActionResult Index()
        {
            <span class="kwrd">return View();
        }

        <span class="kwrd">public ActionResult Create()
        {
            <span class="kwrd">return View(<span class="kwrd">new BookViewModel());
        }

        [HttpPost]
        <span class="kwrd">public ActionResult Create(BookViewModel viewModel)
        {
            <span class="kwrd">if (ModelState.IsValid)
            {
                <span class="rem">// TODO: Save book
                <span class="kwrd">return RedirectToAction(<span class="str">"Index");
            }

            <span class="kwrd">return View(viewModel);
        }
    }
}

Because we don’t have a Home controller, we need to edit our default route to point to the Books controller. Open App\_Start/RouteConfig.cs and change the default route to look like this:

<pre class="csharpcode">routes.MapRoute(
                name: <span class="str">"Default",
                url: <span class="str">"{controller}/{action}/{id}",
                defaults: <span class="kwrd">new { controller = <span class="str">"Book", action = <span class="str">"Index", id = UrlParameter.Optional }
            );

Now we can create our Views. The Index View will now be the default view for when we start our App, which will simply contain a link to the Create page. Create a Books folder under the Views folder and add the file Index.cshtml:

<pre class="csharpcode">@model dynamic

@{
    ViewBag.Title = "Books";
}

<span class="kwrd"><<span class="html">h2<span class="kwrd">>@ViewBag.Title<span class="kwrd"></<span class="html">h2<span class="kwrd">>

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

We can also add the initial version of the Create view as Create.cshtml. Note that the model type of the view is our BookViewModel:

<pre class="csharpcode">@model MVCFormsExample.ViewModels.BookViewModel

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

<span class="kwrd"><<span class="html">h2<span class="kwrd">>@ViewBag.Title<span class="kwrd"></<span class="html">h2<span class="kwrd">>

The project should look like this now:

03-basic-project-complete

If you run the project, you’ll see the initial Index page load up:

04-index-page

And clicking the link goes to the empty Create page:

05-create-page

Creating the form

On Create.cshtml, we can create Labels, Inputs and Validation messages for each element in the ViewModel. Update Create.cshtml to look like this:

<pre class="csharpcode">@model MVCFormsExample.ViewModels.BookViewModel

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

<span class="kwrd"><<span class="html">h2<span class="kwrd">>@ViewBag.Title<span class="kwrd"></<span class="html">h2<span class="kwrd">>

@using (Html.BeginForm())
{
    @Html.HiddenFor(m =<span class="kwrd">> m.Id)

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

    <span class="kwrd"><<span class="html">button <span class="attr">type<span class="kwrd">="submit"<span class="kwrd">>Create<span class="kwrd"></<span class="html">button<span class="kwrd">>
}

We start with using Html.BeginForm, which wraps the containing markup in <form> tags. The default HTTP method is POST and the default Action to post to is the same as the current route – in this case Create. For the Id field, we only use the built in Html.HiddenFor helper since we don’t want to display it. For the remaining fields of the BookViewModel, we need to use Html.LabelFor, Html.EditorFor and Html.ValidationMessageFor, including some HTML markup to put each div on it’s own new line (the CSS is included with the “Basic” template). Finally a submit button will POST the form.

If you now run the project and go to the Create form, it will look like this:

06-create-page-form

You can also click Create. Because the data on the form is Valid, it will redirect to the Index page. The data is not valid of course, so we need to add some Validation.

The ViewModel should be updated to include some DataAnnotations. These annotations are used by the MVC framework to validate the model data on submission. Lets update the ViewModel, with [Required] attributes on Id, Name, Author, DatePublished, a Range attribute on Rating (between 1 and 5 stars), a Date datatype on DatePublished and Display attributes to override the Label value. We also switch the type of DatePublished from DateTime to Nullable<DateTime>, to prevent the date of “1/1/0001 12:00:00AM” from being the default value:

<pre class="csharpcode"><span class="kwrd">using System;
<span class="kwrd">using System.ComponentModel.DataAnnotations;

<span class="kwrd">namespace MVCFormsExample.ViewModels
{
    <span class="kwrd">public <span class="kwrd">class BookViewModel
    {
        [Required]
        <span class="kwrd">public Guid Id { get; set; }
        
        [Required]
        <span class="kwrd">public <span class="kwrd">string Name { get; set; }
        
        [Required]
        <span class="kwrd">public <span class="kwrd">string Author { get; set; }
        
        [DataType(DataType.Date)]
        [Required]
        [Display(Name = <span class="str">"Date published")]
        <span class="kwrd">public DateTime? DatePublished { get; set; }
        
        [Range(1,5)]
        [Display(Name = <span class="str">"Rating (1-5)")]
        <span class="kwrd">public <span class="kwrd">int Rating { get; set; }
    }
}

If you now reload the page and submit the form, you’ll notice that instead of a redirect, the validation has kicked in and the appropriate messages have been added:

07-create-page-form-invalid

By correcting the errors, the form will submit and redirect to the Index action.

Summary

Part 1 here has shown you how to bind a View Model to a form in ASP.NET MVC, along with some basic Validation rules being applied. In Part 2, I’ll walk through persisting the data in temporary memory and creating the Edit action.