MVC, post partial model

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"]);
    }
}

1 answer

  • answered 2017-11-12 20:36 Shyju

    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
    }