Question

Following this question I was wondering if there is anyway to make MVC model binder only bind elements to a list if there is a value to populate them. For example if have a form with three inputs with the same name and one value isn't entered how do I stop MVC binding a list that has 3 elements one of which is null?

Was it helpful?

Solution

Custom Model Binder

You could implement your own model binder to prevent the null values from being added to the list:

View:

@model MvcApplication10.Models.IndexModel

<h2>Index</h2>

@using (Html.BeginForm())
{
    <ul>
        <li>Name: @Html.EditorFor(m => m.Name[0])</li>
        <li>Name: @Html.EditorFor(m => m.Name[1])</li>
        <li>Name: @Html.EditorFor(m => m.Name[2])</li>
    </ul>

    <input type="submit" value="submit" />
}

Controller:

public class HomeController : Controller
{
    public ViewResult Index()
    {
        return View();
    }

    [HttpPost]
    public ActionResult Index(IndexModel myIndex)
    {
        if (ModelState.IsValid)
        {
            return RedirectToAction("NextPage");
        }
        else
        {
            return View();
        }
    }
}

Model:

public class IndexModel
{
    public List<string> Name { get; set; }
}

Custom Model Binder:

public class IndexModelBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        bool hasPrefix = bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName);
        string searchPrefix = (hasPrefix) ? bindingContext.ModelName + "." : "";

        List<string> valueList = new List<string>();

        int index = 0;
        string value;

        do
        {
            value = GetValue(bindingContext, searchPrefix, "Name[" + index + "]");

            if (!string.IsNullOrEmpty(value))
            {
                valueList.Add(value);
            }

            index++;

        } while (value != null); //a null value indicates that the Name[index] field does not exist where as a "" value indicates that no value was provided.

        if (valueList.Count > 0)
        {
            //obtain the model object. Note: If UpdateModel() method was called the model will have been passed via the binding context, otherwise create our own.
            IndexModel model = (IndexModel)bindingContext.Model ?? new IndexModel();
            model.Name = valueList;
            return model;
        }

        //No model to return as no values were provided.
        return null;
    }

    private string GetValue(ModelBindingContext context, string prefix, string key)
    {
        ValueProviderResult vpr = context.ValueProvider.GetValue(prefix + key);
        return vpr == null ? null : vpr.AttemptedValue;
    }
}

You will need to register the model binder in the Application_Start() (global.asax):

    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();

        //this will use your custom model binder any time you add the IndexModel to an action or use the UpdateModel() method.
        ModelBinders.Binders.Add(typeof(IndexModel), new IndexModelBinder());

        RegisterGlobalFilters(GlobalFilters.Filters);
        RegisterRoutes(RouteTable.Routes);
    }

Custom Validation Attribute

Alternatively, you could validate that all of the values are populated by using a custom attribute:

View:

@model MvcApplication3.Models.IndexModel

<h2>Index</h2>

@using (Html.BeginForm())
{
    @Html.ValidationMessageFor(m => m.Name)
    <ul>
        <li>Name: @Html.EditorFor(m => m.Name[0])</li>
        <li>Name: @Html.EditorFor(m => m.Name[1])</li>
        <li>Name: @Html.EditorFor(m => m.Name[2])</li>
    </ul>

    <input type="submit" value="submit" />
}

Controller:

Use the same controller defined above.

Model:

public class IndexModel
{
    [AllRequired(ErrorMessage="Please enter all required names")]
    public List<string> Name { get; set; }
}

Custom Attribute:

public class AllRequiredAttribute : ValidationAttribute
{
    public override bool IsValid(object value)
    {
        bool nullFound = false;

        if (value != null && value is List<string>)
        {
            List<string> list = (List<string>)value;

            int index = 0;

            while (index < list.Count && !nullFound)
            {
                if (string.IsNullOrEmpty(list[index]))
                {
                    nullFound = true;
                }
                index++;
            }
        }

        return !nullFound;
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top