Monday, May 11, 2015

Editing Multiple Records Using Model Binding in MVC

Introduction
You saw that in ASP.NET MVC we can easily edit a single record handled by the default model binding. It may be possible that you need to edit multiple records. We can show an editable grid of data to the user and the user can easily edit multiple records. You can also save the edited data values by hitting a button.
In that context, multiple model objects are being submitted to the action method. The single record editing works on the assumption that form field names from the view match the corresponding model property names. However when multiple model objects are submitted this assumption is no longer true. So, in this article we'll create the MVC application in Visual Studio 2013.
So, let's start with the following sections:
  • Creating MVC application
  • Adding Model
  • Adding Controller
  • Adding View
  • Working the Controller and View
Creating MVC Application
In this section we'll create the MVC application using Visual Studio 2013 using the following procedure:
Step 1: Open Visual Studio 2013 and click on "New Project".
Step 2: Select the "ASP.NET Web Application" and enter the name as in the following:
Creating ASP.Net Web Application
Step 3: Select the "MVC" project template as in the following:
Mvc Project Template in VS 2013
Visual Studio automatically creates the MVC application using MVC 5 and adds files and folders to the project.
Adding Model
In this section we'll add a model for the application. We'll add the existing database to the model using an ADO.NET Entity Data Model using the following procedure.
Step 1: Add the ADO.NET Entity Data Model to the Model folder.
Step 2: Select the database and table as in the following:
Entity Data Model Wizard
After adding the data model you can see that the Employee entity is added to the application.
Employee Entity Diagram
Adding Controller
In this section, we'll add the controller for the model and view. Use the following procedure.
Step 1: Add a new Controller by right-clicking on the Controllers folder in the Solution Explorer.
Step 2: Select the "MVC 5 Controller - Empty" to generate the controller.
Creating Mvc Empty Controller
Step 3: Add the following code to the Index() method:
using ModelBindingApp.Models;
public ActionResult Index()
{
    CompanyEntities DbCompany = new CompanyEntities();
    var data = from item in DbCompany.Employees
                where item.Company == "TCS"
                orderby item.ID
                select item;
    return View(data.ToList());
}
In the code above, this method selects employees from the TCS Company and passes them to the View as a list.
Adding View
In this section we'll add the view for the method in the controller. Use the following procedure.
Step 1: Right-click on the Index() to add the view as in the following:
Adding View in Mvc
Step 2: Replace the code with the code below:
@model List<ModelBindingApp.Models.Employee>
@{
    ViewBag.Title = "Index";
}

<h2>Employee List</h2>

@using (Html.BeginForm("Index""Sample"FormMethod.Post))
{
    <table class="table">
        @for (int i = 0; i < Model.Count; i++)
        {
            <tr>
                <td>
                    @Html.TextBox("employees[" + @i + "].ID",
                    Model[i].ID, 
new {@readonly="readonly"})
                </td>
                <td>
                    @Html.TextBox("employees[" + @i + "].Name"
                    Model[i].Name, new { @readonly = "readonly" })
                </td>
                <td>
                    @Html.TextBox("employees[" + @i + "].Gender"
                    Model[i].Gender, new { @readonly = "readonly" })
                </td>
                <td>
                    @Html.TextBox("employees[" + @i + "].Company"
                    Model[i].Company, new { @readonly = "readonly" })
                </td>
            </tr>
        }

        <tr>
            <td colspan="4">
                <input type="submit" value="Submit" />
            </td>
        </tr>
    </table>
}
In the code above, all the employee records will display having the company name TCS. You cannot edit the TextBox due to readonly. So, all the textboxes having the same index are considered as "one record".
Step 3: Press Ctrl+F5 to run the application.
Dispalyining Veiw of Controller in Mvc
Working the Controller and View
The data is submitted by the Index(). Now to handle the data we need to write the Index() method with a parameter. Use the following procedure.
Step 1: Add another Index() method with the following code:
[HttpPost]
public ActionResult Index(List<Employee> employees)
{
    CompanyEntities DbCompany = new CompanyEntities();

    foreach (Employee Emp in employees)
    {
        Employee Existed_Emp = DbCompany.Employees.Find(Emp.ID);
        Existed_Emp.Name = Emp.Name;
        Existed_Emp.Gender = Emp.Gender;
        Existed_Emp.Company = Emp.Company;
    }

    DbCompany.SaveChanges();
    return View();
}
In the code above, the Index() method takes the parameter named employees. Due to the naming conventions followed by the default binding in the MVC transforms the form fields value into a generic list of Employee objects. Once the changes have been made SaveChanges() is called to save the changes.
Step 2: We need to edit the markup code with the following code:
@using (Html.BeginForm("Index""Sample"FormMethod.Post))
{
    <table class="table">
        @for (int i = 0; i < Model.Count; i++)
        {
            <tr>
                <td>
                    @Html.Hidden("employees.Index", (@i+10))
                    @Html.TextBox("employees[" + (@i + 10) + "].ID"
                    Model[i].ID, new {@readonly="readonly"})
                </td>
                <td>
                    @Html.TextBox("employees[" + (@i + 10) + "].Name"
                    Model[i].Name)
                </td>
                <td>
                    @Html.TextBox("employees[" + (@i + 10) + "].Gender",
                    Model[i].Gender)                
                </td>
                <td>
                    @Html.TextBox("employees[" + (@i + 10) + "].Company",
                    Model[i].Company)                
                </td>
            </tr>
        }

        <tr>
            <td colspan="4">
                <input type="submit" value="Submit" />
            </td>
        </tr>
    </table>
}
In the code above, each table row has the hidden field named Index and the value is set to some arbitrary value. All textboxes have the same index that is specified by the hidden field and considered as "one record".
Step 3: Now again run the application and edit multiple records.
Editing Multiple Objects
Step 4: You can see the records have been updated as in the following:
Generating Index View

ASP.NET MVC - Excluding Model Validation Rules

While building a simple MVC app, I came across the problem where I am using one model in several views. The model includes DataAnnotation attribute rules for validation. However, some rules don't apply to certain views. It seems this is a common problem, without a well established solution. After searching a bit, I found this post by Andrew West that nicely summarizes the problem and several solutions. His last suggestion is to remove the items from ModelState that you don't want to participate in the validation. I agreed that this was the simplest and least dependent solution and pursued it.

What I finally came up with was a simple extension method for the ModelStateDictionary that looks like this:

public static void CleanAllBut(
   this ModelStateDictionary modelState,
   params string[] includes)
   {
      modelState
        .Where(x => !includes
           .Any(i => String.Compare(i, x.Key, true) == 0))
        .ToList()
        .ForEach(k => modelState.Remove(k));
   }

The method removes everything in the model state dictionary that doesn't match one of the "includes" strings. Being a params argument to the method, the call to it becomes very clean and readable (unlike the extension method itself). So I added this call to my controller method:

ModelState.CleanAllBut("username", "password");

While we can use the existing ModelStateDictionary.Remove() method for when we only want to explicitly remove one item, for multi-key removal we could make a similar extension method that takes a list of keys to remove.