MVC Razor create list of objects to submit

20,759

Solution 1

For this you can use Html.BeginCollectionItem https://www.nuget.org/packages/BeginCollectionItem/

What you can do is have a View for the Main Form and Pass the Parent Model to it as this will represent the Parent, then you will need a row to represent a Child, you can call this ChildRow.cshtml

So for the Parent we are creating Create.cshtml View which we will pass the Parent Model.

@model Parent

@using(Html.BeginForm())
{
   @Html.TextBoxFor(m => m.Name)


    <table id="children">
        <tbody>

<!-- This here will display the Child Row for existing Rows in the Parent model -->

            @foreach (var child in Model.Children )
            {
                @Html.Partial("ChildRow", child)
            }
        </tbody>

    </table>
<button  type="button" id="addChild">Add Child</button>

<button type="submit"> Save</button>
    }

Then this is what the ChildRow.cshtml will look like, It will have a Child model.

Note: you will have to add IsDeleted property to child Model, this will help you in the controller - to see if Child was deleted or Not.

@model Child

@using (Html.BeginCollectionItem("Children"))
{
    <tr>
        <td>
             @Html.HiddenFor(m => m.IsDeleted, new { data_is_deleted = "false" })
            @Html.HiddenFor(m => m.ChildId)
            @Html.TextBoxFor(m => m.Name )
        </td>

        <td>
             <span class="glyphicon glyphicon-trash action-icon" data-action="removeItem"></span>
        </td>


    <tr>
}

Now you have to add a Row to the end of the Table when the button Add Child is clicked.

For this you will create a new action in the Controller:

    public ActionResult AddChild()
    {
        return PartialView("Child", new Child());
    }

And then add this jQuery to the Create.cshtml

    $("#addChild").on("click", function () {
        $.ajax({
            url: '@Url.Action("AddChild", "YourController")'
        }).success(function (partialView) {
            $('#children> tbody:last-child').append(partialView);
        });
    });

this will append a new Child Row to the Table

Also you will have to hide/disable the row if you want to delete on the same page aswell, for that you can add this jQuery:

$('body').on("click", '*[data-action="removeItem"]', function (e) {
    e.stopPropagation();
    var btn = $(this);
    var row = btn.closest('tr');
    var firstCell = $(row.find('td')[0]);
    var checkBox = firstCell.find('*[data-is-deleted]');
    var checkBoxVal = checkBox.val();

    if (checkBoxVal === 'False' || checkBox.val() === 'false') {
        checkBox.val('true');
        row.find('td').find('input, textarea, select').attr('readonly', 'readonly');
    } else {
        checkBox.val('false');
        row.find('td').find('input, textarea, select').attr("readonly", false);
    }

});

Then when you POST back to the Controller you will see the List of Children in the Model.

[HttpPost]
public ActionResult Create(Parent model)
{

   var newChildren = model.Children.Where(s => !s.IsDeleted && s.ChildId == 0);

   var updated = model.Children.Where(s => !s.IsDeleted && s.ChildId != 0);

   var deletedChildren = model.Children.Where(s => s.IsDeleted && s.ChildId != 0);


    return View(model);
}

You will have to do something similar for the GrandChildren.

Solution 2

I did a project related to your scenario. However, I couldn't find a razor solution. So i ended up using SheepIt jquery plugin to dynamically add fields.

https://github.com/mdelrosso/sheepit

Make sure you stick to id and name pattern which asp mvc understands.

<input class="form-control text-box single-line" id="sheepItForm_#index#__Field" name="sheepItForm[#index#].Field" type="text" value="" />
<span class="field-validation-valid text-danger" data-valmsg-for="sheepItForm[0].Field" data-valmsg-replace="true"></span>

Hope this helps!

Solution 3

I think you need a usual Standard <input/> which you should know from HTML5. If I understood you correctly you want to pass the data from the view to the Controller. Now you should need a button in the form you've created. You need/should call a Controller by passing the model-data from the view to the Controller. @Html.ActionLink() is a Razor-Helper for <Input>

Something like this inside the form may should do the trick:

@Html.ActionLink("Click here to pass", "ActionName", new { ParameterNameOfTheController = Model })

The logic behind that stuff should happen in the Controller. I hope this can help you.

Share:
20,759
Softe Eng
Author by

Softe Eng

Updated on July 09, 2022

Comments

  • Softe Eng
    Softe Eng almost 2 years

    I am an experienced .NET C# Software Developer, however only a few months ago i started worked with MVC Razor (MVC 5).

    I have a small situation that i couldn't find any answer to on the net(after hours of searching)

    I have a Model that has a list of another Model which in turn also has a list of a model as shown below.

    public class Parent
    {
        public string Name { get; set; }
        public string Sex { get; set; }
        public int Age { get; set; }
        public List<Child> Children { get; set; } 
    }
    
    public class Child
    {
        public string Name { get; set; }
        public string Sex { get; set; }
        public int Age { get; set; }
        public List<GrandChild> GrandChildren { get; set; } 
    }
    
    public class GrandChild
    {
        public string Name { get; set; }
        public string Sex { get; set; }
        public int Age { get; set; }
    }
    

    and my Controller has 2 methods, one is main Get, and the other one is post in order to post new data

    public ActionResult Index()
    {
        return View();
    }
    
    [HttpPost]
    public ActionResult PostParent(Parent parent)
    {
        if (ModelState.IsValid)
        {
            //Do an insert to the DB
            return View("~/Views/Home/Index.cshtml");
        }
    
        return Json("Error");
    }
    

    However in my View in Form, i cannot find a way to create an add button and insert new data to the list of Children and GrandChildren(incase of a Child)

    @using (Html.BeginForm("PostParent", "Home", FormMethod.Post, new {@class = "form-horizontal", role = "form"}))
    {
        @Html.LabelFor(x => x.Name)
        @Html.TextBoxFor(x => x.Name)
    }
    

    I can only add fields for primitive type properites, but not for Objects.

    I would really appreciate any Help!

  • Softe Eng
    Softe Eng about 8 years
    Hi H. Senkaya Yes i already have a submit button, and the logic is in the controller, however, i am wondering how to create new list and add items to the list from the view, so when i click submit, the data will be there.
  • Softe Eng
    Softe Eng about 8 years
    Hi Vish J Well i actually found a solution by adding items to the list using pure javascript and sticking to mvc naming patterns. But this will take too much time if you have big complex nested objects.
  • Softe Eng
    Softe Eng about 8 years
    Wow, that actually worked, thanks for your example now i have a better understanding on how to use Html.BeginCollectionItem. Marked as an Answer :)
  • Coder
    Coder about 5 years
    A new id of my child class isnt being created and passed back with the model. Instead it's just passing 0. Any ideas?
  • Dawood Awan
    Dawood Awan about 5 years
    @Callum better to post a question with your code and models - then link the question in the comment for someone to take a look
  • Taylor C. White
    Taylor C. White about 5 years
    @SofteEng it doesn't look like you marked this as the answer.