How to Design a Form in Asp.net Using Table

Prologue

Developers usually stumble with default binding when handling the HttpPost action. For example: if you have view, view model, controller actions, and data all wired-up and it looks like your form should be working, but your model is empty or partially empty when it hits the controller after you press "Save", you probably need to make some adjustments to the view to get the Razor engine to compose the HTML properly. Getting the code for the client side right is essential, and it fails silently when it's wrong.

If you just need help with that, jump to the section on Saving Data.

If you feel you would benefit from a more complete case study, read on.

Introduction

Many developers know that they can create forms on web pages with a minimum of code using ASP.NET model binding. Visual Studio's default MVC view templates will even create a standard list, create, edit, and delete views without any additional programming.

But the power of default model binding extends beyond the flat data model of a simple input form or list of records. Using a few straightforward coding techniques, developers can use ASP.NET to create forms and collect data for hierarchical entity relationships. In many applications, this can make the difference between leveraging the rapid development capabilities of ASP.NET MVC and strapping on the additional infrastructure and complexity of a client-side framework like Angular or React.

This guide will present an example of using ASP.NET MVC model binding to present and collect hierarchical form data in a hierarchical structure.

Skill Levels

It will be helpful to have an understanding of these topics at the indicated skill level:

Technology Skill Level
ASP.NET Intermediate
C# Intermediate
Entity Framework Beginner
MVC Intermediate
MVVM Beginner

Scope

This guide will present an example of using ASP.NET MVC model binding to present and collect hierarchical form data in a hierarchical structure.

The example project and the code presented in this guide are based on the .NET Framework. Implementation details for .NET Core and .NET Standard will be covered in a different guide.

Structure

  1. We'll begin with an overview of the case study entities and the principal views of the example solution used to prepare this guide.

  2. Then, we'll see how to create a view model incorporating member fields of various primitive types and incorporating a field that is a collection of an object type.

  3. Next, we'll look at the code required to present information to the end user.

  4. We'll conclude with a close look at how to ensure that Razor code creates the correct HTML and we'll take a look how to use HtmlHelpers and CSS to apply formatting to the form fields created by the view.

Case Study

The code and screenshots shown in this guide match the code and view layouts an example Visual Studio project. The solution can be forked or downloaded from a GitHub repository:

The sample solution implements the following:

  • A multi-project solution with separate projects for

    • Data layers (context and repositories)
  • The Model-View ViewModel (MVVM) design pattern
  • The repository design pattern
  • The Entity Framework ORM with code-first development

Using the example solution you can follow along with each section below and experiment on your own.

Prerequisites

  • A working knowledge of ASP.NET MVC
  • An understanding of the Model View ViewModel (MVVM) design pattern
  • Visual Studio ready to go

BlipBinding Case Study Solution

The case study implemented by the BlipBinding solution is a simple application for maintaining information about customers, their orders, and the items in their orders. The permanent data store for the application is a SQL Server database. The tables and their relationships are shown below:

BlipBinding entity relationship diagram

BlipBinding database entity-relationship diagram

Entities and Relationships

Note that the many-to-many relationship between Orders and Items is implemented through the use of a merge table with payload: in addition to maintaining the relationship between Orders and Items, the OrderItems table also contains information about the items included in an order, the price at which they were sold and the quantity which were sold.

Obviously, this isn't a complete order processing system; it's just meant to provide an example of hierarchical relationships in a familiar form.

The database is created and maintained using Entity Framework code-first design. Each table and it's relationship to other tables is defined by a class in the Blip.Entities project of the BlipBinding solution. Let's look at the Customer and Order entities:

