ASP.NET MVC Tip #32 – Repopulate Form Fields with ViewData.Eval()
When you need to repopulate the form data in an edit form, displaying both valid and invalid values, use ViewData.Eval() to retrieve the values from both the view data dictionary and the view data Model.
You use view data in an ASP.NET MVC application to pass data from a controller action to a view. You assign the view data to a controller’s ViewData property and you read the view data from a view’s ViewData property.
The MVC ViewData class is a hybrid of two classes. On the one hand, the ViewData class inherits from the base Dictionary class (Dictionary<string,object>). Therefore, it works just like a standard generic Dictionary collection. You add a key and value pair to ViewData like this:
ViewData[“message”] = “Hello World!”;
Within a view, you display an item from the dictionary like this:
<%= ViewData[“message”] %>
On the other hand, the ViewData class exposes a Model property. The Model property is useful when you want to pass a strongly typed object to the view. Within a controller, you assign a value to the Model property like this:
var someMovie = new Movie();
someMovie.Title = “Star Wars”;
ViewData.Model = someMovie;
Within the view (assuming that you have created a typed view) you can access a property of the Model like this:
<%= ViewData.Model.Title %>
A typed view generates a typed ViewData class. In this example, the value of the ViewData.Model property is cast to a Movie type automatically.
So, the ViewData object represents both typed and untyped data. You access the untyped data through the ViewData’s indexer property. You access the typed data through the ViewData’s Model property.
Normally, the typed and untyped worlds never meet. However, the ViewData class supports a special method, named Eval(), that searches both its untyped and typed data .
Why is this useful? I didn’t really understand why the Eval() method is useful until I read the following forum post:
When validating form data, you can use the untyped dictionary to preserve the invalid form field values and the typed Model to represent the original form data. For example, the controller in Listing 1 contains three actions named Index(), Edit() and Update(). The Index() action displays a list of movie links. The Edit() action return an HTML form that can be used to edit a movie record. The Update() action updates the Movie record in the database.
Listing 1 – MovieController.cs
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using Tip32.Models; namespace Tip32.Controllers { [HandleError] public class HomeController : Controller { private MovieDataContext _dataContext; public HomeController() { _dataContext = new MovieDataContext(); } public ActionResult Index() { var movies = from m in _dataContext.Movies select m; ViewData.Model = movies.ToList(); return View("Index"); } public ActionResult Edit(int id) { // Retrieve matching movie var movieToEdit = _dataContext.Movies.SingleOrDefault(m => m.Id == id); this.ViewData.Model = movieToEdit; // Transfer properties from TempData to Model foreach (var prop in TempData) this.ViewData[prop.Key] = prop.Value; // Return view return View("Edit"); } public ActionResult Update(int id, string title, string dateReleased) { // Validate date released DateTime parsedDateReleased; DateTime.TryParse(dateReleased, out parsedDateReleased); // Errors? Redisplay Edit view if (parsedDateReleased == DateTime.MinValue) { TempData["dateReleased"] = dateReleased; return RedirectToAction("Edit", new {id = id }); } // Update movie in database var movieToEdit = _dataContext.Movies.SingleOrDefault(m => m.Id == id); movieToEdit.Title = title; movieToEdit.DateReleased = parsedDateReleased; _dataContext.SubmitChanges(); return RedirectToAction("Index"); } } }
The Edit() action passes a movie to the Edit form as a strongly typed movie object. When a user submits the Edit form to the Update() action, the Update() action validates the modified form data. If there are no validation errors, then the movie is updated in the database and the user is redirected back to the Index view.
If, however, there are validation errors, then the user is redirected back to the Edit() action. The invalid values submitted by the user are passed to the Edit() action through the TempData dictionary.
This is the important point. The Edit() action adds the invalid form values to the untyped view data dictionary and not the typed view data Model object. Imagine that the user entered the value “apple” for the movie Date Released form field. In that case, you could not assign this invalid value to the Model because the DateReleased property is a DateTime property (you cannot assign the string “apple” to a DateTime property).
The untyped dictionary enables you to display invalid values. The typed Model only enables you to display valid values. That’s why you need both.
The Edit view is contained in Listing 2. Notice that the view data is displayed by calling ViewData.Eval(). The ViewData.Eval() method first attempts to retrieve a value from the untyped dictionary. Next, the Eval() method attempts to retrieve the value from a property of the Model.
Listing 2 – Edit.aspx
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" AutoEventWireup="true" CodeBehind="Edit.aspx.cs" Inherits="Tip32.Views.Home.Edit" %> <asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server"> <h1>Edit Movie</h1> <form method="post" action="/Home/Update/<%= ViewData.Model.Id %>"> <label for="title">Title:</label> <br /> <input name="title" value="<%= ViewData.Eval("title") %>" /> <br /><br /> <label for="dateReleased">Date Released:</label> <br /> <input name="dateReleased" value="<%= ViewData.Eval("dateReleased") %>" /> <br /><br /> <input type="submit" value="Edit Movie" /> </form> </asp:Content>
The ViewData.Eval() method is used internally by the standard HTML helpers. That means that you could also create the view like in Listing 3.
Listing 3 – Edit2.aspx
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" AutoEventWireup="true" CodeBehind="Edit2.aspx.cs" Inherits="Tip32.Views.Home.Edit2" %> <asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server"> <h1>Edit Movie</h1> <form method="post" action="/Home/Update/<%= ViewData.Model.Id %>"> <label for="title">Title:</label> <br /> <%= Html.TextBox("title") %> <br /><br /> <label for="dateReleased">Date Released:</label> <br /> <%= Html.TextBox("dateReleased") %> <br /><br /> <input type="submit" value="Edit Movie" /> </form> </asp:Content>
The view in Listing 3 does exactly the same thing as the view in Listing 2. For example, the Html.TextBox() helper renders a text input field. The value of the text field is retrieved internally by calling ViewData.Eval(). An attempt is made to retrieve the value from the view data dictionary. If that fails, an attempt is made to retrieve the value from the Model.
Summary
If you need to validate form data, and you need to repopulate a form with both the valid and invalid data that a user submitted, then use the ViewData.Eval() method. The ViewData.Eval() method correctly searches both the untyped and typed view data.