DataTables Ajax请求无法命中Controller Action的问题排查及解决
问题描述
我正面临一个棘手的问题:相同的代码在项目其他位置能正常运行,但当前场景下,DataTables的Ajax请求始终无法命中Controller中的Action。页面加载后,DataTables只显示processing弹窗,完全加载不出数据。我在Controller的Action上设置了断点,发现请求根本没到达这个方法。
我试过把url改成@Url.Action("GetList", "Home"),怀疑是路由的问题,但检查路由配置后没发现异常。直接访问这个Url会返回404页面,而项目里其他同类请求哪怕报错,至少能命中Controller Action。
更新:我把页面和Controller Action的名称从Parts改成了PartSearch,但问题还是存在,这个Action属于Home Controller。改名是因为项目里已经有PartsController了,那个Controller是后端用的,需要登录凭证,没法用在当前场景。
相关代码
JavaScript代码
$(document).ready(function () { var oTableMenuPermission = ""; $("#PartsListTable").dataTable({ "ajax": { "url": "/Home/GetList", "type": "POST", "datatype": "json", }, "responsive": true, "bRetrieve": true, "bProcessing": true, "deferRender": true, "dom": 'lBfrtip', "serverSide": "true", "order": [0, "PartNumber"], "language": { "processing": "processing...Please wait" }, "buttons": [ { extend: 'copyHtml5', exportOptions: { columns: ':visible' } } , { extend: 'excelHtml5', exportOptions: { columns: ':visible' } } , { extend: 'csvHtml5', exportOptions: { columns: ':visible' } } , { extend: 'pdfHtml5', exportOptions: { columns: ':visible' } } , { extend: 'print', exportOptions: { columns: ':visible' } } , 'colvis' ], columnDefs: [{ orderable: false, targets: [2, 3, 4, 5, 6] }], "aoColumns": [ { "data": "PartNumber", "name": "PartNumber" }, { "data": "PartImage", "aTargets": [0], "render": function (data) { if (data === null) return '<img src=Content/Images/Parts/NoImage.png style="width:50px;" />'; return '<img src=Content/Images/Parts/' + data + ' class="zoom" style="width:50px;" />'; } }, { "data": "Description", "name": "Description" }, { "data": "HasUpgrade", "aTargets": [0], "render": function (data) { if (data === true) { return '<b style="color:green;">Yes</b>'; } else { return '<b style="color:red;">No</b>'; } } }, { "data": "IsUpgrade", "aTargets": [0], "render": function (data) { if (data === true) { return '<b style="color:green;">Yes</b>'; } else { return '<b style="color:red;">No</b>'; } } }, { "mRender": function (oObj, type, full) { var button = '<div>' button += '<div class="btn-group">'; button += '<a class="btn btn-warning btn-sm" href="' + ControlerNameParts + '/Details/' + full.Material + '" data-ajax-update="#SkEdit" data-ajax-success="openModalDialog(\'SkEdit\', \'Edit\')" data-ajax-mode="replace" data-ajax-method="GET" data-ajax-failure="clearModalDialog(\'SkEdit\');alert(\'Ajax call failed\')" data-ajax-begin="prepareModalDialog(\'SkEdit\')" data-ajax="true">Details</a> '; button += '</div></div>'; return button; } }, ], }); });
Controller Action代码
[HttpPost] public ActionResult GetList() { // Server Side Parameters int start = Convert.ToInt32(Request["start"]); int length = Convert.ToInt32(Request["length"]); string searchValue = Request["search[value]"]; string sortColumnName = Request["column[" + Request["order[0][column]"] + "][name]"]; string sortDirection = Request["order[0][dir]"]; List<PartSearchView> prodList = new List<PartSearchView>(); using (GeneralEntities db = new GeneralEntities()) { prodList = db.PartSearchView.ToList(); int totalRows = prodList.Count; if (!string.IsNullOrEmpty(searchValue)) { prodList = prodList.Where(x => x.PartNumber != null && x.PartNumber.ToLower().Contains(searchValue.ToLower())).ToList(); } int totalRowsAfterFilteing = prodList.Count; // Sorting prodList = prodList.OrderBy(sortColumnName + " " + sortDirection).ToList<PartSearchView>(); prodList = prodList.Skip(start).Take(length).ToList(); return Json(new { data = prodList, draw = Request["draw"], recordsTotal = totalRows, recordsFiltered = totalRowsAfterFilteing }, JsonRequestBehavior.AllowGet); } }
路由配置代码
public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); // routes.MapRoute( // name: "Roles", // url: "Roles/{action}/{id}", // defaults: new { controller = "UserAndRole", action = "Index", id = UrlParameter.Optional } //); routes.MapRoute( name: "About", url: "About", defaults: new { controller = "Home", action = "About" } ); routes.MapRoute( name: "Services", url: "Services", defaults: new { controller = "Home", action = "Services" } ); routes.MapRoute( name: "Contact", url: "Contact", defaults: new { controller = "Home", action = "Contact" } ); routes.MapRoute( name: "Quote", url: "Quote", defaults: new { controller = "Home", action = "Quote" } ); routes.MapRoute( name: "MyEquipment", url: "MyEquipment/{id}", defaults: new { controller = "MyEquipment", action = "Index" } ); // routes.MapRoute( // name: "PartsSearch", // url: "Parts", // defaults: new { controller = "Home", action = "Parts" } //); routes.MapRoute( name: "MyEquipmentTlj", url: "MyEquipment/Parts/{id}", defaults: new { controller = "MyEquipment", action = "Parts" } ); // routes.MapRoute( // name: "Dashboard", // url: "Dashboard/{id}", // defaults: new { controller = "Dashboard", action = "Index", page = UrlParameter.Optional } //); routes.MapRoute( name: "EquipCat", url: "Equipment/Category/{slug}/{Catid}", defaults: new { controller = "Equipment", action = "Index", page = UrlParameter.Optional } ); routes.MapRoute( name: "EquipTag", url: "Equipment/Tag/{slug}/{Tagid}", defaults: new { controller = "Equipment", action = "Index", page = UrlParameter.Optional } ); routes.MapRoute( name: "EquipPost", url: "Equipment/Post/{id}/{slug}", defaults: new { controller = "Equipment", action = "Post" } ); routes.MapRoute( name: "AddUsers", url: "Users/Create/{CompanyId}/{AddressId}", defaults: new { controller = "Users", action = "Create" } ); routes.MapRoute( name: "BestenAddUsers", url: "UsersManager/Create/{CompanyId}/{AddressId}", defaults: new { controller = "UsersManager", action = "Create" } ); routes.MapRoute( name: "AddMachines", url: "Machine/Add/{CompanyId}/{AddressId}", defaults: new { controller = "Machine", action = "Add" } ); routes.MapRoute( name: "EventManagerAdd", url: "CalendarManager/Add/{companyId}/{addressId}", defaults: new { controller = "CalendarManager", action = "Add" } ); routes.MapRoute( name: "ListAddresses", url: "Addresses/{id}", defaults: new { controller = "Addresses", action = "Index" } ); routes.MapRoute( name: "ListOrders", url: "Orders/{id}", defaults: new { controller = "Orders", action = "Index" } ); routes.MapRoute( name: "UserCreate", url: "Users/create/{CompanyId}/{AddressId}", defaults: new { controller = "Users", action = "Create"} ); routes.MapRoute( name: "NotesAdd", url: "Notes/Add/{CompanyId}/{AddressId}", defaults: new { controller = "Notes", action = "Add" } ); routes.MapRoute( name: "SerialAdd", url: "SerialNumbers/AddSerial/{SerialNumber}/{CompanyId}/{AddressId}", defaults: new { controller = "SerialNumbers", action = "AddSerial" } ); routes.MapRoute( name: "SerialSearch", url: "SerialSearch", defaults: new { controller = "Home", action = "SerialSearch" } ); routes.MapRoute( name: "ExpensesAdd", url: "ExpenseReports/Create/{CompanyId}/{AddressId}", defaults: new { controller = "ExpenseReports", action = "Create" } ); routes.MapRoute( name: "InvoiceList", url: "Invoice/{cmpid}", defaults: new { controller = "Invoice", action = "Index" } ); routes.MapRoute( name: "BInvoiceList", url: "InvoiceManager/{cmpid}", defaults: new { controller = "InvoiceManager", action = "Index", cmpid = UrlParameter.Optional } ); routes.MapRoute( name: "CInvoiceList", url: "InvoiceManager/List/{addid}", defaults: new { controller = "InvoiceManager", action = "List" } ); routes.MapRoute( name: "QuoteList", url: "Quotes/{cmpid}", defaults: new { controller = "Quotes", action = "Index" } ); routes.MapRoute( name: "PurchaseOrderAdd", url: "PurchaseOrder/Add/{id}/{RFQID}", defaults: new { controller = "PurchaseOrder", action = "Add" } ); routes.MapRoute( name: "Subjobs", url: "SubJobs/{id}", defaults: new { controller = "SubJobs", action = "Index" } ); routes.MapRoute( name: "BQuoteList", url: "QuotePdfManager/{cmpid}", defaults: new { controller = "QuotePdfManager", action = "Index", cmpid = UrlParameter.Optional } ); routes.MapRoute( name: "CQuoteList", url: "QuotePdfManager/List/{addid}", defaults: new { controller = "QuotePdfManager", action = "List" } ); routes.MapRoute( name: "ConfirmEmail", url: "Account/SendEmailConfirm/{UserName}", defaults: new { controller = "Account", action = "SendEmailConfirm" } ); routes.MapRoute( name: "ShippingAdd", url: "ShippingOrBOLs/Add/{companyId}/{addressId}/{orderId}", defaults: new { controller = "ShippingOrBOLs", action = "Add" } ); routes.MapRoute( name: "EventAdd", url: "Calendar/{companyId}/{addressId}", defaults: new { controller = "Calendar", action = "Index" } ); routes.MapRoute( name: "MessagesAdd", url: "Messages/Add/{user}", defaults: new { controller = "Messages", action = "Add" } ); routes.MapRoute( name: "PastDue", url: "InvoiceManager/PastDue/{id}/{pastdue}", defaults: new { controller = "InvoiceManager", action = "PastDue" } ); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); }
问题根源与解决方案
最后我终于找到问题所在了:问题出在columnDefs: [{ orderable: false, targets: [2, 3, 4, 5, 6] }]这一行。
当前表格一共6列,列索引是从0开始计数的,有效索引范围是0到5,但这里的targets数组里包含了索引6,超出了有效范围。
- 在非服务端模式下,这种错误只会导致加载时格式异常(比如按钮、搜索框、记录数等不显示),但数据还能加载出来;
- 而在服务端模式下,DataTables在生成请求之前就会因为这个索引错误直接失败,导致请求根本发不到Controller的Action里。
解决方法很简单,把targets数组里的6去掉就行,修改后的代码如下:
columnDefs: [{ orderable: false, targets: [2, 3, 4, 5] }]
之前我一直误以为是路由的问题,走了不少弯路,最后在DataTables相关社区找到了对应的解决方案。
内容的提问来源于stack exchange,提问作者Scott Purtan

