Reputation: 12253
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?
Upvotes: 2
Views: 1807
Reputation: 4909
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);
}
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;
}
}
Upvotes: 1