Blip.Entities\Customers\Customer.cs

                                  1                  using                                                      System                  ;                                                      2                                    using                                                      System                  .                  Collections                  .                  Generic                  ;                                                      3                                    using                                                      System                  .                  ComponentModel                  .                  DataAnnotations                  ;                                                      4                                    using                                                      System                  .                  ComponentModel                  .                  DataAnnotations                  .                  Schema                  ;                                                      5                                    using                                                      Blip                  .                  Entities                  .                  Geographies                  ;                                                      6                                    using                                                      Blip                  .                  Entities                  .                  Orders                  ;                                                      7                  8                                    namespace                                                      Blip                  .                  Entities                  .                  Customers                                                      9                                    {                                                      10                                                      public                                                      class                                                      Customer                                                      11                                                      {                                                      12                                                      public                                                      Customer                  (                  )                                                      13                                                      {                                                      14                                      Orders                                    =                                                      new                                                      HashSet                  <                  Order                  >                  (                  )                  ;                                                      15                                                      }                                                      16                  17                                                      [                  Key                  ]                                                      18                                                      [                  Column                  (                  Order                                    =                                                      0                  )                  ]                                                      19                                                      [                  DatabaseGenerated                  (                  DatabaseGeneratedOption                  .                  None                  )                  ]                                                      20                                                      public                                                      Guid                                      CustomerID                                    {                                                      get                  ;                                                      set                  ;                                                      }                                                      21                  22                                                      [                  Required                  ]                                                      23                                                      [                  MaxLength                  (                  128                  )                  ]                                                      24                                                      public                                                      string                                      CustomerName                                    {                                                      get                  ;                                                      set                  ;                                                      }                                                      25                  26                                                      [                  Required                  ]                                                      27                                                      [                  MaxLength                  (                  3                  )                  ]                                                      28                                                      public                                                      string                                      CountryIso3                                    {                                                      get                  ;                                                      set                  ;                                                      }                                                      29                  30                                                      [                  MaxLength                  (                  3                  )                  ]                                                      31                                                      public                                                      string                                      RegionCode                                    {                                                      get                  ;                                                      set                  ;                                                      }                                                      32                  33                                                      public                                                      virtual                                                      Country                                      Country                                    {                                                      get                  ;                                                      set                  ;                                                      }                                                      34                  35                                                      public                                                      virtual                                                      Region                                      Region                                    {                                                      get                  ;                                                      set                  ;                                                      }                                                      36                  37                                                      public                                                      virtual                                                      ICollection                  <                  Order                  >                                      Orders                                    {                                                      get                  ;                                                      set                  ;                                                      }                                                      38                                                      }                                                      39                                    }                              

csharp

Note the following characteristics:

  • ComponentModel DataAnnotations are used to identify the key field for the database and to specify the size and other options.

  • The one-to-many relationship between Customers and Orders is created by the virtual member field comprised of a collection of Order entities.

  • The required relationship between Customers and Countries in the database is reflected in the field to hold the value CountryIso3 and the field to identify the relationship, Country , which is of type Country .

Order.cs

The Order entity is defined in a similar way:

                                  1                  using                                                      System                  ;                                                      2                                    using                                                      System                  .                  Collections                  .                  Generic                  ;                                                      3                                    using                                                      System                  .                  ComponentModel                  .                  DataAnnotations                  ;                                                      4                                    using                                                      System                  .                  ComponentModel                  .                  DataAnnotations                  .                  Schema                  ;                                                      5                                    using                                                      Blip                  .                  Entities                  .                  Customers                  ;                                                      6                                    using                                                      Blip                  .                  Entities                  .                  Items                  ;                                                      7                  8                                    namespace                                                      Blip                  .                  Entities                  .                  Orders                                                      9                                    {                                                      10                                                      public                                                      class                                                      Order                                                      11                                                      {                                                      12                                                      public                                                      Order                  (                  )                                                      13                                                      {                                                      14                                      Items                                    =                                                      new                                                      HashSet                  <                  Item                  >                  (                  )                  ;                                                      15                                                      }                                                      16                  17                                                      [                  Key                  ]                                                      18                                                      [                  DatabaseGenerated                  (                  DatabaseGeneratedOption                  .                  None                  )                  ]                                                      19                                                      public                                                      Guid                                      OrderID                                    {                                                      get                  ;                                                      set                  ;                                                      }                                                      20                  21                                                      [                  Required                  ]                                                      22                                                      public                                                      Guid                                      CustomerID                                    {                                                      get                  ;                                                      set                  ;                                                      }                                                      23                  24                                                      [                  Required                  ]                                                      25                                                      public                                                      DateTime                                      OrderDate                                    {                                                      get                  ;                                                      set                  ;                                                      }                                                      26                  27                                                      [                  Required                  ]                                                      28                                                      [                  MaxLength                  (                  128                  )                  ]                                                      29                                                      public                                                      string                                      Description                                    {                                                      get                  ;                                                      set                  ;                                                      }                                                      30                  31                                                      public                                                      virtual                                                      ICollection                  <                  Item                  >                                      Items                                    {                                                      get                  ;                                                      set                  ;                                                      }                                                      32                  33                                                      public                                                      virtual                                                      Customer                                      Customer                                    {                                                      get                  ;                                                      set                  ;                                                      }                                                      34                                                      }                                                      35                                    }                              

csharp

  • The Order can only belong to one Customer , as reflected in the field for a single CustomerID and the navigation property Customer .

  • The one-to-many relationship between Orders and Items is implemented through the virtual property for the collection of Item entities.

