简体   繁体   中英

ASP.NET MVC PagedList using AJAX in partial view

I'm building an ASP.NET MVC 5 app using Visual Studio 2015. The search works fine on the first try, but then if I click any of the page numbers in the MVC PagedList component , it throws an Internal Server Error . Here's the AJAX form; note that it passes the data received from the search to a partial view:

@using (Ajax.BeginForm("SearchCustomers", "Permits",
new AjaxOptions
{
    UpdateTargetId = "targetElement",
    OnSuccess = "onAjaxSuccess",
    OnFailure = "onAjaxFailure"
},
new { @class = "form-horizontal form-small", role = "form", id="customerSearchForm" }))
{
    @Html.AntiForgeryToken()
    <div class="modal-header">
        <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
        <h4>Customer Search</h4>
    </div>
    <div class="modal-body">
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })
        <div class="form-group-sm clearfix">
            @Html.LabelFor(m => m.SearchVm.SearchCustomerNameNumber, new { @class = "control-label col-xs-5 col-md-5" })
            <div class="col-xs-5 col-md-5">
                <div class="input-group">
                    @Html.EditorFor(m => m.SearchVm.SearchCustomerNameNumber, new {htmlAttributes = new {@class = "form-control"}})
                    <span class="input-group-btn">
                        <button type="submit" class="btn btn-custom-success btn-sm btn-custom-sm small-box-shadow btn-block">
                            Search
                            <i class="fa fa-search fa-lg" aria-hidden="true"></i>
                        </button>
                    </span>
                </div>
                @Html.ValidationMessageFor(m => m.SearchVm.SearchCustomerNameNumber, "", new { @class = "text-danger" })
            </div>
        </div>
        <div class="modal-search" id="targetElement">
            @Html.Partial("_PermitsCustomerList", Model.SearchVm.Customers)
        </div>
    </div>
}

In the _PermitsCustomerList partial view, I have the following:

@using PagedList
@using PagedList.Mvc
@model IPagedList<MyProject.ViewModels.CustomerViewModel>
@if (Model != null && Model.Count > 0)
{
    <div class="panel panel-default data-grid data-grid-wide">
        <table class="table table-hover table-striped table-bordered table-responsive">
            <tr>
                <th>
                    Customer #
                </th>
                <th>
                    Customer Name
                </th>
            </tr>
            @foreach (var item in Model)
            {
                <tr>
                    <td>
                        @Html.DisplayFor(modelItem => item.Customer_NO)
                    </td>
                    <td>
                        @Html.DisplayFor(modelItem => item.Customer_Name)
                    </td>
                </tr>
            }
        </table>
        <div id="contentPager">
            @Html.PagedListPager(Model, page => Url.Action("SearchCustomers", "Permits", 
               new { page }), 
               PagedListRenderOptions.EnableUnobtrusiveAjaxReplacing( new AjaxOptions()
       {
           HttpMethod = "POST",
           UpdateTargetId = "targetElement",
           OnSuccess = "onAjaxSuccess",
           OnFailure = "onAjaxFailure"
       }))
        </div>
    </div>
}

And here's the action on the controller:

[HttpPost]
[ValidateAntiForgeryToken]
public PartialViewResult SearchCustomers(PermitsViewModel permitsVm, int? page)
{
    if (string.IsNullOrEmpty(permitsVm.SearchVm.SearchCustomerNameNumber)) return null;
    permitsVm.Page = page;
    int number;
    var list = int.TryParse(permitsVm.SearchVm.SearchCustomerNameNumber, out number) 
       ? CustomerDataService.SearchCustomerByNumber(number) 
       : CustomerDataService.SearchCustomerByName(permitsVm.SearchVm.SearchCustomerNameNumber);

    return PartialView("_PermitsCreateCustomerList", list.ToPagedList(permitsVm.Page ?? 1, 10));
}

Here are the success and failure callback functions:

function onAjaxFailure(xhr, status, error) {
    $("#targetElement").html("<strong>An error occurred retrieving data:" + error + "<br/>.</strong>");
}
function onAjaxSuccess(data, status, xhr) {
    if (!$.trim(data)) {
        $("#targetElement").html("<div class='text-center'><strong>No results found for search.</strong></div>");
    }
}

I looked at this example: MVC 4 Using Paged List in a partial View and added the PagedListRenderOptions.EnableUnobtrusiveAjaxReplacing but something is still missing.

When I view the console panel in Chrome, it has this error when I click on a page number:

http://localhost:9999/MyProject/Permits/SearchCustomers?page=2 500 (Internal Server Error)

What am I doing wrong when trying to do an AJAX call with the PagedList component?

