3

I am using Kendo UI's DataSource to send sorting information to my ServiceStack service. I know that this has been addressed if you're using using POST, but I want to adhere to RESTful designs, so the request verb needs to be GET. I'm using this snippet to test with:

var dataSource = new kendo.data.DataSource({
        serverSorting: true,
        sort: [{ field: "ProductName", dir: "desc" },
               { field: "Category", dir: "asc"}],

        serverPaging: true,
        page: 2,
        pageSize: 5,

        transport: {

            read: {
                url: "/products",
                dataType: "json",
                contentType: "application/json",
                dataType: "jsonp"
            },

            parameterMap: function (data, type) {
                //return kendo.stringify(data);
                //return JSON.stringify(data);
                //return $.param(data, true);
                //data.sort = kendo.stringify(data.sort);
                return data;
            }
        }
    });

    dataSource.fetch(function () {
        console.log(dataSource.view());
    });

The sorting parameters get turned into a jagged array like:

sort[0][field]: ProductName
sort[0][dir]: desc
sort[1][field]: Category
sort[1][dir]: asc

My request DTO is:

    public class SortTerm
{
    public string field { get; set; }
    public string dir { get; set; }
}

public class KendoQuery
{

    public List<SortTerm> Sort { get; set; }

    public int Skip { get; set; }
    public int Take { get; set; }
    public int Page { get; set; }
    public int PageSize { get; set; }
}

All of the simple parameters get deserialized, but how on earth can I transform the Sort property, either client-side or server-side, to have it populate correctly?

Notice that I've tried a variety of serialization techniques in the parameterMap function and I'm completely stumped.

EDIT

So this all boils down to: How do I pass an array of objects via $.get() to a ServiceStack service, when jQuery thoughtfully rearranges my request into a jagged array? This stinks of a request filter, but I have to imagine its been solved before for **GET** requests.

Community
  • 1
  • 1
jklemmack
  • 3,518
  • 3
  • 30
  • 56

2 Answers2

5

You cannot use complex data structures, such as that of the jagged sorting array, with a GET request using standard techniques because JSON GET requests only support simple variables as these can be translated to request parameters in the query string. So the request will form correctly for the simple parameters ?Page=2&PageSize=5 ... but for sort[0][field] this cannot be specified as a query string request parameter.

However you could work around this if you where to stringify your search criteria object using datasource.transport.parameterMap so that it can be passed as a parameter, which is then converted at the server.

parameterMap: function (data, type) {
    // Convert the search criteria to a JSON string and store it on value sortJson
    data.sortJson = JSON.stringify(data.sort);

    // Remove the sort value, as this will be provided by sortJson
    delete data.sort;

    // Return the data
    return data
}

At the server you will need to handle converting the JSON string to the List<SortTerm>:

public class KendoQuery
{

    List<SortTerm> sort;
    public List<SortTerm> Sort {
         get {
             // Handles deserialising the SortJson value 
             if(sort == null)
                 sort = ServiceStack.JsonSerializer.DeserializeFromString<List<SortTerm>>(SortJson);
             return sort;
         }
    }

    public string SortJson { get; set; } 
    public int Skip { get; set; }
    public int Take { get; set; }
    public int Page { get; set; }
    public int PageSize { get; set; }
}

I hope this helps.

Air
  • 8,274
  • 2
  • 53
  • 88
Scott
  • 21,211
  • 8
  • 65
  • 72
  • The overall reason I was wiffing out was, as you said, that GET just can't reasonably pass complex data structures. Though I said either client- or server-side fix would be fine, I am preferring server-side to keep the client-side code clean, and back-end agnostic. I've posted my code in the answer below. – jklemmack Jul 18 '14 at 20:48
  • @jklemmack Yeah that's reasonable about keeping the client side clean where possible so your solution looks appropriate too (+1). – Scott Jul 18 '14 at 20:57
3

Scott's answer above is the right one, but here is my more server-centric solution. Basically, I manually reconstruct the SortTerm object from the query string. This is extendable to the other Kendo-specific parameters, like Filters & Groups.

Client-code:

<html>
<head>
    <meta charset="utf-8">
    <title>Kendo UI Snippet</title>

    <script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
    <script src="http://cdn.kendostatic.com/2014.1.528/js/kendo.all.min.js"></script>
</head>
<body>
    <script>
        var dataSource = new kendo.data.DataSource({
            serverSorting: true,
            sort: [{ field: "ProductName", dir: "desc" },
                   { field: "Category", dir: "asc" }],

            serverPaging: true,
            page: 2,
            pageSize: 5,

            transport: {
                read: {
                    url: "/products",
                    dataType: "json"
                }
            }
        });

        dataSource.fetch(function () {
            console.log(dataSource.view());
        });
    </script>
</body>
</html>

Server code (in AppHost implementation):

this.GlobalRequestFilters.Add((req, resp, dto) =>
{
    if (dto is KendoQueryBase)
    {
        KendoQueryBase qb = dto as KendoQueryBase;
        if (qb.Sort == null) qb.Sort = new List<SortTerm>();
        Dictionary<string, string> qs = req.QueryString.ToDictionary();
        var i = 0;
        while (qs.ContainsKey("sort[{0}][field]".Fmt(i)))
        {
            qb.Sort.Add(new SortTerm()
            {
                field = qs["sort[{0}][field]".Fmt(i)],
                dir = qs["sort[{0}][dir]".Fmt(i)]
            });
            i++;
        }
    }
});
jklemmack
  • 3,518
  • 3
  • 30
  • 56