The data context and Entity Framework code-first migrations for the database are located in the Blip.Data project, along with the repository methods.

Presenting Data

The Blip.Web project in the BlipBinding solution is based on the standard .NET Framework MVC template, so the layout of the views is based on the default Bootstrap CSS styling and the _layout.cshtml included with the template.

For the purposes of this guide, there are two notable views in the case study, one to display a list of customers and another to display a list of orders for each customer.

Customer/Index View

The view for the list of customers is a simple table displaying some basic information about the customer and an Html.ActionLink helper method to navigate to the list of orders for the customer:

BlipBinding Customer/Index view

BlipBinding solution, Blip.Web project, Customer/Index view

Using the Seed method of Entity Framework code-first migrations, we have populated the database with a few customers, orders, and items. If you run the example solution the application will create the database and add the same records (the GUID's created by your computer for the key fields will be different than those shown).

Blip.Web\Views\Customer\Index.cshtml

In the code for the view above, note that the list of customers is created with a foreach loop that iterates through the collection of CustomerDisplayViewModel entities. This is a standard way of presenting a list with a variable number of records:

                                  1                  @                  foreach                  (                  var                                      item                                    in                                      Model                  )                                                      2                                    {                                                      3                                                      <                  tr                  >                                                      4                                                      <                  td                  >                                                      5                                      @Html                  .                  DisplayFor                  (                  modelItem                                    =>                                      item                  .                  CustomerID                  )                                                      6                                                      <                  /                  td                  >                                                      7                                                      <                  td                  >                                                      8                                      @Html                  .                  DisplayFor                  (                  modelItem                                    =>                                      item                  .                  CustomerName                  )                                                      9                                                      <                  /                  td                  >                                                      10                                                      <                  td                  >                                                      11                                      @Html                  .                  DisplayFor                  (                  modelItem                                    =>                                      item                  .                  CountryName                  )                                                      12                                                      <                  /                  td                  >                                                      13                                                      <                  td                  >                                                      14                                      @Html                  .                  DisplayFor                  (                  modelItem                                    =>                                      item                  .                  RegionName                  )                                                      15                                                      <                  /                  td                  >                                                      16                                                      <                  td                  >                                                      17                                      @Html                  .                  ActionLink                  (                  "Orders"                  ,                                                      "Index"                  ,                                                      "Order"                  ,                                                      new                                                      {                                      customerid                                    =                                      item                  .                  CustomerID                                    }                  ,                                                      null                  )                                                      18                                                      <                  /                  td                  >                                                      19                                                      <                  /                  tr                  >                                                      20                                    }                              

csharp

In the list of customers, the view model has a flat structure, it's just an enumerable list of objects that contain the customer information. Here's the @model directive from the beginning of Index.cshtml :

                                  1                  @model IEnumerable                  <                  Blip                  .                  Entities                  .                  Customers                  .                  ViewModels                  .                  CustomerDisplayViewModel                  >                              

csharp

Now let's take a closer look at that view model.

Blip.Entities\Customers.ViewModels\CustomerDisplayViewModel.cs

                                  1                  using                                                      System                  ;                                                      2                                    using                                                      System                  .                  ComponentModel                  .                  DataAnnotations                  ;                                                      3                  4                                    namespace                                                      Blip                  .                  Entities                  .                  Customers                  .                  ViewModels                                                      5                                    {                                                      6                                                      public                                                      class                                                      CustomerDisplayViewModel                                                      7                                                      {                                                      8                                                      [                  Display                  (                  Name                                    =                                                      "Customer Number"                  )                  ]                                                      9                                                      public                                                      Guid                                      CustomerID                                    {                                                      get                  ;                                                      set                  ;                                                      }                                                      10                  11                                                      [                  Display                  (                  Name                                    =                                                      "Customer Name"                  )                  ]                                                      12                                                      public                                                      string                                      CustomerName                                    {                                                      get                  ;                                                      set                  ;                                                      }                                                      13                  14                                                      [                  Display                  (                  Name                                    =                                                      "Country"                  )                  ]                                                      15                                                      public                                                      string                                      CountryName                                    {                                                      get                  ;                                                      set                  ;                                                      }                                                      16                  17                                                      [                  Display                  (                  Name                                    =                                                      "State / Province / Region"                  )                  ]                                                      18                                                      public                                                      string                                      RegionName                                    {                                                      get                  ;                                                      set                  ;                                                      }                                                      19                                                      }                                                      20                                    }                              

csharp

Note that we're using data annotations in the view model to provide the field labels to display on the view.

When the repository method populates this model it combines data from the Customers table with CountryNameEnglish from the Country table and RegionName from the Region table. In this way, the view model can present information that is more helpful to the user than the index values for country and region from the Customers table.

Order/Index View

The simple list of orders for a customer shows the customer information and the order number and date as read-only fields, and the purchase order/description as an editable field. By changing values in the editable field and saving we can see how model binding works when doing HttpPost actions.

BlipBinding Order/Index view

BlipBinding solution, Blip.Web project, Order/Index view

In this view, we're presenting information in a hierarchical structure. At the top level is the customer information. Underneath that is the list of orders for the customer.

Let's see how the data is presented in code.

Blip.Web\Views\Order\Index.cshtml

For the top-tier data, pertaining to the customer, the fields are composed in a very standard way:

                                  1                                                      <                  div                                    class                  =                  "form-group"                  >                                                      2                                      @Html                  .                  LabelFor                  (                  model                                    =>                                      model                  .                  CustomerName                  ,                                                      new                                                      {                                      @                  class                                                      =                                                      "control-label col-md-2"                                                      }                  )                                                      3                                                      <                  div                                    class                  =                  "col-md-10"                  >                                                      4                                      @Html                  .                  EditorFor                  (                  model                                    =>                                      model                  .                  CustomerName                  ,                                                      new                                                      {                                      htmlAttributes                                    =                                                      new                                                      {                                      @                  class                                                      =                                                      "form-control"                  ,                                      @                  readonly                                                      =                                                      "readonly"                                                      }                                                      }                  )                                                      5                                                      <                  /                  div                  >                                                      6                                                      <                  /                  div                  >                              

csharp

Note that we're using the EditorFor HtmlHelper to let the Razor engine determine the correct type of HTML element for the data type. We're also applying the form-control CSS class to be sure the control picks up the appropriate styling. The field is changed from an editable textbox to a display-only field with the application of the @readonly HTML attribute.

For the second tier data, the list of orders, we're looping through the records in the view model. But in this case we're not using a foreach loop and we're not using the EditorFor HtmlHelper. We'll look at the reasons for these choices in more detail in the section on saving data.

                                  1                  @                  if                                                      (                  Model                  .                  Orders                                    !=                                                      null                  )                                                      2                                    {                                                      3                                                      for                                                      (                  var                                      i                                    =                                                      0                  ;                                      i                                    <                                      Model                  .                  Orders                  .                  Count                  (                  )                  ;                                      i                  ++                  )                                                      4                                                      {                                                      5                                                      <                  tr                  >                                                      6                                      @Html                  .                  HiddenFor                  (                  x                                    =>                                      Model                  .                  Orders                  [                  i                  ]                  .                  CustomerID                  )                                                      7                                                      <                  td                  >                                                      8                                      @Html                  .                  TextBoxFor                  (                  x                                    =>                                      Model                  .                  Orders                  [                  i                  ]                  .                  OrderID                  ,                                                      new                                                      {                                      @                  class                                                      =                                                      "form-control"                  ,                                      @                  readonly                                                      =                                                      "readonly"                                                      }                  )                                                      9                                                      <                  /                  td                  >                                                      10                                                      <                  td                  >                                                      11                                      @Html                  .                  TextBoxFor                  (                  x                                    =>                                      Model                  .                  Orders                  [                  i                  ]                  .                  OrderDate                  ,                                                      new                                                      {                                      @                  class                                                      =                                                      "form-control"                  ,                                      @                  readonly                                                      =                                                      "readonly"                                                      }                  )                                                      12                                                      <                  /                  td                  >                                                      13                                                      <                  td                  >                                                      14                                      @Html                  .                  TextBoxFor                  (                  x                                    =>                                      Model                  .                  Orders                  [                  i                  ]                  .                  Description                  ,                                                      new                                                      {                                      @                  class                                                      =                                                      "form-control"                                                      }                  )                                                      15                                                      <                  /                  td                  >                                                      16                                                      <                  /                  tr                  >                                                      17                                                      }                                                      18                                    }                              

csharp

You'll also note that we're using a for loop with a counting variable rather than a foreach loop. This is crucial to getting binding to work for the 'HttpPost' action, as we'll see soon.

Blip.Entities\Orders.ViewModels\CustomerOrdersDisplayViewModel.cs

The view model for customer orders reflects the hierarchical structure of the view shown above. It assembles the display information about the customer from the Customers, Countries, and Regions tables and includes a property that is a collection of OrderDisplayViewModel entities.

                                  1                  using                                                      System                  ;                                                      2                                    using                                                      System                  .                  Collections                  .                  Generic                  ;                                                      3                                    using                                                      System                  .                  ComponentModel                  .                  DataAnnotations                  ;                                                      4                  5                                    namespace                                                      Blip                  .                  Entities                  .                  Orders                  .                  ViewModels                                                      6                                    {                                                      7                                                      public                                                      class                                                      CustomerOrdersListViewModel                                                      8                                                      {                                                      9                                                      [                  Display                  (                  Name                                    =                                                      "Customer Number"                  )                  ]                                                      10                                                      public                                                      Guid                                      CustomerID                                    {                                                      get                  ;                                                      set                  ;                                                      }                                                      11                  12                                                      [                  Display                  (                  Name                                    =                                                      "Customer Name"                  )                  ]                                                      13                                                      public                                                      string                                      CustomerName                                    {                                                      get                  ;                                                      set                  ;                                                      }                                                      14                  15                                                      [                  Display                  (                  Name                                    =                                                      "Country"                  )                  ]                                                      16                                                      public                                                      string                                      CountryNameEnglish                                    {                                                      get                  ;                                                      set                  ;                                                      }                                                      17                  18                                                      [                  Display                  (                  Name                                    =                                                      "Region"                  )                  ]                                                      19                                                      public                                                      string                                      RegionNameEnglish                                    {                                                      get                  ;                                                      set                  ;                                                      }                                                      20                  21                                                      public                                                      List                  <                  OrderDisplayViewModel                  >                                      Orders                                    {                                                      get                  ;                                                      set                  ;                                                      }                                                      22                                                      }                                                      23                                    }                              

csharp

Let's take a look at the class that composes the Orders collection.

Blip.Entities\Orders.ViewModels\OrderDisplayViewModel.cs

Note that each entity in OrderDisplayViewModel is linked to the associated customer in CustomerOrdersListViewModel . When we transpose the entities into the view model structure we need to preserve the relationship between the entities (and the tables in the database).

                                  1                  using                                                      System                  ;                                                      2                                    using                                                      System                  .                  ComponentModel                  .                  DataAnnotations                  ;                                                      3                  4                                    namespace                                                      Blip                  .                  Entities                  .                  Orders                  .                  ViewModels                                                      5                                    {                                                      6                                                      public                                                      class                                                      OrderDisplayViewModel                                                      7                                                      {                                                      8                                                      public                                                      Guid                                      CustomerID                                    {                                                      get                  ;                                                      set                  ;                                                      }                                                      9                  10                                                      [                  Display                  (                  Name                                    =                                                      "Order Number"                  )                  ]                                                      11                                                      public                                                      Guid                                      OrderID                                    {                                                      get                  ;                                                      set                  ;                                                      }                                                      12                  13                                                      [                  Display                  (                  Name                                    =                                                      "Order Date"                  )                  ]                                                      14                                                      public                                                      DateTime                                      OrderDate                                    {                                                      get                  ;                                                      set                  ;                                                      }                                                      15                  16                                                      [                  Display                  (                  Name                                    =                                                      "PO / Description"                  )                  ]                                                      17                                                      public                                                      string                                      Description                                    {                                                      get                  ;                                                      set                  ;                                                      }                                                      18                                                      }                                                      19                                    }                              

csharp

Note also that there are no virtual properties in either of these classes to provide navigation between entities. The view models serve the functional purpose of the view and are uncoupled from the entity relationships of the classes and the database tables. Accordingly, when two view models are used together they reflect the relationship(s) between the view models, rather than the entities from which their data is drawn.

The repository methods take care of transposing the data from the structure of the entities to the structure of the view models and back again.

OrdersController Action for Index HttpGet

By using MVVM and the repository design pattern, we can make our controller actions succinct and provide separation of concerns between the presentation layer, business logic, and data. We can see that in action in the controller action that populates the Order/Index view.

Blip.Web\Controllers\OrderController.cs

                                  1                  using                                                      System                  ;                                                      2                                    using                                                      System                  .                  Net                  ;                                                      3                                    using                                                      System                  .                  Web                  .                  Mvc                  ;                                                      4                                    using                                                      Blip                  .                  Data                  .                  Orders                  ;                                                      5                                    using                                                      Blip                  .                  Entities                  .                  Orders                  .                  ViewModels                  ;                                                      6                  7                                    namespace                                                      BlipProjects                  .                  Controllers                                                      8                                    {                                                      9                                                      public                                                      class                                                      OrderController                                                      :                                                      Controller                                                      10                                                      {                                                      11                                                      // GET: Order                                                      12                                                      public                                                      ActionResult                                                      Index                  (                  string                                      customerid                  )                                                      13                                                      {                                                      14                                                      if                  (                  !                  String                  .                  IsNullOrWhiteSpace                  (                  customerid                  )                  )                                                      15                                                      {                                                      16                                                      if                                                      (                  Guid                  .                  TryParse                  (                  customerid                  ,                                                      out                                                      Guid                                      customerId                  )                  )                                                      17                                                      {                                                      18                                                      var                                      repo                                    =                                                      new                                                      OrdersRepository                  (                  )                  ;                                                      19                                                      var                                      model                                    =                                      repo                  .                  GetCustomerOrdersDisplay                  (                  customerId                  )                  ;                                                      20                                                      return                                                      View                  (                  model                  )                  ;                                                      21                                                      }                                                      22                                                      }                                                      23                                                      return                                                      new                                                      HttpStatusCodeResult                  (                  HttpStatusCode                  .                  BadRequest                  )                  ;                                                      24                                                      }                                                      25                                    ..                  .                              

csharp

All that this controller action needs to do when passed a CustomerID from the Customer/Index view is pass that value to the appropriate repository method and take the resultant data model, an instance of the CustomerOrdersListViewModel class, and pass it to the view.

Saving Data

Saving data using default binding can be a tricky process -- the correct approach is not well-documented. This is also a situation where the code fails silently. Developers will see their web pages being populated with data correctly, but the values won't show up in the model when it arrives at the controller action for HttpPost.

To better understand this, we're first going to take a look at the problem, then show how to code the functionality correctly.

What Not to Do

In the Razor code for the list of customers above, we saw that we could populate the list using a foreach loop and the DisplayFor HtmlHelper method. If we used a foreach loop for the list of orders, the <table> element would look like this:

                                  1                  <                  table                                                      class                  =                  "                  table                  "                  >                                                      2                                                      <                  tr                  >                                                      3                                                      <                  th                  >                                                      4                  @Html.DisplayNameFor(model => model.Orders[0].OrderID)                  5                                                      </                  th                  >                                                      6                                                      <                  th                  >                                                      7                  @Html.DisplayNameFor(model => model.Orders[0].OrderDate)                  8                                                      </                  th                  >                                                      9                                                      <                  th                  >                                                      10                  @Html.DisplayNameFor(model => model.Orders[0].Description)                  11                                                      </                  th                  >                                                      12                                                      </                  tr                  >                                                      13                  @if (Model.Orders != null)                  14                  {                  15                  foreach (var order in Model.Orders)                  16                  {                  17                                                      <                  tr                  >                                                      18                  @Html.HiddenFor(x => order.CustomerID)                  19                                                      <                  td                  >                                                      20                  @Html.DisplayFor(x => order.OrderID)                  21                                                      </                  td                  >                                                      22                                                      <                  td                  >                                                      23                  @Html.DisplayFor(x => order.OrderDate)                  24                                                      </                  td                  >                                                      25                                                      <                  td                  >                                                      26                  @Html.EditorFor(x => order.Description)                  27                                                      </                  td                  >                                                      28                                                      </                  tr                  >                                                      29                  }                  30                  }                  31                                    </                  table                  >                              

html

That's nice and concise, and seems to leverage the power of Razor HtmlHelper extension methods to "automagically" generate HTML. The problem is; it doesn't work .

The HTML produced by the preceding code would look like this:

Ambiguous element ID's

Form Elements with Ambiguous Element Names and ID's

Look at the areas highlighted in yellow. Each row is a separate textbox on the form shown above for the Order/Index view. Each record has the same value for the name and id elements: order.Description . Without a way to identify the records distinctly, MVC gives up and returns null for the Orders field of the CustomerOrdersListViewModel to which the Order/Index view is bound.

Correctly Binding Collection Data

In the Razor code for the list of orders for a specific customer, we used a for loop with a local variable index value i . The loop looks like this:

                                  1                  @                  if                                                      (                  Model                  .                  Orders                                    !=                                                      null                  )                                                      2                                    {                                                      3                                                      for                                                      (                  var                                      i                                    =                                                      0                  ;                                      i                                    <                                      Model                  .                  Orders                  .                  Count                  (                  )                  ;                                      i                  ++                  )                                                      4                                                      {                                                      5                                                      <                  tr                  >                                                      6                                      @Html                  .                  HiddenFor                  (                  x                                    =>                                      Model                  .                  Orders                  [                  i                  ]                  .                  CustomerID                  )                                                      7                                                      <                  td                  >                                                      8                                      @Html                  .                  TextBoxFor                  (                  x                                    =>                                      Model                  .                  Orders                  [                  i                  ]                  .                  OrderID                  ,                                                      new                                                      {                                      @                  class                                                      =                                                      "form-control"                  ,                                      @                  readonly                                                      =                                                      "readonly"                                                      }                  )                                                      9                                                      <                  /                  td                  >                                                      10                                                      <                  td                  >                                                      11                                      @Html                  .                  TextBoxFor                  (                  x                                    =>                                      Model                  .                  Orders                  [                  i                  ]                  .                  OrderDate                  ,                                                      new                                                      {                                      @                  class                                                      =                                                      "form-control"                  ,                                      @                  readonly                                                      =                                                      "readonly"                                                      }                  )                                                      12                                                      <                  /                  td                  >                                                      13                                                      <                  td                  >                                                      14                                      @Html                  .                  TextBoxFor                  (                  x                                    =>                                      Model                  .                  Orders                  [                  i                  ]                  .                  Description                  ,                                                      new                                                      {                                      @                  class                                                      =                                                      "form-control"                                                      }                  )                                                      15                                                      <                  /                  td                  >                                                      16                                                      <                  /                  tr                  >                                                      17                                                      }                                                      18                                    }                              

csharp

Note the following particulars:

  1. The index value i appears in the lambda expression for each form element being generated by the loop, for example:

    (x => Model.Orders[i].OrderID)

  2. The TextBoxFor HtmlHelper is used, rather than the more general (and automagic) DisplayFor and EditorFor .

  3. The OrderID and OrderDate fields are set as readonly using the HTML class attribute, rather than using DisplayFor .

  4. Bootstrap textbox styling is applied by adding the @class = "form-control" attribute.

The generated HTML looks like this:

description

Form Elements with a Distinct Name and ID Attributes

As the areas highlighted in yellow show, each field has a name and id attribute that is distinct for each record. The record index gives MVC something to use to bind the form data to the data model.

By setting a breakpoint in the HttpPost controller action for the Order/Index view we can see that the new data we entered, "expedite" in the Description field of record 0, is being posted back to the server along with the values for the readonly fields.

Visual Studio property inspector

Visual Studio Debugging Showing Property Inspector Values for an OrderDisplayViewModel Entity

Posting Display Data

As we noted above, we're using the TextBoxFor HtmlHelper for read-only fields, rather than the DisplayFor helper. This is so we can return the data to the controller when the HttpPost event fires (when the Save button on the page is pressed).

When MVC generates the HTML for a DisplayFor HTML helper, it renders the element as simple text.

For example, a table cell coded like this:

                                  1                  <                  td                  >                                                      2                  @Html.DisplayFor(modelItem => item.OrderID)                  3                                    </                  td                  >                              

html

                                  1                  <                  td                  >                                                      2                  490dabe1-1570-473a-8331-5f32333b2635                  3                                    </                  td                  >                              

html

That's just straight text, so there's no way for MVC to bind it to the model.

But a table cell for a display-only text field coded like this:

                                  1                  <                  td                  >                                                      2                  @Html.TextBoxFor(x => Model.Orders[i].OrderID, new { @class = "form-control", @readonly = "readonly" })                  3                                    </                  td                  >                              

html

                                  1                  <                  td                  >                                                      2                                                      <                  input                                                      name                  =                  "                  Orders[0].OrderID                  "                                                      class                  =                  "                  form-control                  "                                                      id                  =                  "                  Orders_0__OrderID                  "                                                      type                  =                  "                  text                  "                                                      readonly                  =                  "                  readonly                  "                                                      value                  =                  "                  490dabe1-1570-473a-8331-5f32333b2635                  "                                                      data-val-required                  =                  "                  The Order Number field is required.                  "                                                      data-val                  =                  "                  true                  "                  >                                                      3                                    </                  td                  >                              

html

As an <input> field, this element will be passed back to the controller during the POST . Because it has a distinct id , the data in this readonly field can be bound to the view model just like data from an editable field. The values for CustomerID and OrderID give us the index values necessary to save the changes to the editable field, Description .

Note, also, that while we're displaying OrderDate as a readonly text field, and thereby returning it during the POST event, we don't have to. Only the index values necessary to save the changed data need to be returned in the POST event.

In our example, the OrderID field is displayed, but the CustomerID field is included in the form using the HiddenFor HtmlHelper. As coded, it looks like this:

                                  1                  @Html.HiddenFor(x => Model.Orders[i].CustomerID)                                    2``                  3                  4And the HTML rendered by it looks like this:                  5                  6```html                  7                                    <                  input                                                      name                  =                  "                  Orders[0].CustomerID                  "                                                      id                  =                  "                  Orders_0__CustomerID                  "                                                      type                  =                  "                  hidden                  "                                                      value                  =                  "                  f8214550-69f6-4089-b58a-2de2d4ab01c8                  "                                                      data-val-required                  =                  "                  The CustomerID field is required.                  "                                                      data-val                  =                  "                  true                  "                  >                              

html

Aside from being hidden on the HTML served to the client, this is a full-featured data element that is bound to the view model received by the HttpPost controller action for the Index view. Using HiddenFor is a convenient way of keeping form layout simple while including all the data necessary for identifying the records to be updated during a POST .

The astute reader may have realized that in our case study the CustomerID field isn't necessary to save changes to an Order object. Because OrderID is a GUID it inherently provides a unique identifier for an individual order.

HttpPost Controller Actions

By using the repository design pattern we can keep our controller actions simple. In the case of the HttpPost action for the Order/Index view, all we need to do is validate CustomerOrdersListViewModel and pass it to the appropriate repository method.

Blip.Web\Controllers\OrdersController.cs

                                  1                  ..                  .                                                      2                                    [                  HttpPost                  ]                                                      3                                    [                  ValidateAntiForgeryToken                  ]                                                      4                                    public                                                      ActionResult                                                      Index                  (                  CustomerOrdersListViewModel                                      model                  )                                                      5                                    {                                                      6                                                      if                                                      (                  ModelState                  .                  IsValid                  )                                                      7                                                      {                                                      8                                                      if                                                      (                  model                  .                  Orders                                    !=                                                      null                  )                                                      9                                                      {                                                      10                                                      var                                      repo                                    =                                                      new                                                      OrdersRepository                  (                  )                  ;                                                      11                                      repo                  .                  SaveOrders                  (                  model                  .                  Orders                  )                  ;                                                      12                                                      }                                                      13                                                      return                                                      View                  (                  model                  )                  ;                                                      14                                                      }                                                      15                                                      return                                                      new                                                      HttpStatusCodeResult                  (                  HttpStatusCode                  .                  BadRequest                  )                  ;                                                      16                                    }                                                      17                                    ..                  .                              

csharp

Save Repository Method

The repository method associated with our order list view can also be simple. All we need to do is find the appropriate records in the Orders table and update them.

Blip.Data\Orders\OrdersRepository.cs

                                  1                  ..                  .                                                      2                                    public                                                      void                                                      SaveOrders                  (                  List                  <                  OrderDisplayViewModel                  >                                      orders                  )                                                      3                                    {                                                      4                                                      if                                                      (                  orders                                    !=                                                      null                  )                                                      5                                                      {                                                      6                                                      using                                                      (                  var                                      context                                    =                                                      new                                                      ApplicationDbContext                  (                  )                  )                                                      7                                                      {                                                      8                                                      foreach                                                      (                  var                                      order                                    in                                      orders                  )                                                      9                                                      {                                                      10                                                      var                                      record                                    =                                      context                  .                  Orders                  .                  Find                  (                  order                  .                  OrderID                  )                  ;                                                      11                                                      if                                                      (                  record                                    !=                                                      null                  )                                                      12                                                      {                                                      13                                      record                  .                  Description                                    =                                      order                  .                  Description                  ;                                                      14                                                      }                                                      15                                                      }                                                      16                                      context                  .                  SaveChanges                  (                  )                  ;                                                      17                                                      }                                                      18                                                      }                                                      19                                    }                                                      20                                    ..                  .                              

csharp

MoreIinformation

If you want to dive deeper into the topics discussed in this guide, the following is a curated list of resources.

Related PluralSight Training Classes

PluralSight offers a number of courses on the topics mentioned in this guide. The following are some suggestions organized by technology:

Case Study Code on GitHub

The complete Visual Studio solutions described in this guide are available on GitHub:

You can fork the projects, run the code, and experiment on your own.

Note that the sample project is not intended to be a real-life case study or production code; it exists to illustrate the topics covered in this guide.

Other Resources

Disclaimer: Pluralsight and the author of this Guide are not responsible for the content, accuracy, or availability of 3rd party resources.

How to Design a Form in Asp.net Using Table

Source: https://www.pluralsight.com/guides/asp.net-mvc-getting-default-data-binding-right-for-hierarchical-views

0 Response to "How to Design a Form in Asp.net Using Table"

Enregistrer un commentaire

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel