1

ASP .NET MVC Partial View is rendering unexpected html based on Model. Can someone please explain why this is happening? There seems to be something about rendering Partial Views that I don’t understand. Thank you in advance.

I have a simple MVC Partial View that binds to a list of Person objects. I make two Ajax calls one for adding and one for removing a person from the list. The Adding of a person works fine. The Removing of a person always removes the last person object from the list instead of rendering the html according to the persons that are in the list from the Model.

After adding 2 persons (click Add Person button twice and enter names): adding 2 persons

After I hit remove button for “Person B” calls the RemovePerson() JS function, which calls the PersonController RemovePerson() Action. The Controller removes the item with the specified index from the list but the HTML coming from the partial view does not reflects the new updated model. Instead of removing person B always removes last person in this case “Person C” even though I am passing the appropriate index and removing the item from the list in the controller.

I have added the Visual Studio solution as a zip file in GitHub: https://github.com/peter-base/PersonList

Index View:

//adds a new person textbox
function OnAddPerson() {
    $.ajax({
        cache: false,
        type: "POST",
        url: "/Person/AddPerson",
        data: $("#person-list-form").serialize(),
        success: function (partialView) {
            $("#persons-container").html(partialView);
        },
        error: function (jqXHR, textStatus, errorThrown) {
            alert("error:" + errorThrown);
        },
        complete: function () {
        }
    });
};

function RemovePerson(index) {
    $("#remove_person_index").val(index);
    alert("index: " + $("#remove_person_index").val());
    $.ajax({
        cache: false,
        type: "POST",
        url: "/Person/RemovePerson",
        data: $("#person-list-form").serialize(),
        success: function (partialView) {
            $("#persons-container").html(partialView);
        },
        error: function (jqXHR, textStatus, errorThrown) {
           alert("error:" + errorThrown);
        },
        complete: function () {
        }
    });
};



@using PersonList.Models
<script src="~/Scripts/PersonList.js"></script>
@{
    ViewBag.Title = "Index";
}

<div>
    <h2>Index - Persons</h2>
    <button type="button" class="btn btn-default form-control"     onclick="OnAddPerson();">
        <span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add Person
    </button>
</div>
    <div id="persons-container">
        @*List of Persons*@
        @Html.Partial("_PersonList", new List<Person>() { new Person() { Index = 0, Name = "Person A" } }, new ViewDataDictionary())
    </div>



<!-- Partial View in Different File --> @model 
List<PersonList.Models.Person>

@using (Html.BeginForm("", "", FormMethod.Post, new { @id = "person-list-form", @name = "person-list-form" })) {
for (int i = 0; i < Model.Count; i++)
{
    <div class="panel panel-info">
        <div class="panel-body">

            <div class="col-md-2">
                @Html.HiddenFor(m => m[i].Index)
                <label class="control-label">Name</label>
                @Html.TextBoxFor(m => m[i].Name,
                    new
                    {
                        @class = "ccb-nodes form-control",
                        @placeholder = "Enter CR-CN"
                    })
            </div>
            <div class="col-md-2">
                <button type="button" id="@Model[i].Index" class="btn btn-    primary" onclick="RemovePerson(this.id)">Remove</button>
            </div>
        </div>
    </div>
}
@Html.Hidden("remove_person_index", 1, new { @id = "remove_person_index" })
}

Person Object:

public class Person
{
    public string Name { get; set; }
    public int Index { get; set; }
}

Person Controller:

public class PersonController : Controller
{
    // GET: Person
    public ActionResult Index()
    {
        return View();
    }

    [HttpPost]
    public ActionResult AddPerson(List<Person> personList)
    {
        //add new person
        personList.Add(new Person());
        //set indexes
        for (int i = 0; i < personList.Count; i++)
        {
            personList[i].Index = i;
        }
        return PartialView("_PersonList", personList);
    }

    [HttpPost]
    public ActionResult RemovePerson(List<Person> personList, int remove_person_index)
    {
        //remove person
        var person = personList.FirstOrDefault(i => i.Index == remove_person_index);
        if (person != null)
        {
            personList.Remove(person);
        }
        //set indexes
        for (int i = 0; i < personList.Count; i++)
        {
            personList[i].Index = i;
        }

        return PartialView("_PersonList", personList);
    }
}
elite_developer
  • 116
  • 1
  • 6
  • You code is never going to bind correctly. Refer [this answer](http://stackoverflow.com/questions/28019793/submit-same-partial-view-called-multiple-times-data-to-controller/28081308#28081308) for some options to dynamically add and remove collection items, and [this answer](http://stackoverflow.com/questions/40539321/a-partial-view-passing-a-collection-using-the-html-begincollectionitem-helper/40541892#40541892) for a more complete example using `BeginCollectionItem` –  Feb 28 '17 at 02:22
  • The answer shows how to add and remove items in the client side. Do you think it will still work when adding and removing on the server side if I use the BeginCollectionItem in my partial view? – elite_developer Feb 28 '17 at 02:33
  • Your misunderstanding the answers. When you click `Add` you call a server method that returns a partial view for a new item and add it to the DOM. When you click 'Delete', you call a server method that removes the item from the database (assuming it exists) and in its ajax success callback, you remove the item from the DOM. Your current attempts to post back the whole collection just to remove or delete one item is terrible performance wise. –  Feb 28 '17 at 02:39
  • I agree with you that this is not the most performant solution, but should'nt still work? The data is not coming from the database is just textboxes for the user to submit the data via a form. If User wants to create 10 persons then he just adds 10 texboxes with the names and submits all the data to the controller. I am manually generating the Indexes so the controller is receiving the data but displaying it is the problem after removing a person from the list. – elite_developer Feb 28 '17 at 02:53
  • If you really want want to use bad code, then add `ModelState.Clear()` immediately before `return PartialView(...);` –  Feb 28 '17 at 02:55
  • I tried 'ModelState.Clear()' and it works perfectly. Now can you help me understand why it doesnt works without this line of code. When I pass the updated list to the partial view is keeping the old ModelState? why is it not updating? – elite_developer Feb 28 '17 at 03:05
  • First [this answer](http://stackoverflow.com/questions/26654862/textboxfor-displaying-initial-value-not-the-value-updated-from-code/26664111#26664111) explains the behavior and why form controls take their values if `ModelState` exist. And [this answer](http://stackoverflow.com/questions/37730148/rendered-partial-view-does-not-match-model/37739240#37739240) explains what is happening in your case. But abandon this and read the links I gave you initially to understand how to do this correctly –  Feb 28 '17 at 03:12
  • Awesome. Thanks for your help. I am new here so not sure how to mark that you answered my question? – elite_developer Feb 28 '17 at 03:20
  • I'll just dupe it to my other answer so its closed out and others can find the solution for a similar problem (you can always vote on the other answers I lined to when you earn the rep) –  Feb 28 '17 at 03:24

0 Answers0