views:

441

answers:

5

Say you create a form using ASP.NET MVC that has a dynamic number of form elements.

For instance, you need a checkbox for each product, and the number of products changes day by day.

How would you handle that form data being posted back to the controller? You can't set up parameters on the action method because you don't know how many form values are going to be coming back.

Thanks.

A: 

Depending on your data, you could either output a 'CheckboxList' (which is not possible in the newer versions any more) and use a string[] parameter, or you could set up multiple forms and just modify the action.

leppie
+5  A: 

Just give each checkbox a unique name value:

<input class="approveCheck" id="<%= "approveCheck" + recordId %>" 
 name="<%= "approveCheck" + recordId %>" type="checkbox" />

Then parse the list of form values in the Action, after submit:

foreach (var key in Request.Form.Keys)
    {
        string keyString = key.ToString();
        if (keyString.StartsWith("approveCheck", StringComparison.OrdinalIgnoreCase))
        {
            string recNum = keyString.Substring(12, keyString.Length - 12);

            string approvedKey = Request.Form["approveCheck" + recNum];
            bool approved = !String.IsNullOrEmpty(approvedKey);
            // ...

You don't need to pass form values as arguments; you can just get them from Request.Form.

One other option: write a model binder to change the list into a custom type for form submission.

Craig Stuntz
+2  A: 

Per Craig's answer.. that is safer. There are quirks to posting multiple form elements with the same name. I would add that it would be wise to wrap the logic that makes the "collection" of controls in a way similar to WebForms. Web Forms prepend the container control's name and adds an index. For example, in a Repeater the form elements inside would be named (something like) RepeaterName_Element1, RepeaterName_Element2. When you go to get the elements out, you have to use FindControl or something of the sort.

Greg Ogle
+2  A: 

Depending on the binders you are using, this should work:

<%var i = 0;
  foreach (var product (IList<ProductSelection>)ViewData["products"]) {%>
      <%=Html.Hidden(string.Format("products[{0}].Id", i), product.Id)%>
      <%=Html.Checkbox(string.Format("products[{0}].Selected", i))%>
      <%=product.Name%><br/>
<%}%>

...which will result in HTML something like this (notice the array notation on the names):

<input name="products[0].Id" type="hidden" value="123">
<input name="products[0].Selected" type="checkbox">
Widget
<input name="products[1].Id" type="hidden" value="987">
<input name="products[1].Selected" type="checkbox">
Gadget

...and the controller method that handles the post:

public ActionResult SelectProducts(IList<ProductSelection> products)
{
     ...
}

Upon binding, products parameter will contain two instances of ProductSelection.

One caveat is that I have not used the new default binding for complex objects. Rather I am using either the NameValueDeserializer or CastleBind, both from MvcContrib. They both behave this way. I am guessing binding in the Beta will work the same way.

Tim Scott
A: 

Thank you all. I should have thought of Request.Forms! Guess I was getting too excited about the action method parameters and I forgot about it!

James
Use Request.Form only when binding cannot do the job. In very few cases will you need to mess with Request.Form.
Tim Scott