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;。您如何使用此信息和实现后端数据访问与此处无关。

重构您的操作方法、模型绑定和参数数据类型应该可以解决您的问题。虽然我是根据经验写这篇文章的,但实际上并没有在我的机器上测试过你的代码。