so I've looked everywhere for an answer to this peculiar problem, but I couldn't find the exact solution. Lots of similar problems, but not one that solves my exact problem, so apologies if this has already been answered.
I have the following (simplified) models:
public abstract class SimpleModel {
public List<SimpleFieldModel> Fields{ get; set; }
}
public abstract class SimpleFieldModel {
public List<ListItemModel> Items { get; set; }
}
public class ListItemModel {
public string Text { get; set; }
public string Value { get; set; }
public bool Selected { get; set; }
}
With the following SimpleModel View:
@for(var i = 0; i < Model.Fields.Count; i++) {
@Html.EditorFor(Model.Fields[i])
}
And the following SimpleFieldModel View:
@for(var i = 0; i < Model.Items.Count; i++) {
@Html.CheckBoxFor(m => Model.Items[i].Selected)
@Html.HiddenFor(m => Model.Items[i].Text)
}
And this produces the expected markup:
<input name="Fields[5].Items[0].Selected" id="Fields_5__Items_0__Selected" type="checkbox" value="true" data_val="89">
<input name="Fields[5].Items[0].Selected" type="hidden" value="false">
<input name="Fields[5].Items[0].Text" id="Fields_5__Items_0__Text" type="hidden" value="Option 1">
And when this is posted to the controller, I receive the following:
- form-data: Fields[5].Items[0].Selected = "true,false"
- model: Fields[5].Items[0].Selected = false
I have had to write a method to populate my collection with the data from the form, but I feel that Razor should be taking care of this for me, but it doesn't seem to be working.
Edit
It would appear the root cause may be down to the fact I'm expecting to receive an abstract model into my controller, as so:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(SimpleModel model)
{
}
To overcome this, I have the following model binder:
public class SimpleModelBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
var typeValue = bindingContext.ValueProvider.GetValue("ConcreteModelType");
var type = Type.GetType(
(string)typeValue.ConvertTo(typeof(string)),
true
);
if (!typeof(SimpleModel).IsAssignableFrom(type))
{
throw new InvalidOperationException("Bad Type");
}
var model = Activator.CreateInstance(type);
bindingContext.ModelMetadata =
ModelMetadataProviders.Current.GetMetadataForType(() => model, type);
return model;
}
}
The strange thing though, is that at the point of exiting the above binder, the model has the correct number of Fields, and these each have their correct and expected Items. But, when I enter into the controller method, my model appears to have been altered, as it's now missing the Items from the Fields.
I apologise for not including all code initially, but I cannot provide access to the full code-base.
Further Update
I think it might also be relevant to mention that I also have a SimpleFieldModelBinder. This is as so:
public class SimpleFieldModelBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
var typeValue = bindingContext.ValueProvider.GetValue(string.Format("{0}.{1}", bindingContext.ModelName, "ConcreteFieldModelType"));
var type = Type.GetType(
(string)typeValue.ConvertTo(typeof(string)),
true
);
if (!typeof(SimpleFieldModel).IsAssignableFrom(type))
{
throw new InvalidOperationException("Bad Type");
}
var model = Activator.CreateInstance(type);
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, type);
return model;
}
}
So, when I exit the SimpleModelBinder, I have all my fields, and they have their correct Items in place (although unbound to recent form changes). Yet, my controller seems to be receiving a list of SimpleFieldModel objects which are missing their relevant Items list.
Things are definitely going wrong with the SimpleFieldModelBinder. If I take it out, my SimpleModel contains a list of the abstract SimpleFieldModel objects. But, if I leave it in, the controller receives a SimpleModel with the correct concrete types inside its Fields list, but these are all cleared of their Items.
Another Update
I added some code in the SimpleFieldModelBinder, so that it populates the Items collection from the form data. On exiting the CreateModel, the collection was intact (and had the appropriate Selected values in place). Yet when I get to my controller (which I have to assume is the next step from binding?) These Items collections have been removed. One thing that's slightly suspicious though, is that the first Field has its Items in place, but all other fields do not. I suspect the binding is only working for the first item in the Fields collection, even though during debugging, I hit the SimpleFieldModelBinder for all the fields.
What might I be missing?
Thank you
SimpleModel model(i.e. to match the model you pass to the view) and theItemsproperty will be correctly bound with eachSelectedandTextproperty (you don't create form controls forValueso that property will benulland its not clear why you doingnew { data_val = Model.Items[i].Value })Fields[5]prefix - it will be justname="Items[0].Selected"unless there is something else you have not shown usSimpleModel model, thenmodelwill be correctly bound with the values in your form. (and is@Html.EditorFor(m => m.Model.Fields)to generate the correct html for all items in your collection - no loops)