jacobsowles
jacobsowles

Reputation: 3003

Dynamically creating hidden form fields for various models (MVC 4)

I'm trying to dynamically create hidden fields for a set of properties, but I'm getting a 500 server error when I submit the form. I confirmed the following:

Here's what I have:

View

@model PaneViewModel

using (Ajax.BeginForm("AddItem", "Action", new AjaxOptions
{
    UpdateTargetId = "tool-wrapper",
    HttpMethod = "POST",
}))
{
    // Some standard input fields here (these are working properly).

    [...]

    // Here's what's broken:
    @foreach (var property in Model.NewItem.GetType().GetProperties().Where(<criteria here>))
    {
        @Html.HiddenFor(m => m.NewItem.GetType().GetProperty(property.Name), column.GetValue(Model.NewItem, null))
    }

    <button type="submit">Add</button>
}

ItemViewModel

public class ItemViewModel
{
    public int SomeField { get; set; }
    public int AnotherField { get; set; }
}

PaneViewModel

public class PaneViewModel
{
    public ItemViewModel NewItem { get; set; }
}

Controller

[HttpPost]
public ActionResult AddItem([Bind(Prefix = "NewItem")] ItemViewModel model)
{
    // Stuff here.
}

It's worth noting that the following generates the hidden fields with the correct names and values in the generated HTML, but the values of the hidden field aren't posted to the controller action:

@foreach (var property in Model.NewItem.GetType().GetProperties().Where(<criteria here>))
{
    @Html.Hidden(property.Name, column.GetValue(Model.NewItem, null))
}

So it seems the problem is with the m => m.NewItem.GetType().GetProperty(property.Name) component

Upvotes: 1

Views: 2196

Answers (1)

user3559349
user3559349

Reputation:

  1. This type of logic does not belong in a view
  2. Html.HiddenFor() expects an expression (Expression<Func<TModel, TProperty>>) as the first parameter, but .GetProperty() returns typeof PropertyInfo
  3. You should not be generating multiple hidden inputs for properties of your model, but rather use a view model to represent only what you need to edit (it degrades performance by sending extra data to the client and then posting it back again unchanged, and anyone could use FireBug or similar tools to change the values and you might be none the wiser.

However, if you do want to do this, the you could create a html helper that generates hidden inputs for all properties marked with the [HiddenInput] attribute (or modify this example to pass in some condition that filters the required properties)

public static MvcHtmlString HiddenForModel<TModel, TValue>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TValue>> expression)
{
  StringBuilder html = new StringBuilder();
  ModelMetadata metaData = ModelMetadata.FromLambdaExpression(expression, helper.ViewData);
  var properties = metaData.Properties.Where(p => p.TemplateHint == "HiddenInput");
  foreach(var property in properties)
  {
    html.Append(helper.Hidden(property.PropertyName));
  }
  return MvcHtmlString.Create(html.ToString());
}

Note this will also generate the id and data-val-* attributes which are probably unnecessary, so you could minimize the generated html by using

foreach(var property in properties)
{
  TagBuilder input = new TagBuilder("input");
  input.MergeAttribute("type", "hidden");
  input.MergeAttribute("name", property.PropertyName);
  input.MergeAttribute("value", string.Format("{0}", property.Model));
  html.Append(input.ToString());
}

Upvotes: 2

Related Questions