"
ASP.NET (snapshot 2017) Microsoft documentation and samples

Use ViewData and Implement ViewModel Classes

by Microsoft

Download PDF

This is step 6 of a free “NerdDinner” application tutorial that walks-through how to build a small, but complete, web application using ASP.NET MVC 1.

Step 6 shows how enable support for richer form editing scenarios, and also discusses two approaches that can be used to pass data from controllers to views: ViewData and ViewModel.

If you are using ASP.NET MVC 3, we recommend you follow the Getting Started With MVC 3 or MVC Music Store tutorials.

NerdDinner Step 6: ViewData and ViewModel

We’ve covered a number of form post scenarios, and discussed how to implement create, update and delete (CRUD) support. We’ll now take our DinnersController implementation further and enable support for richer form editing scenarios. While doing this we’ll discuss two approaches that can be used to pass data from controllers to views: ViewData and ViewModel.

Passing Data from Controllers to View-Templates

One of the defining characteristics of the MVC pattern is the strict “separation of concerns” it helps enforce between the different components of an application. Models, Controllers and Views each have well defined roles and responsibilities, and they communicate amongst each other in well defined ways. This helps promote testability and code reuse.

When a Controller class decides to render an HTML response back to a client, it is responsible for explicitly passing to the view template all of the data needed to render the response. View templates should never perform any data retrieval or application logic – and should instead limit themselves to only have rendering code that is driven off of the model/data passed to it by the controller.

Right now the model data being passed by our DinnersController class to our view templates is simple and straight-forward – a list of Dinner objects in the case of Index(), and a single Dinner object in the case of Details(), Edit(), Create() and Delete(). As we add more UI capabilities to our application, we are often going to need to pass more than just this data to render HTML responses within our view templates. For example, we might want to change the “Country” field within our Edit and Create views from being an HTML textbox to a dropdownlist. Rather than hard-code the dropdown list of country names in the view template, we might want to generate it from a list of supported countries that we populate dynamically. We will need a way to pass both the Dinner object and the list of supported countries from our controller to our view templates.

Let’s look at two ways we can accomplish this.

Using the ViewData Dictionary

The Controller base class exposes a “ViewData” dictionary property that can be used to pass additional data items from Controllers to Views.

For example, to support the scenario where we want to change the “Country” textbox within our Edit view from being an HTML textbox to a dropdownlist, we can update our Edit() action method to pass (in addition to a Dinner object) a SelectList object that can be used as the model of a countries dropdownlist.

[!code-csharpMain]

   1:  //
   2:  // GET: /Dinners/Edit/5
   3:   
   4:  [Authorize]
   5:  public ActionResult Edit(int id) {
   6:   
   7:      Dinner dinner = dinnerRepository.GetDinner(id);
   8:   
   9:      ViewData["Countries"] = new SelectList(PhoneValidator.AllCountries, dinner.Country);
  10:   
  11:      return View(dinner);
  12:  }

The constructor of the SelectList above is accepting a list of counties to populate the drop-downlist with, as well as the currently selected value.

We can then update our Edit.aspx view template to use the Html.DropDownList() helper method instead of the Html.TextBox() helper method we used previously:

[!code-aspxMain]

   1:  <%= Html.DropDownList("Country", ViewData["Countries"] as SelectList) %>

The Html.DropDownList() helper method above takes two parameters. The first is the name of the HTML form element to output. The second is the “SelectList” model we passed via the ViewData dictionary. We are using the C# “as” keyword to cast the type within the dictionary as a SelectList.

And now when we run our application and access the /Dinners/Edit/1 URL within our browser we’ll see that our edit UI has been updated to display a dropdownlist of countries instead of a textbox:

Because we also render the Edit view template from the HTTP-POST Edit method (in scenarios when errors occur), we’ll want to make sure that we also update this method to add the SelectList to ViewData when the view template is rendered in error scenarios:

[!code-csharpMain]

   1:  //
   2:  // POST: /Dinners/Edit/5
   3:   
   4:  [AcceptVerbs(HttpVerbs.Post)]
   5:  public ActionResult Edit(int id, FormCollection collection) {
   6:   
   7:      Dinner dinner = dinnerRepository.GetDinner(id);
   8:   
   9:      try {
  10:      
  11:          UpdateModel(dinner);
  12:   
  13:          dinnerRepository.Save();
  14:   
  15:          return RedirectToAction("Details", new { id=dinner.DinnerID });
  16:      }
  17:      catch {
  18:      
  19:          ModelState.AddModelErrors(dinner.GetRuleViolations());
  20:   
  21:          ViewData["countries"] = new SelectList(PhoneValidator.AllCountries, dinner.Country);
  22:   
  23:          return View(dinner);
  24:      }
  25:  }

And now our DinnersController edit scenario supports a DropDownList.

Using a ViewModel Pattern

The ViewData dictionary approach has the benefit of being fairly fast and easy to implement. Some developers don’t like using string-based dictionaries, though, since typos can lead to errors that will not be caught at compile-time. The un-typed ViewData dictionary also requires using the “as” operator or casting when using a strongly-typed language like C# in a view template.

An alternative approach that we could use is one often referred to as the “ViewModel” pattern. When using this pattern we create strongly-typed classes that are optimized for our specific view scenarios, and which expose properties for the dynamic values/content needed by our view templates. Our controller classes can then populate and pass these view-optimized classes to our view template to use. This enables type-safety, compile-time checking, and editor intellisense within view templates.

For example, to enable dinner form editing scenarios we can create a “DinnerFormViewModel” class like below that exposes two strongly-typed properties: a Dinner object, and the SelectList model needed to populate the countries dropdownlist:

