How to handle MVC model binding prefix with same f

2020-07-22 18:54发布

问题:

My main view model has a collection of ChildViewModel. In the view, I loop over the collection and call EditorFor(), like so:

@for (int i = 0; i < Model.Children.Count; i++)
{
    @Html.EditorFor(m => m.Child[i]);
}

The editor template looks like:

@model ChildModel

@using (Html.BeginForm("EditChild", "MyController"))
{
    @Html.HiddenFor(m => m.ChildId)
    @Html.TextBoxFor(m => m.ChildName)
}

This will generate markup where each children is in a separate form, and each such form will have an input control with name like Child[0].ChildName. I use a separate form for each children, as the children will be displayed one on each row, and the user can then edit and submit a single row.

My form action method is:

[HttpPost]
public ActionResult EditChild(ChildViewModel form) { }

The problem is that when this is called, all properties of model will be null, because the model binder doesn't know about the prefix. In some situations we can use BindAttribute to tell the model binder about the prefix, but in this case the prefix isn't constant: It will be Child[0], Child[1], etc.

In summary, we want to repeat the same form for each row of a collection, and then allow the user to POST a single form. How can web handle id, name, prefix and model binding in this scenario?

回答1:

I have a same problem with you, and there is my solution, hope it can help you.

add a hidden input in your EditorTemplate or Partial View

<input type="hidden" name="__prefix" value="@ViewData.TemplateInfo.HtmlFieldPrefix" />

define a custom model binder and override the BindModel method

public class CustomModelBinder : DefaultModelBinder
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var prefixValue = bindingContext.ValueProvider.GetValue("__prefix");
        if (prefixValue != null)
        {
            var prefix = (String)prefixValue.ConvertTo(typeof(String));
            if (!String.IsNullOrEmpty(prefix) && !bindingContext.ModelName.StartsWith(prefix))
            {
                if (String.IsNullOrEmpty(bindingContext.ModelName))
                {
                    bindingContext.ModelName = prefix;
                }
                else
                {
                    bindingContext.ModelName = prefix + "." + bindingContext.ModelName;

                    // fall back
                    if (bindingContext.FallbackToEmptyPrefix && 
                        !bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName))
                    {
                        bindingContext.ModelName = prefix;
                    }
                }
            }
        }
        return base.BindModel(controllerContext, bindingContext);
    }
}

The model binder will trim the prefix and make the default model binding work.