Search code examples
ajaxasp.net-mvcasp.net-ajaxpartial-viewsasp.net-mvc-partialview

Ajax call returning old ASP.NET MVC partial view instead of updated view


I have an Ajax call triggered by a button, that calls a controller to update a partial view. The controller returns an updated partial view, but the partial view received in the success function of the Ajax call is the original view, not the updated view.

I created a sample ASP.NET MVC program to reproduce the problem. The program displays a list of customers in a table as follows.

Snapshot of UI

The text boxes are rendered in the partial view _Status. When the Toggle Status button is clicked, controller is called via Ajax to toggle the customer's status between true and false, and refresh the partial view of the corresponding text box. The problem is that the status never changes. Why is that?

UPDATE

I just added the following line of code in the Status action of the controller, and now, the Ajax success function correctly receives the updated partial view!

this.ModelState.Clear();

Can someone explain why?

Here is the Index.cshtml view that displays the initial view.

@model IEnumerable<ComboModel.Models.CustomerSummary>

<script type="text/javascript">
    function updatePartialView(id) {
        debugger;
        $.ajax({
            url: "/CustomerSummary/Status",
            data: $('#' + id + ' :input').serialize(),
            dataType: "HTML",
            type: "POST",
            success: function (partialView) {
                // Here the received partial view is not the one created in
                // the controller, but the original view. Why is that?
                debugger;
                $('#' + id).replaceWith(partialView);
            },
            error: function (err) {
                debugger;
            },
            failure: function (err) {
                debugger;
            }
        });
    }
</script>

<h2>Customer Summary</h2>
<table>
    <tr>
        <th>Name</th>
        <th>Active?</th>
        <th>Toggle</th>
    </tr>

    @foreach (var summary in Model)
    {
        <tr>
            <td>@summary.FirstName @summary.LastName</td>
            @Html.Partial("_Status", summary.Input)
            <td><button type="button" name="@("S" + summary.Input.Number)" onclick="updatePartialView(this.name)">Toggle Status</button></td>
        </tr>
    }
</table>

The _Status.cshtml partial view.

@model ComboModel.Models.CustomerSummary.CustomerSummaryInput

<td id="@("S" + Model.Number)">
    @Html.TextBoxFor(model => model.Active)
    <input type="hidden" value="@Model.Number" name="Number" />
</td>

The CustomerSummaryController.cs.

using System.Collections.Generic;
using System.Web.Mvc;
using ComboModel.Models;

namespace ComboModel.Controllers
{
    public class CustomerSummaryController : Controller
    {
        private readonly CustomerSummaries _customerSummaries = new CustomerSummaries();

        public ViewResult Index()
        {
            IEnumerable<CustomerSummary> summaries = _customerSummaries.GetAll();
            return View(summaries);
        }

        public PartialViewResult Status(CustomerSummary.CustomerSummaryInput input)
        {
            this.ModelState.Clear(); // If I add this, things work. Why?
            input.Active = input.Active == "true" ? "false" : "true";
            return PartialView("_Status", input);
        }
    }

    public class CustomerSummaries
    {
        public IEnumerable<CustomerSummary> GetAll()
        {
            return new[]
            {
                new CustomerSummary
                {
                    Input = new CustomerSummary.CustomerSummaryInput {Active = "true", Number = 0},
                    FirstName = "John",
                    LastName = "Smith"
                },
                new CustomerSummary
                {
                    Input = new CustomerSummary.CustomerSummaryInput {Active = "false", Number = 1},
                    FirstName = "Susan",
                    LastName = "Power"
                },
                new CustomerSummary
                {
                    Input = new CustomerSummary.CustomerSummaryInput {Active = "true", Number = 2},
                    FirstName = "Jim",
                    LastName = "Doe"
                },
            };
        }
    }
}

And finally, the CustomerSummary.cs model.

namespace ComboModel.Models
{
    public class CustomerSummary
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }

        public CustomerSummaryInput Input { get; set; }

        public class CustomerSummaryInput
        {
            public int Number { get; set; }
            public string Active { get; set; }
        }
    }
}

Thanks!


Solution

  • This is a duplicate of Asp.net MVC ModelState.Clear.

    In the current scenario, because there is no validation of the status field, it is ok to clear the ModelState. However, the MVC correct way would be to pass a status value (true or false) to a GET action in the controller, toggle the value, return the result string, and update the text box on the page.