After lots of trial-and-error (mostly error!), the answer was to not use a view model field for the search.

Here's the new search field in the main view:

@{
    string searchName = ViewBag.SearchName;
}
@Html.EditorFor(x => searchName, new {htmlAttributes = new {@class = "form-control"}})

Then in the action, this change receives the searchName value:

[HttpPost]
[ValidateAntiForgeryToken]
public PartialViewResult CreateSearch(string searchName, int? page)
{
    if (string.IsNullOrEmpty(searchName)) return null;
    int number;
    var list = int.TryParse(searchName, out number) 
        ? CustomerDataService.SearchCustomerByNumber(number) 
        : CustomerDataService.SearchCustomerByName(searchName);
    var permitsVm = new PermitsViewModel 
        {SearchVm = {Customers = list.ToPagedList(page ?? 1, 20)}};
    ViewBag.SearchName = searchName;
    return PartialView("_PermitsCreateCustomerList", permitsVm);
}

Note the ViewBag.SearchName ; that will be used to pass the search field value to the partial view.

<div id="contentPager">
    @Html.PagedListPager(Model, page => Url.Action("SearchCustomers", "Permits", 
       new { ViewBag.SearchName, page }), 
       PagedListRenderOptions.EnableUnobtrusiveAjaxReplacing( new AjaxOptions()
{
   HttpMethod = "POST",
   UpdateTargetId = "targetElement",
   OnSuccess = "onAjaxSuccess",
   OnFailure = "onAjaxFailure"
}))
</div>

In the paging mechanism above, we use the ViewBag to pass the search value back to the controller.

Update 1: You also need the following on the main view (the one containing the partial) to ensure the anti-forgery token gets sent when you click the numbers in the paging:

$.ajaxPrefilter(function(options, originalOptions, jqXHR) {
    if (options.type.toUpperCase() === "POST") {
        // We need to add the verificationToken to all POSTs
        var token = $("input[name^=__RequestVerificationToken]").first();
        if (!token.length) return;

        var tokenName = token.attr("name");

        // If the data is JSON, then we need to put the token in the QueryString:
        if (options.contentType.indexOf('application/json') === 0) {
            // Add the token to the URL, because we can't add it to the JSON data:
            options.url += ((options.url.indexOf("?") === -1) ? "?" : "&") + token.serialize();
        } else if (typeof options.data === 'string' && options.data.indexOf(tokenName) === -1) {
            // Append to the data string:
            options.data += (options.data ? "&" : "") + token.serialize();
        }
    }
});

From here: https://gist.github.com/scottrippey/3428114

Update 2: You can use the view model on the controller but have to pass a RouteValueDictionary :

<div id="contentPager">
    @Html.PagedListPager(Model, page => Url.Action("SearchCustomers", "Permits", 
       new RouteValueDictionary()
    {
        { "Page", page},
        { "SearchVm.SearchCustomerNameNumber", Model.SearchVm.SearchCustomerNameNumber }
    }), 
       PagedListRenderOptions.EnableUnobtrusiveAjaxReplacing( new AjaxOptions()
    {
       HttpMethod = "POST",
       UpdateTargetId = "targetElement",
       OnSuccess = "onAjaxSuccess",
       OnFailure = "onAjaxFailure"
    }))
</div>

With this, you'd change the action:

[HttpPost]
[ValidateAntiForgeryToken]
public PartialViewResult CreateSearch(PermitsViewModel permitsVm)
{
    if (string.IsNullOrEmpty(permitsVm.SearchVm.SearchCustomerNameNumber)) return null;
    int number;
    var list = int.TryParse(permitsVm.SearchVm.SearchCustomerNameNumber, out number) 
        ? CustomerDataService.SearchCustomerByNumber(number) 
        : CustomerDataService.SearchCustomerByName(permitsVm.SearchVm.SearchCustomerNameNumber);
    permitsVm.SearchVm.Customers = list.ToPagedList(permitsVm.Page ?? 1, 10);
    return PartialView("_PermitsCreateCustomerList", permitsVm);
}

The complex objects help on the RouteValueDictionary came from here: https://stackoverflow.com/a/23743358/177416

It looks like you're not passing a required argument to your controller. Change your PagedListPager to this:

@Html.PagedListPager(Model, page => Url.Action("SearchCustomers", "Permits",
new { page = page, permitsVm = Json.Encode(Model)}), 
PagedListRenderOptions.EnableUnobtrusiveAjaxReplacing( new AjaxOptions()
{
   HttpMethod = "POST",
   UpdateTargetId = "targetElement",
   OnSuccess = "onAjaxSuccess",
   OnFailure = "onAjaxFailure"
}))

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM