0

I have object A which has a List<ObjectB> ObjectB has several properties. Id, Mandatory, Name etc.

So I return a viewmodel (ObjectA) and have this in my razor:

@model ObjectA
<div>
<div>@Html.HiddenFor(m => ObjectA.ObjectC.ID)
    <dl class="dl-horizontal">
        <dt>
            @Html.DisplayNameFor(model => ObjectA.ObjectC.Name)
        </dt>

        <dd>
            @Html.DisplayFor(model => ObjectA.ObjectC.Name)
        </dd>
    </dl></div>
    // display stuff from objectA
    @using (Html.BeginForm())
    {
        foreach (var ft in ObjectA.ObjectB)
        {
            @Html.HiddenFor(c => ft.ID)
            <div class="row">
                <div class="col">
                @if (ft.Mandatory)
                {
                    @Html.CheckBoxFor(c => ft.Mandatory, new { id = ft.ID, disabled = "disabled" })
                    @Html.HiddenFor(c => ft.Mandatory)
                }
                else
                {
                    @Html.CheckBoxFor(c => ft.Mandatory, new { id = ft.ID })
                }
                </div>
        </div>
    </div>
</div>

and in my Controller I tried as input parameter: List<ObjectB> items but it was null. Now I know I could try FormCollection which I did and found out that form.Get("ft.id") had the amount of items in de objectB list. same for mandatory. But I'd like it strong typed. Either:

1 object A with all subobjects of type B

2 a list/ienumerable of type objectB.

It's probably a small thing, but I can't see it right now.

edit my model:

public class ObjectA : BaseViewModel
{
    public ObjectC DisplayOnly { get; internal set; }

    public List<ObjectB> Features { get; set; }
}

My view: (see above) My controller:

[Route("Details/{id:int}")]
[HttpPost]
public ActionResult Details(ObjectA vm)
{
    if (ModelState.IsValid)
    {
        int hid = Convert.ToInt32(RouteData.Values["id"]);
    }
}
JP Hellemons
  • 5,977
  • 11
  • 63
  • 128
  • When you post your model `ObjectA`, at Html level I don't see any reason for the browser to send all the fields but only those present in the Html form. So it doesn't make much sense trying to receive `List` as input in your controller. – derloopkat Nov 12 '17 at 20:08
  • All props of `ObjectA` are just there to display. And the ObjectB is a sort of extended select list. It's id, mandatory, name, price, description etc. So only the changes of objectB is needed. But a full objectA with everything is okay too. Tried that and was null. – JP Hellemons Nov 12 '17 at 20:11

1 Answers1

1

With your current code, it will render the checkbox input elements with name attribute values set to ft.Mandatory. When the form is submitted, model binder has no idea where to map this to because it does not match with the view model property names/property hierarchy. For model binding to work, the names of input should match with the parameter class's property name.

You can use Editor Templates to handle this use case.

Create a folder called EditorTemplates in ~/Views/Shared or ~/Views/YourControllerName and create a new view and give it the same name as your class name which you are using to represent the data needed for the checkbox. In your case it will be ObjectB.cshtml

Now make this view strongly typed to OptionB and render the checkbox and hidden input.

@model OptionB
<div>
    @Html.CheckBoxFor(x => x.Mandatory)
    @Html.HiddenFor(c => c.Id)
</div>

Remember, the browser will not send the values of disabled form elements when the form is submitted.

Now in your main view, you can call EditorFor helper method.

@using (Html.BeginForm())
{   
    @Html.EditorFor(a=>a.OptionBList)
    <button type="submit">Send form</button>
}

This will render the checkboxes with name attribute values like this (Assuming you have 3 items in OptionBList) , along with hidden inputs for checkboxes

Options[0].Mandatory
Options[1].Mandatory
Options[2].Mandatory

Now you can simply use OptionA as the parameter of your HttpPost action method

[HttpPost]
public ActionResult Create(OptionA model)
{
   foreach(var item in model.OptionBList)
   {
     //check item.Mandatory and item.Id
   }
   //to do : return something
}
Shyju
  • 214,206
  • 104
  • 411
  • 497
  • But I'd like to receive a list with all checked checkboxes. Some are mandatory so they are selected by default and made readonly/disabled. But I need those too in my controller action. – JP Hellemons Nov 12 '17 at 20:39
  • This will give all of checkboxes (one for each item in the OptionBList collecction). See my update to the answer. Disabled checkboxes won't be submitted. – Shyju Nov 12 '17 at 20:40
  • 1
    Your editor template seems to work. I did had to add constructor to the model with no input parameter. But it posts back now and the list is not null. – JP Hellemons Nov 12 '17 at 21:01