Web Api 抛出 500 错误
Web Api is throwing a 500 error
我有一个 Javascript Kendo 网格,我正在尝试使用 MVC Web Api 进行服务器端排序。除排序外,一切正常。这是 HTML 代码:
<script type="text/javascript">
$(function() {
$("#gridTrades").kendoGrid({
sortable: true,
columns: [{ field: "tradeName", title: "Name", width: "250px" }, { field: "isActive", title: "Active", template: '#= isActive ? "<i class=\'fa fa-check\'></i>" : "" #', width: "100px" }],
dataSource: {
serverPaging: true,
serverFiltering: false,
serverSorting: true,
pageSize: 3,
schema: {
data: "data",
total: "total",
model: {
fields: {
id: { editable: false, type: "number" },
tradeName: { editable: false, type: "string" },
isActive: { editable: false, type: "boolean"}
}
}
},
batch: false,
transport: {
read: {
url: "/api/v1/Trades"
}
}
}
});
});
</script>
这是我的网站 Api 代码:
[System.Web.Http.HttpGet]
[System.Web.Http.Route("api/v1/Trades")]
public IHttpActionResult GetTrades(int page = -1, int pageSize = -1, int skip = -1, int take = -1, string[][] sort = null)
{
var trades = new BusinessLayer.VipScheduler.Trades();
var total = BusinessLayer.VipScheduler.LoadData.FromSqlStatement<Models.Settings.GetTradesReturnModel>($"SELECT COUNT(*) AS Total FROM {BusinessLayer.VipScheduler.Trade.LgTableName}");
trades.GetAll();
var retval = new Models.Settings.GetTradesReturnModel {Total = total[0].Total};
foreach (var trade in trades.OrderByDescending(z => z.IsActive).ThenBy(z => z.TradeName))
{
retval.Data.Add(new Models.Settings.GetTradesReturnModelData
{
Id = trade.TradeId,
IsActive = trade.IsActive,
TradeName = trade.TradeName
});
}
return Ok(retval);
}
我 运行 遇到的问题是排序参数。如果我完全取消排序参数,Kendo 网格工作正常,但排序不起作用。如果我像上面那样保留排序参数,我会收到 500 错误:
Optional parameter 'sort' is not supported by 'FormatterParameterBinding'.
这是请求 Kendo 网格正在发送:
GET /api/v1/Trades?take=3&skip=0&page=1&pageSize=3&sort%5B0%5D%5Bfield%5D=tradeName&sort%5B0%5D%5Bdir%5D=asc
如能提供有关使此排序参数正常工作的任何帮助,我们将不胜感激!
为了防止您现在遇到的错误 - 只需将 sort
参数设为非可选(将其移至参数列表的开头并删除 = null
)。如果绑定失败(例如,查询字符串中未指定排序)- 无论如何它将具有 null
值,因此在这种情况下没有理由使用 = null
默认值。
现在,您的排序规范分为多个查询字符串参数:
sort[0][field]=tradeName&sort[0][dir]=asc
所以要将它绑定到您的模型,您将需要一个自定义模型活页夹。首先创建class表示排序规范:
public class KendoSortSpecifier {
public string Field { get; set; }
public string Direction { get; set; }
}
然后自定义model binder(这里只是举例,需要的话根据自己的需要调整):
public class KendoSortSpecifierBinder : System.Web.Http.ModelBinding.IModelBinder {
private static readonly Regex _sortFieldMatcher;
private static readonly Regex _sortDirMatcher;
const int MaxSortSpecifiers = 5;
static KendoSortSpecifierBinder() {
_sortFieldMatcher = new Regex(@"^sort\[(?<index>\d+)\](\[field\]|\.field)$", RegexOptions.Singleline | RegexOptions.Compiled);
_sortDirMatcher = new Regex(@"^sort\[(?<index>\d+)\](\[dir\]|\.dir)$", RegexOptions.Singleline | RegexOptions.Compiled);
}
public bool BindModel(HttpActionContext actionContext, System.Web.Http.ModelBinding.ModelBindingContext bindingContext) {
if (bindingContext.ModelType != typeof(KendoSortSpecifier[]))
return false;
var request = actionContext.Request;
var queryString = request.GetQueryNameValuePairs();
var result = new List<KendoSortSpecifier>();
foreach (var kv in queryString) {
var match = _sortFieldMatcher.Match(kv.Key);
if (match.Success) {
var index = int.Parse(match.Groups["index"].Value);
if (index >= MaxSortSpecifiers)
continue;
while (result.Count <= index) {
result.Add(new KendoSortSpecifier());
}
result[index].Field = kv.Value;
}
else {
match = _sortDirMatcher.Match(kv.Key);
if (match.Success) {
var index = int.Parse(match.Groups["index"].Value);
if (index >= MaxSortSpecifiers)
continue;
while (result.Count <= index) {
result.Add(new KendoSortSpecifier());
}
result[index].Direction = kv.Value;
}
}
}
bindingContext.Model = result.Where(c => !String.IsNullOrEmpty(c.Field)).ToArray();
return true;
}
}
最后是你的控制器方法签名:
public IHttpActionResult GetTrades(
[System.Web.Http.ModelBinding.ModelBinder(typeof(KendoSortSpecifierBinder))]
KendoSortSpecifier[] sort,
int page = -1,
int pageSize = -1,
int skip = -1,
int take = -1)
现在你已经有了你的排序的强类型模型(除了 Direction
可以用枚举而不是字符串表示)并且可以根据需要使用它来排序你的数据。
我建议您重构 API 方法。不要向它提供路由参数,而是使用这样的 DTO:
public IHttpActionResult GetTrades(GetTradesRequestDTO model)
{
// method body
}
无论如何,这是最佳做法,并且模型绑定问题已最小化。另外我不确定你为什么要给你的参数默认值 -1 。而是使用从零开始的索引。这可能是个人喜好,但我不确定这些负值会带来什么。最后,您的排序参数也需要重构。它可以是字符串列表,或强类型模型的集合,每个模型都定义一个排序操作。即 List<SortAction> sort;
。您如何使用此信息和实现后端数据访问与此处无关。
重构您的操作方法、模型绑定和参数数据类型应该可以解决您的问题。虽然我是根据经验写这篇文章的,但实际上并没有在我的机器上测试过你的代码。
我有一个 Javascript Kendo 网格,我正在尝试使用 MVC Web Api 进行服务器端排序。除排序外,一切正常。这是 HTML 代码:
<script type="text/javascript">
$(function() {
$("#gridTrades").kendoGrid({
sortable: true,
columns: [{ field: "tradeName", title: "Name", width: "250px" }, { field: "isActive", title: "Active", template: '#= isActive ? "<i class=\'fa fa-check\'></i>" : "" #', width: "100px" }],
dataSource: {
serverPaging: true,
serverFiltering: false,
serverSorting: true,
pageSize: 3,
schema: {
data: "data",
total: "total",
model: {
fields: {
id: { editable: false, type: "number" },
tradeName: { editable: false, type: "string" },
isActive: { editable: false, type: "boolean"}
}
}
},
batch: false,
transport: {
read: {
url: "/api/v1/Trades"
}
}
}
});
});
</script>
这是我的网站 Api 代码:
[System.Web.Http.HttpGet]
[System.Web.Http.Route("api/v1/Trades")]
public IHttpActionResult GetTrades(int page = -1, int pageSize = -1, int skip = -1, int take = -1, string[][] sort = null)
{
var trades = new BusinessLayer.VipScheduler.Trades();
var total = BusinessLayer.VipScheduler.LoadData.FromSqlStatement<Models.Settings.GetTradesReturnModel>($"SELECT COUNT(*) AS Total FROM {BusinessLayer.VipScheduler.Trade.LgTableName}");
trades.GetAll();
var retval = new Models.Settings.GetTradesReturnModel {Total = total[0].Total};
foreach (var trade in trades.OrderByDescending(z => z.IsActive).ThenBy(z => z.TradeName))
{
retval.Data.Add(new Models.Settings.GetTradesReturnModelData
{
Id = trade.TradeId,
IsActive = trade.IsActive,
TradeName = trade.TradeName
});
}
return Ok(retval);
}
我 运行 遇到的问题是排序参数。如果我完全取消排序参数,Kendo 网格工作正常,但排序不起作用。如果我像上面那样保留排序参数,我会收到 500 错误:
Optional parameter 'sort' is not supported by 'FormatterParameterBinding'.
这是请求 Kendo 网格正在发送:
GET /api/v1/Trades?take=3&skip=0&page=1&pageSize=3&sort%5B0%5D%5Bfield%5D=tradeName&sort%5B0%5D%5Bdir%5D=asc
如能提供有关使此排序参数正常工作的任何帮助,我们将不胜感激!
为了防止您现在遇到的错误 - 只需将 sort
参数设为非可选(将其移至参数列表的开头并删除 = null
)。如果绑定失败(例如,查询字符串中未指定排序)- 无论如何它将具有 null
值,因此在这种情况下没有理由使用 = null
默认值。
现在,您的排序规范分为多个查询字符串参数:
sort[0][field]=tradeName&sort[0][dir]=asc
所以要将它绑定到您的模型,您将需要一个自定义模型活页夹。首先创建class表示排序规范:
public class KendoSortSpecifier {
public string Field { get; set; }
public string Direction { get; set; }
}
然后自定义model binder(这里只是举例,需要的话根据自己的需要调整):
public class KendoSortSpecifierBinder : System.Web.Http.ModelBinding.IModelBinder {
private static readonly Regex _sortFieldMatcher;
private static readonly Regex _sortDirMatcher;
const int MaxSortSpecifiers = 5;
static KendoSortSpecifierBinder() {
_sortFieldMatcher = new Regex(@"^sort\[(?<index>\d+)\](\[field\]|\.field)$", RegexOptions.Singleline | RegexOptions.Compiled);
_sortDirMatcher = new Regex(@"^sort\[(?<index>\d+)\](\[dir\]|\.dir)$", RegexOptions.Singleline | RegexOptions.Compiled);
}
public bool BindModel(HttpActionContext actionContext, System.Web.Http.ModelBinding.ModelBindingContext bindingContext) {
if (bindingContext.ModelType != typeof(KendoSortSpecifier[]))
return false;
var request = actionContext.Request;
var queryString = request.GetQueryNameValuePairs();
var result = new List<KendoSortSpecifier>();
foreach (var kv in queryString) {
var match = _sortFieldMatcher.Match(kv.Key);
if (match.Success) {
var index = int.Parse(match.Groups["index"].Value);
if (index >= MaxSortSpecifiers)
continue;
while (result.Count <= index) {
result.Add(new KendoSortSpecifier());
}
result[index].Field = kv.Value;
}
else {
match = _sortDirMatcher.Match(kv.Key);
if (match.Success) {
var index = int.Parse(match.Groups["index"].Value);
if (index >= MaxSortSpecifiers)
continue;
while (result.Count <= index) {
result.Add(new KendoSortSpecifier());
}
result[index].Direction = kv.Value;
}
}
}
bindingContext.Model = result.Where(c => !String.IsNullOrEmpty(c.Field)).ToArray();
return true;
}
}
最后是你的控制器方法签名:
public IHttpActionResult GetTrades(
[System.Web.Http.ModelBinding.ModelBinder(typeof(KendoSortSpecifierBinder))]
KendoSortSpecifier[] sort,
int page = -1,
int pageSize = -1,
int skip = -1,
int take = -1)
现在你已经有了你的排序的强类型模型(除了 Direction
可以用枚举而不是字符串表示)并且可以根据需要使用它来排序你的数据。
我建议您重构 API 方法。不要向它提供路由参数,而是使用这样的 DTO:
public IHttpActionResult GetTrades(GetTradesRequestDTO model)
{
// method body
}
无论如何,这是最佳做法,并且模型绑定问题已最小化。另外我不确定你为什么要给你的参数默认值 -1 。而是使用从零开始的索引。这可能是个人喜好,但我不确定这些负值会带来什么。最后,您的排序参数也需要重构。它可以是字符串列表,或强类型模型的集合,每个模型都定义一个排序操作。即 List<SortAction> sort;
。您如何使用此信息和实现后端数据访问与此处无关。
重构您的操作方法、模型绑定和参数数据类型应该可以解决您的问题。虽然我是根据经验写这篇文章的,但实际上并没有在我的机器上测试过你的代码。