[!code-csharpMain]

   1:  public class DinnerFormViewModel {
   2:   
   3:      // Properties
   4:      public Dinner     Dinner    { get; private set; }
   5:      public SelectList Countries { get; private set; }
   6:   
   7:      // Constructor
   8:      public DinnerFormViewModel(Dinner dinner) {
   9:          Dinner = dinner;
  10:          Countries = new SelectList(PhoneValidator.AllCountries, dinner.Country);
  11:      }
  12:  }

We can then update our Edit() action method to create the DinnerFormViewModel using the Dinner object we retrieve from our repository, and then pass it to our view template:

[!code-csharpMain]

   1:  //
   2:  // GET: /Dinners/Edit/5
   3:   
   4:  [Authorize]
   5:  public ActionResult Edit(int id) {
   6:   
   7:      Dinner dinner = dinnerRepository.GetDinner(id);
   8:      
   9:      return View(new DinnerFormViewModel(dinner));
  10:  }

We’ll then update our view template so that it expects a “DinnerFormViewModel” instead of a “Dinner” object by changing the “inherits” attribute at the top of the edit.aspx page like so:

[!code-cshtmlMain]

   1:  Inherits="System.Web.Mvc.ViewPage<NerdDinner.Controllers.DinnerFormViewModel>

Once we do this, the intellisense of the “Model” property within our view template will be updated to reflect the object model of the DinnerFormViewModel type we are passing it:

We can then update our view code to work off of it. Notice below how we are not changing the names of the input elements we are creating (the form elements will still be named “Title”, “Country”) – but we are updating the HTML Helper methods to retrieve the values using the DinnerFormViewModel class:

[!code-aspxMain]

   1:  <p>
   2:      <label for="Title">Dinner Title:</label>
   3:      <%= Html.TextBox("Title", Model.Dinner.Title) %>
   4:      <%=Html.ValidationMessage("Title", "*") %>
   5:  </p>
   6:   
   7:  <p>
   8:      <label for="Country">Country:</label>
   9:      <%= Html.DropDownList("Country", Model.Countries) %>                
  10:      <%=Html.ValidationMessage("Country", "*") %>
  11:  </p>

We’ll also update our Edit post method to use the DinnerFormViewModel class when rendering errors:

[!code-csharpMain]

   1:  //
   2:  // POST: /Dinners/Edit/5
   3:   
   4:  [AcceptVerbs(HttpVerbs.Post)]
   5:  public ActionResult Edit(int id, FormCollection collection) {
   6:   
   7:      Dinner dinner = dinnerRepository.GetDinner(id);
   8:   
   9:      try {
  10:          UpdateModel(dinner);
  11:   
  12:          dinnerRepository.Save();
  13:   
  14:          return RedirectToAction("Details", new { id=dinner.DinnerID });
  15:      }
  16:      catch {
  17:          ModelState.AddModelErrors(dinner.GetRuleViolations());
  18:   
  19:          return View(new DinnerFormViewModel(dinner));
  20:      }
  21:  }

We can also update our Create() action methods to re-use the exact same DinnerFormViewModel class to enable the countries DropDownList within those as well. Below is the HTTP-GET implementation:

[!code-csharpMain]

   1:  //
   2:  // GET: /Dinners/Create
   3:   
   4:  public ActionResult Create() {
   5:   
   6:      Dinner dinner = new Dinner() {
   7:          EventDate = DateTime.Now.AddDays(7)
   8:      };
   9:   
  10:      return View(new DinnerFormViewModel(dinner));
  11:  }

Below is the implementation of the HTTP-POST Create method:

[!code-csharpMain]

   1:  //
   2:  // POST: /Dinners/Create
   3:   
   4:  [AcceptVerbs(HttpVerbs.Post)]
   5:  public ActionResult Create(Dinner dinner) {
   6:   
   7:      if (ModelState.IsValid) {
   8:   
   9:          try {
  10:              dinner.HostedBy = "SomeUser";
  11:   
  12:              dinnerRepository.Add(dinner);
  13:              dinnerRepository.Save();
  14:   
  15:              return RedirectToAction("Details", new { id=dinner.DinnerID });
  16:          }
  17:          catch {
  18:              ModelState.AddModelErrors(dinner.GetRuleViolations());
  19:          }
  20:      }
  21:   
  22:      return View(new DinnerFormViewModel(dinner));
  23:  }

And now both our Edit and Create screens support drop-downlists for picking the country.

Custom-shaped ViewModel classes

In the scenario above, our DinnerFormViewModel class directly exposes the Dinner model object as a property, along with a supporting SelectList model property. This approach works fine for scenarios where the HTML UI we want to create within our view template corresponds relatively closely to our domain model objects.

For scenarios where this isn’t the case, one option that you can use is to create a custom-shaped ViewModel class whose object model is more optimized for consumption by the view – and which might look completely different from the underlying domain model object. For example, it could potentially expose different property names and/or aggregate properties collected from multiple model objects.

Custom-shaped ViewModel classes can be used both to pass data from controllers to views to render, as well as to help handle form data posted back to a controller’s action method. For this later scenario, you might have the action method update a ViewModel object with the form-posted data, and then use the ViewModel instance to map or retrieve an actual domain model object.

Custom-shaped ViewModel classes can provide a great deal of flexibility, and are something to investigate any time you find the rendering code within your view templates or the form-posting code inside your action methods starting to get too complicated. This is often a sign that your domain models don’t cleanly correspond to the UI you are generating, and that an intermediate custom-shaped ViewModel class can help.

Next Step

Let’s now look at how we can use partials and master-pages to re-use and share UI across our application.

Previous Next



Comments ( )
Link to this page: //www.vb-net.com/AspNet-DocAndSamples-2017/aspnet/mvc/overview/older-versions-1/nerddinner/use-viewdata-and-implement-viewmodel-classes.htm
< THANKS ME>