Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET MVC 3 How to have multi-field create capability for Model with ICollection Property on Create view

Note: I'm using MVC3+Razor, EF4, CF-CTP5

  1. How can you allow the view to have the ability to add multiple Address classes per Organization dynamically on the client, and bound strongly to the model on post?

  2. How can you have the view parse values in the model if the (ModelState.IsValid == false) such that if you enter 3 addresses and post an invalid model, it re-populates the number addresses and with their appropriate values?

Here are my models:

public class Organization
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual ICollection<Address> Addresses { get; set; }
    public virtual ICollection<PhoneNumber> PhoneNumbers { get; set; }
    ...
}

public class Address
{
    public int Id { get; set; }
    public string Line1 { get; set; }
    public string Line2 { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string PostalCode { get; set; }
    public string Country { get; set; }
    public int Type { get; set; }
}

I'm trying to figure out how you can have the Create action for Organization (/Organization/Create) handle the create like thus (such that addresses and phone numbers are part of the submitted model):

[HttpPost]
public ActionResult Create(Organization organization)
{
    if (ModelState.IsValid)
    {
        _db.Organizations.Add(organization);
        _db.SaveChanges();
        return RedirectToAction("Details", organization.Id);
    }

    return View(organization);

}
like image 889
Ken Perkins Avatar asked Nov 15 '22 04:11

Ken Perkins


1 Answers

Your question is quite vaste :) This is just one of the way your requirement can be achieved and I am sure there are better than mine.

I am going to start from your second question:

How can you have the view parse values in the model if the (ModelState.IsValid == false) such that if you enter 3 addresses and post an invalid model, it re-populates the number addresses and with their appropriate values?

If I correctly understand your request it looks very simple to me. The answer is simply code your view to render a Model class content and return the invalid model to the client exactly as you are doing in your Create action.

If your form (and its fields) have been decorated with the ValidationSummary/ValidationMessage html helpers, you are going to see also validation messages.

  1. How can you allow the view to have the ability to add multiple Address classes per Organization dynamically on the client, and bound strongly to the model on post?

You can have a main view showing Organization attributes and then have another view showing related addresses. Here you can place a hyperlink or a button that open a dialog for adding a new address object and then refresh the address list when done. At the same way you can have edit and delete buttons as icons on the list.

The address list is a piece of markup completely handled at client side that, to be correctly binded to the server side Model class should adhere to some simple naming rules for it's input attributes.

To make the Default Model Binder class bind correctly your form use the following snippet for your Organization class

@using (Html.BeginForm()) {
    @Html.HiddenFor(o => o.Id)
    @Html.ValidationSummary( true )
    <fieldset>
        <legend>My Organization</legend>
        <div class="editor-label">@Html.LabelFor( model => model.Name )</div>
        <div class="editor-field">
            @Html.EditorFor( model => model.Name )
            @Html.ValidationMessageFor( model => model.Name )
        </div>
        <br />
        <div id="container">
            <div>Address List</div>
            @foreach (Address a in Model.Addresses ) {
                Html.EditorFor(a);
            }
        </div>
        <div style="text-align:right;margin-top:14px;">
            <input type="submit" id="btnSubmit" value="Save" />
        </div>
    </fieldset>
}

To be automatically bindable the resultant code for the form should look as the following

<form action="..." id="..." method="post">
    <input type="hidden" name="Id" value="2">
    <input type="hidden" name="Name" value="Acme Corporation">

    <!-- markup for each address -->
    <input type="hidden" name="Addresses[0].Id" value="1">
    <input type="hidden" name="Addresses[0].Line1" value="Line 1">
    <input type="hidden" name="Addresses[0].Line2" value="Line 2">
    ... and so on...
</form>

having it's properties named as Addresses[index].PropertyName. If you add new addresses on the client it does'nt matter so much: as long as your code respect this rule you can have the default Model Binder do the job for you.

Hope this helps

like image 150
Lorenzo Avatar answered May 18 '23 17:05

Lorenzo