Friday, January 29, 2016

ASP.NET MVC: Data Binding a collection of items (parent with child rows)

Scenario

  • Client is the parent record
  • A client can have one or more contacts
  • Contact is the child record

Models

Parent Record

public class ClientModel
{
// Parent Record - Attributes
// Db-wise: Id is the primary key,
//          serves as foreign key for child records
public int Id { get; set; }  
public string ClientName { get; set; }

// Child Records
public List<ContactModel> Contacts { get; set; }

public ClientModel()
{
Contacts = new List<ContactModel>();
}
}

Child Record

public class ContactModel
{
// Each child record has a temporary unique id to facilitate binding
public ContactModel()
{
this.GUID = Guid.NewGuid().ToString();
}

// Helper to form control id.
//  e.g. [50e32e2b-a9af-45ec-8be5-97ffa219dae8].FirstName
public string ControlId(string controlName)
{
return string.Format("[{0}].{1}", this.GUID, controlName);
}

public string GUID { get; set; }

// Attributes of child record
// Db-wise: Foreign key is the Client Id
//          Primary key is auto generated id
public string FirstName { get; set; }
public string LastName { get; set; }
}

Views

Client.cshtml

@using MvcMultipleItemDataBinding.Models
@model ClientModel
@{
    ViewBag.Title = "Client";
}

<h2>Client</h2>

@using (Html.BeginForm())
{
    @Html.HiddenFor(m => m.Id)
    @Html.LabelFor(model => model.ClientName)
    @Html.TextBoxFor(model => model.ClientName)

    <h3>Contacts</h3>
    <table>
        <thead>
            <tr>
                <td>First Name</td>
                <td>Last Name</td>
            </tr>
        </thead>
        <tbody>
            @foreach (var contact in Model.Contacts)
            {
                @Html.Partial("_Contacts", contact);
            }
        </tbody>
    </table>
 
    <input type="submit" value="Submit" />
}

_Contacts.cshtml

@using MvcMultipleItemDataBinding.Models
@model ContactModel

<tr>
    <td>
        @*index of one of the items in the array of items*@
        @*index.value = 50e32e2b-a9af-45ec-8be5-97ffa219dae8*@
        @Html.Hidden(name: "index", value: @Model.GUID)
        @*e.g. [50e32e2b-a9af-45ec-8be5-97ffa219dae8].FirstName*@
        @Html.TextBox(@Model.ControlId("FirstName"), Model.FirstName)
    </td>
    <td>@Html.TextBox(@Model.ControlId("LastName"), Model.LastName)</td>
</tr>

Controller Actions

[HttpGet]

public ActionResult Client()
{
var model = new ClientModel();
// Display 2 blank rows
model.Contacts.Add(new ContactModel());
model.Contacts.Add(new ContactModel());
return View(model);
}

[HttpPost]

public ActionResult Client(ClientModel model)
{
TryUpdateModel(model.Contacts);
return View(model);
}