0

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

11
  • What exactly are you expecting? Commented Mar 21, 2017 at 15:42
  • You POST method just needs a parameter SimpleModel model (i.e. to match the model you pass to the view) and the Items property will be correctly bound with each Selected and Text property (you don't create form controls for Value so that property will be null and its not clear why you doing new { data_val = Model.Items[i].Value }) Commented Mar 21, 2017 at 21:41
  • But you code will not generate that html - it will not have the Fields[5] prefix - it will be just name="Items[0].Selected" unless there is something else you have not shown us Commented Mar 21, 2017 at 23:16
  • Quite right. My model is more complex. I'll try and add more to this to help explain the situation. Thanks. Commented Mar 22, 2017 at 17:52
  • @Oxonhammer. If your POST method has a parameter SimpleModel model, then model will 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) Commented Mar 23, 2017 at 22:01

1 Answer 1

1

Please copy this to your Visual Studio. This should help you.

Controller/Models

public class HomeController : Controller
{
    public class SimpleModel
    {
        public List<ListItemModel> Items { get; set; }
    }

    public class ListItemModel
    {
        public string Text { get; set; }
        public string Value { get; set; }
        public bool Selected { get; set; }
    }

    [HttpPost]
    public ActionResult Index29(SimpleModel sm)
    {
        //put breakpoint here to interrogate passed in model
        return View();
    }

    public ActionResult Index29()
    {
        ListItemModel lim = new ListItemModel { Selected = false, Text = "aText", Value = "aValue" };
        ListItemModel limTwo = new ListItemModel { Selected = true, Text = "bText", Value = "bValue" };
        SimpleModel sm = new SimpleModel();
        sm.Items = new List<ListItemModel>();
        sm.Items.Add(lim);
        sm.Items.Add(limTwo);

        return View(sm);
    }

View:

@model Testy20161006.Controllers.HomeController.SimpleModel
@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index29</title>
</head>
<body>
    <div>
        @using (Html.BeginForm())
        {

            for (var i = 0; i < Model.Items.Count; i++)
            {
                @Html.Label(Model.Items[i].Text)

                //modify the next line
                @Html.CheckBoxFor(
                    m => Model.Items[i].Selected)

                @Html.HiddenFor(m => Model.Items[i].Text)
                @Html.HiddenFor(m => Model.Items[i].Value)
            }

            <input type="submit" value="Post" />
        }
    </div>
</body>
</html>
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.