Yanick Rochon
Yanick Rochon

Reputation: 53626

JSON.Net in Api controllers w/ param containing Dictionary is always null

I've seen some tutorials out there that claim to work, but they are outdated or simply do not work.

How can I use JSON.Net to serialize and deserialize the data received to and sent from my API controllers?

We are using VS2012.

Update

I have a model like this

public class SearchModel
{
    public int PageIndex { get; set; }
    public int PageSize { get; set; }
    public Dictionary<string, object> Terms { get; set; }
}

And an Api controller like this

public class ModelSearchApiController : ApiController
{

     public List<Model> Get([FromUri] SearchModel search)
     {
         return new List<Model>();
     }
}

However, search provides the correct value set in the Ajax request, the property Terms is always an empty dictionary.

I know we can provide a value like [ { Key:"foo", Value:123 } ] but why can't I just pass a normal JSON object (ie { foo:123 }) ??? Why can it serialize a Dictionary into a nice standard JSON object, but cannot take that exact same object and recreate a Dictionary. This is beyound me.

Edit

In other words, if the browser sends these arguments :

 pageIndex: 0
 pageSize: 100
 terms[foo]: Bar
 terms[buz]: 1234

What would be the required object signature? Because the object mentionned above does not work and the dictionary is just empty.

Upvotes: 3

Views: 7347

Answers (2)

carlosfigueira
carlosfigueira

Reputation: 87323

JSON.NET is the default serializer for ASP.NET Web API - it can convert between JSON and CLR objects, and does so for all JSON input. However, you're not trying to convert a JSON input to your SearchModel - you're trying to convert from the URI-based format which is similar to application/x-www-form-urlencoded, into the CLR type SearchModel, and that is not supported by JSON.NET (it's not JSON!). In general, the serializers are used to convert (on incoming requests) from the request body to the action parameter.

Let's look at this (complete) example below (assuming the default route, to "api/{controller}"). It's very similar to your question, but I also added a Post method in addition to the GET method.

public class ModelSearchApiController : ApiController
{
    public List<Model> Get([FromUri] SearchModel search)
    {
        return new List<Model>
        {
            new Model { PageIndex = search.PageIndex, PageSize = search.PageSize, Terms = search.Terms }
        };
    }

    public List<Model> Post(SearchModel search)
    {
        return new List<Model>
        {
            new Model { PageIndex = search.PageIndex, PageSize = search.PageSize, Terms = search.Terms }
        };
    }
}

public class Model
{
    public int PageIndex { get; set; }
    public int PageSize { get; set; }
    public Dictionary<string, object> Terms { get; set; }
}

public class SearchModel
{
    public int PageIndex { get; set; }
    public int PageSize { get; set; }
    public Dictionary<string, object> Terms { get; set; }
}

If you send this request to the server:

POST http://localhost:64699/api/ModelSearchApi HTTP/1.1
User-Agent: Fiddler
Host: localhost:64699
Content-Type: application/json
Content-Length: 65

{"PageIndex":1,"PageSize":10,"Terms":{"foo":"bar","foo2":"bar2"}}

It will be bound, as you expect, to the SearchModel parameter - the Terms property will be a dictionary with two entries (foo=bar, foo2=bar2).

Now, for the GET parameter. ASP.NET Web API has a concept of model binders and value provider, which would be the component which would convert between the query string into the action parameters. The default binder / provider do not support the "arbitrary" name/value pair syntax *for dictionary inside complex types. You can, as you pointed out, use the key/value pair syntax, and that will be understood, as shown below.

GET http://localhost:64699/api/ModelSearchApi?PageIndex=1&PageSize=10&Terms[0][key]=foo&Terms[0][value]=bar HTTP/1.1
User-Agent: Fiddler
Host: localhost:64699

Now, for your problem you have two options. You can change your API to use a custom model binder or value provider which knows how to understand the "simple" name/value syntax, as shown below:

public class ModelSearchApiController : ApiController
{
    public List<Model> Get([ModelBinder(typeof(MySearchModelBinder))] SearchModel search)
    {
        return new List<Model>
        {
            new Model { PageIndex = search.PageIndex, PageSize = search.PageSize, Terms = search.Terms }
        };
    }
}

public class MySearchModelBinder : IModelBinder
{
    public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
    {
        SearchModel value = new SearchModel();
        value.Terms = new Dictionary<string,object>();
        foreach (var queryParams in actionContext.Request.GetQueryNameValuePairs())
        {
            if (queryParams.Key == "PageIndex")
            {
                value.PageIndex = int.Parse(queryParams.Value);
            }
            else if (queryParams.Key == "PageSize")
            {
                value.PageSize = int.Parse(queryParams.Value);
            }
            else if (queryParams.Key.StartsWith("Terms."))
            {
                value.Terms.Add(queryParams.Key.Substring("Terms.".Length), queryParams.Value);
            }
        }

        bindingContext.Model = value;
        return true;
    }
}

Another option is to pre-process your input data on the client prior to sending to the server, using a function similar to the one below.

function objToKVPArray(obj) {
    var result = [];
    var k;
    for (k in obj) {
        if (obj.hasOwnProperty(k)) {
            result.push({ key: k, value: obj[k] });
        }
    }
    return result;
}

Upvotes: 7

Toan Vo
Toan Vo

Reputation: 1308

You can take reference the link below. Hope this help.

And here is sample using Json.net with web API.

Upvotes: 0

Related Questions