【问题标题】:JSON.Net in Api controllers w/ param containing Dictionary is always null带有包含 Dictionary 的参数的 Api 控制器中的 JSON.Net 始终为空
【发布时间】:2013-05-09 00:22:23
【问题描述】:

我看过一些声称有效的教程,但它们已经过时或根本不起作用。

如何使用 JSON.Net 序列化和反序列化从我的 API 控制器接收和发送的数据?

我们正在使用 VS2012。

更新

我有一个这样的模型

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

还有一个像这样的 Api 控制器

public class ModelSearchApiController : ApiController
{

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

但是,search 提供了在 Ajax 请求中设置的正确值,Terms 属性始终是一个空字典。

我知道我们可以提供像 [ { Key:"foo", Value:123 } ] 这样的值,但为什么我不能只传递一个普通的 JSON 对象(即 { foo:123 })?为什么它可以将Dictionary 序列化为一个很好的标准 JSON 对象,但不能将那个 exact 相同的对象重新创建一个Dictionary。这在我身边。

编辑

换句话说,如果浏览器发送这些参数:

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

所需的对象签名是什么?因为上面提到的对象不起作用,字典只是空的。

【问题讨论】:

  • JSON.NET 是 API 控制器(即派生自 ApiController 的类)的默认序列化程序。你不需要做任何其他事情,它应该可以工作。您有任何具体问题吗?
  • @carlosfigueira,是的。我的一个模型声明了 JsonConverter 并且反序列化器不使用它。这让我假设 Json.net 用于反序列化我的数据。
  • @ckozl,很抱歉告诉你这个,但不管人们怎么说,Json.Net 绝对是 NOT 的默认序列化程序。默认序列化程序为对象提供了一个糟糕的非标准伪 JSON 响应(但仍设法序列化字典)。我只是用我自己的替换它。问题是 它没有从请求中读取任何内容
  • 再次声明,JSON.NET 是 API 控制器的默认序列化程序。如果你定义了一个 JsonConverter,它将被用于 JSON 输入。有关更完整的详细信息,请参阅我的答案。

标签: c# api asp.net-mvc-4 asp.net-web-api json.net


【解决方案1】:

JSON.NET 是 ASP.NET Web API 的默认序列化程序 - 它可以在 JSON 和 CLR 对象之间进行转换,并且对所有 JSON 输入都这样做。但是,您不是在尝试将 JSON 输入转换为 SearchModel - 您是在尝试从类似于 application/x-www-form-urlencoded 的基于 URI 的格式转换为 CLR 类型 SearchModel,并且JSON.NET 不支持(它不是 JSON!)。通常,序列化程序用于(在传入请求时)将 请求正文 转换为操作参数。

让我们看看下面这个(完整的)示例(假设默认路由到"api/{controller}")。这和你的问题很相似,但是我在 GET 方法之外还添加了一个 Post 方法。

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; }
}

如果您将此请求发送到服务器:

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"}}

如您所料,它将绑定到 SearchModel 参数 - Terms 属性将是一个包含两个条目(foo=bar,foo2=bar2)的字典。

现在,对于 GET 参数。 ASP.NET Web API 有一个model bindersvalue provider 的概念,这将是将查询字符串转换为操作参数的组件。默认的 binder / provider 不支持复杂类型中的字典的“任意”名称/值对语法 *。正如您所指出的,您可以使用键/值对语法,这将被理解,如下所示。

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

现在,对于您的问题,您有两种选择。您可以更改您的 API 以使用知道如何理解“简单”名称/值语法的自定义模型绑定器或值提供程序,如下所示:

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;
    }
}

另一种选择是在发送到服务器之前在客户端上预处理您的输入数据,使用类似于下面的函数。

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

【讨论】:

  • 不知道为什么大家都说JSON.Net是默认的JsonFormatter。对我来说,我不得不替换它,因为使用默认格式化程序给出了无效的 JSON 符号 并且 不理解 JSON.Net 的类/属性属性。无论如何,继续前进。我需要具有与模型输入相同的输出(我从服务器得到的应该与我发回给它的相同),因此我只是使用了一个自定义类扩展 Dictionary&lt;string, TModel&gt; 并拥有一个 [JsonArrayAttribute]。这让我拥有了 C# 理解的对象{key, value} 的(丑陋且非标准的)数组。
  • 如果您提出一个解决方案,允许使用您的value provider 将标准 JSON 对象作为 Dictionary 传递给 C#,那么我将检查您的答案作为已接受的解决方案。至于现在,这只是一个 hack,因为我不喜欢这个解决方案(键/值对而不是真正的 JSON 对象)
  • 让我们设置一些术语,我想我们在谈论不同的事情。 JSON 是一种有线格式。 “JSON 对象”的格式如下:{"name":&lt;value&gt;},其中&lt;value&gt; 可以是字符串、数字、布尔值、null 或其他 JSON 对象或数组。 GET 操作接收的输入不是 JSON 对象。它是将 JavaScript 对象转换为名称/值对表示法...
  • 我很想看看你说默认格式化程序生成无效 JSON 的例子 - 你能在 Outlook dot com 上向 carlosfigueira 发送一个例子吗?如果是这种情况,应该是框架中的错误,我认识一些在 ASP.NET 团队工作的人,我很乐意将其传递给他们。
【解决方案2】:

您可以参考下面的link。希望对您有所帮助。

这里是sample 使用 Json.net 和 Web API。

【讨论】:

  • 您提供的链接不起作用,或者至少似乎不起作用。它引用了 VS2012 中不再存在的东西。
  • 好吧,我设法通过替换一些方法签名使其工作,但反序列化器似乎不起作用。我将更新问题以反映我正在尝试做的事情
猜你喜欢
  • 2016-05-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-18
  • 2023-03-05
  • 1970-01-01
相关资源
最近更新 更多