在MVC 3中针对AJAX请求反序列化Dictionarys的问题(一种开箱即用的经典ASP.NET Webforms方法)

时间:2022-04-07 05:25:33

I've been successfully WebForms for AJAX calls with relatively complex set of parameters (called using jQuery.ajax). We're attempting to try using the same approach in MVC 3 but seem to be falling at the first hurdle with MVC failing to deserialize Dictionary arrays successfully.

我使用相对复杂的参数集(使用jQuery.ajax调用)成功地为AJAX调用创建了WebForms。我们试图尝试在MVC 3中使用相同的方法,但似乎在MVC未能成功反序列化数组的第一个障碍。

The approach that works without issue in ASP.NET WebForms "classic" is below:

ASP.NET WebForms“经典”中没有问题的方法如下:

[WebMethod]
public static JQGrid.JQGridData GetListForJQGrid(int? iPageSize, int? iPage, int? iMaxRecords, string sSortField, string sSortOrder,
  Dictionary<string, string> dSearchOptions, Dictionary<string, object>[] aOriginalColumnDefinition, string[] aExtraDataColumns)

And below is the MVC 3 equivalent: (nb exactly the same name/parameters - different return type but I don't think that is relevant)

以下是MVC 3等价物:( nb完全相同的名称/参数 - 不同的返回类型,但我不认为这是相关的)

[HttpPost]
public JSONResult GetListForJQGrid(int? iPageSize, int? iPage, int? iMaxRecords, string sSortField, string sSortOrder,
  Dictionary<string, string> dSearchOptions, Dictionary<string, object>[] aOriginalColumnDefinition, string[] aExtraDataColumns)

With the WebMethod all the data deserializes perfectly. However, when the MVC method is called all the simple parameters deserialize fine but for some unknown reason the array of Dictionary's arrives as an array of nulls.

使用WebMethod,所有数据都可以完美地反序列化。但是,当调用MVC方法时,所有简单参数都会反序列化,但由于某些未知原因,Dictionary的数组作为空数组到达。

So, off the back of that a number of questions:

所以,在一些问题的背后:

  • Has anyone else experienced problems with MVC 3 deserialization of arrays of dictionaries?
  • 有没有其他人遇到过字典数组的MVC 3反序列化问题?

  • Does MVC 3 by default not use System.Web.Script.Serialization.JavaScriptSerializer which is I think what ASP.NET WebMethods use under the bonnet?
  • MVC 3默认情况下不使用System.Web.Script.Serialization.JavaScriptSerializer,我认为ASP.NET WebMethods在引擎盖下使用了什么?

  • Can I force MVC 3 to use System.Web.Script.Serialization.JavaScriptSerializer instead of what it is using?
  • 我可以强制MVC 3使用System.Web.Script.Serialization.JavaScriptSerializer而不是它使用的是什么?

  • Or am I missing something / should my approach be slightly different? Please note that at least for now we'll need to share the client side code between classic ASP.NET WebMethods and MVC 3 and so we want that to remain as is if possible.
  • 或者我错过了什么/我的方法应该略有不同?请注意,至少目前我们需要在经典的ASP.NET WebMethods和MVC 3之间共享客户端代码,因此我们希望尽可能保持原样。

  • Finally, I can see there is a possible workaround that could be used looking at this question: POST json dictionary . Is this workaround the only game in town or have things improved since this question was posed?
  • 最后,我可以看到有一种可能的解决方法可以用来查看这个问题:POST json字典。这个解决方案是镇上唯一的游戏还是因为提出了这个问题而改进了?

jQuery AJAX call:

jQuery AJAX调用:

$.ajax(_oJQGProperties.sURL, //URL of WebService/PageMethod used
{
  data: JSON.stringify(oPostData),
  type: "POST",
  contentType: "application/json",
  complete: DataCallback
});

Example JSON.stringify(oPostData):

{
"dSearchOptions":{},
"aOriginalColumnDefinition":
[
{"name":"ID","sortable":false,"hidedlg":true,"align":"right","title":false,"width":40},
{"name":"URL","sortable":false,"hidedlg":true,"align":"left","title":false,"width":250,"link":"javascript:DoSummat(this,'{0}');","textfield":"Name"},
{"name":"Description","sortable":false,"hidedlg":true,"align":"left","title":false,"width":620}
],
"aExtraDataColumns":["Name"],
"_search":false,
"iPageSize":-1,
"iPage":1,
"sSortField":"",
"sSortOrder":"",
"iMaxRecords":0
}

3 个解决方案

#1


1  

I don't have any experience with binding to a dictionary array, but one possible solution is to use a custom model binder. Scott Hanselman has a blog post on this subject that you might find useful: Splitting DateTime - Unit Testing ASP.NET MVC Custom Model.

我没有任何绑定到字典数组的经验,但一种可能的解决方案是使用自定义模型绑定器。 Scott Hanselman有一篇关于这个主题的博客文章,您可能会发现它很有用:拆分DateTime - 单元测试ASP.NET MVC自定义模型。

#2


1  

Long time getting to update this but I thought I'd share where we got to. The problem turned out to be a bug - details of which can be found here:

很长时间来更新这个,但我想我会分享我们到达的地方。问题结果证明是一个错误 - 详细信息可以在这里找到:

Bug: http://connect.microsoft.com/VisualStudio/feedback/details/636647/make-jsonvalueproviderfactory-work-with-dictionary-types-in-asp-net-mvc

Workaround: POST json dictionary

解决方法:POST json字典

We used the stated workaround which has been fine. I'm not too clear as to when the fix will be shipped and where exactly the bug lay. (Is it .NET dependant / MVC dependant etc) If anyone else knows I'd love to find out :-)

我们使用了陈述的解决方法,这很好。我不太清楚修补程序什么时候发货以及错误发生的地方。 (依赖.NET依赖/ MVC等)如果有人知道我很想知道:-)

Update

I haven't heard still if this is shipped (I assume it goes out with MVC 4?) but in the interim this may be an alternative solution:

如果发货,我还没有听说过(我认为它与MVC 4一起发布?)但在此期间,这可能是另一种解决方案:

http://www.dalsoft.co.uk/blog/index.php/2012/01/10/asp-net-mvc-3-improved-jsonvalueproviderfactory-using-json-net/

Update 2

This has now been shipped as a fix with MVC 4. The issue remains unresolved in MVC 3 and so I've now written it up as a blog post here:

这已经作为MVC 4的修复程序发布了。这个问题在MVC 3中仍然没有解决,所以我现在把它写成博客文章:

http://icanmakethiswork.blogspot.com/2012/10/mvc-3-meet-dictionary.html

#3


1  

I ran into this issue too. After finding this SO post, I thought about upgrading to MVC4, but it's too risky to do all at once in my environment so scratch that.

我也遇到过这个问题。在找到这篇SO帖子之后,我想到了升级到MVC4,但是在我的环境中一次性完成这一过程太冒险了。

This link posted in Johnny Reilly's answer looked promising, but it required flattening my dictionary to a string. Because my MVC model is bidirectional (it's used for reads and writes), and I really wanted that dictionary structure I decided to pass on that too. It would have been a real pain to keep two properties for one value. I would have needed to add more tests, watch out for edge cases, etc.

在Johnny Reilly的回答中发布的这个链接看起来很有希望,但它需要将我的字典扁平化为字符串。因为我的MVC模型是双向的(它用于读取和写入),我真的想要那个字典结构,我决定也传递它。为一个值保留两个属性真的很痛苦。我需要添加更多测试,注意边缘情况等。

Johnny's JsonValueProviderFactory link seemed promising too, but a bit arcane. I'm also not entirely comfortable monkeying around with a part of MVC like that. I had only a few hours to figure this problem out so I passed on this too.

约翰尼的JsonValueProviderFactory链接似乎也很有希望,但有点神秘。我也不太喜欢那样使用MVC的一部分。我只有几个小时的时间来解决这个问题,所以我也传递了这个问题。

Then I found this link somewhere, and thought "Yes! this is more like what I want!". In other words attack the model binding problem by using a custom binder. Replace the buggy one with something else, and use MVC's built-in capability to do so. Unfortunately, this did not work as my use case was List of T, and T was my model. This totally did not work with the sample. So I hacked away at it and ultimately failed.

然后我在某个地方找到了这个链接,并想“是的!这更像我想要的!”。换句话说,通过使用自定义绑定器来攻击模型绑定问题。用其他东西替换有缺陷的那个,并使用MVC的内置功能来实现。不幸的是,这不起作用,因为我的用例是T的列表,而T是我的模型。这完全不适用于样本。所以我砍掉了它,最终失败了。

Then, I got a lightbulb moment - JSON.NET does not have this problem. I use it all the time for doing all sorts of things, from cloning objects, to logging, to REST service endpoints. Why not model binding? So I ultimately ended up with this and my problem was solved. I think it should work with just about anything - I trust JSON.NET =)

然后,我得到了一个灯泡时刻--JSON.NET没有这个问题。我一直用它来做各种各样的事情,从克隆对象到日志记录,再到REST服务端点。为什么不绑定模型?所以我最终得到了这个,我的问题解决了。我认为它应该适用于任何事情 - 我相信JSON.NET =)

/// <summary>
/// Custom binder that maps JSON data in the request body to a model class using JSON.NET.
/// </summary>
/// <typeparam name="T">Model type being bound</typeparam>
/// <remarks>
/// This binder is very useful when your MVC3 model contains dictionaries, something that it can't map (this is a known bug, fixed with MVC 4)
/// </remarks>
public class CustomJsonModelBinder<T> : DefaultModelBinder
    where T : class
{
    /// <summary>
    /// Binds the model by using the specified controller context and binding context.
    /// </summary>
    /// <returns>
    /// The bound object.
    /// </returns>
    /// <param name="controllerContext">The context within which the controller operates. The context information includes the controller, HTTP content, request context, and route data.</param><param name="bindingContext">The context within which the model is bound. The context includes information such as the model object, model name, model type, property filter, and value provider.</param><exception cref="T:System.ArgumentNullException">The <paramref name="bindingContext "/>parameter is null.</exception>
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        HttpRequestBase request = controllerContext.HttpContext.Request;
        request.InputStream.Position = 0;
        var input = new StreamReader(request.InputStream).ReadToEnd();
        T modelObject = JsonConvert.DeserializeObject<T>(input);
        return modelObject;
    }
}

To apply the binder, I added an attribute to my model parameter. This causes MVC3 to use my binder instead of the default. Something like this:

为了应用活页夹,我在模型参数中添加了一个属性。这会导致MVC3使用我的活页夹而不是默认值。像这样的东西:

public ActionResult SomeAction(
    [ModelBinder(typeof(CustomJsonModelBinder<List<MyModel>>))] // This custom binder works around a known dictionary binding bug in MVC3
    List<MyModel> myModelList, int someId)
    {

One caveat - I was using POST with content type "application/json". If you're doing something like form or multipart data instead it will probably crash horribly.

一个警告 - 我使用内容类型为“application / json”的POST。如果你正在做类似表格或多部分数据的事情,它可能会崩溃。

#1


1  

I don't have any experience with binding to a dictionary array, but one possible solution is to use a custom model binder. Scott Hanselman has a blog post on this subject that you might find useful: Splitting DateTime - Unit Testing ASP.NET MVC Custom Model.

我没有任何绑定到字典数组的经验,但一种可能的解决方案是使用自定义模型绑定器。 Scott Hanselman有一篇关于这个主题的博客文章,您可能会发现它很有用:拆分DateTime - 单元测试ASP.NET MVC自定义模型。

#2


1  

Long time getting to update this but I thought I'd share where we got to. The problem turned out to be a bug - details of which can be found here:

很长时间来更新这个,但我想我会分享我们到达的地方。问题结果证明是一个错误 - 详细信息可以在这里找到:

Bug: http://connect.microsoft.com/VisualStudio/feedback/details/636647/make-jsonvalueproviderfactory-work-with-dictionary-types-in-asp-net-mvc

Workaround: POST json dictionary

解决方法:POST json字典

We used the stated workaround which has been fine. I'm not too clear as to when the fix will be shipped and where exactly the bug lay. (Is it .NET dependant / MVC dependant etc) If anyone else knows I'd love to find out :-)

我们使用了陈述的解决方法,这很好。我不太清楚修补程序什么时候发货以及错误发生的地方。 (依赖.NET依赖/ MVC等)如果有人知道我很想知道:-)

Update

I haven't heard still if this is shipped (I assume it goes out with MVC 4?) but in the interim this may be an alternative solution:

如果发货,我还没有听说过(我认为它与MVC 4一起发布?)但在此期间,这可能是另一种解决方案:

http://www.dalsoft.co.uk/blog/index.php/2012/01/10/asp-net-mvc-3-improved-jsonvalueproviderfactory-using-json-net/

Update 2

This has now been shipped as a fix with MVC 4. The issue remains unresolved in MVC 3 and so I've now written it up as a blog post here:

这已经作为MVC 4的修复程序发布了。这个问题在MVC 3中仍然没有解决,所以我现在把它写成博客文章:

http://icanmakethiswork.blogspot.com/2012/10/mvc-3-meet-dictionary.html

#3


1  

I ran into this issue too. After finding this SO post, I thought about upgrading to MVC4, but it's too risky to do all at once in my environment so scratch that.

我也遇到过这个问题。在找到这篇SO帖子之后,我想到了升级到MVC4,但是在我的环境中一次性完成这一过程太冒险了。

This link posted in Johnny Reilly's answer looked promising, but it required flattening my dictionary to a string. Because my MVC model is bidirectional (it's used for reads and writes), and I really wanted that dictionary structure I decided to pass on that too. It would have been a real pain to keep two properties for one value. I would have needed to add more tests, watch out for edge cases, etc.

在Johnny Reilly的回答中发布的这个链接看起来很有希望,但它需要将我的字典扁平化为字符串。因为我的MVC模型是双向的(它用于读取和写入),我真的想要那个字典结构,我决定也传递它。为一个值保留两个属性真的很痛苦。我需要添加更多测试,注意边缘情况等。

Johnny's JsonValueProviderFactory link seemed promising too, but a bit arcane. I'm also not entirely comfortable monkeying around with a part of MVC like that. I had only a few hours to figure this problem out so I passed on this too.

约翰尼的JsonValueProviderFactory链接似乎也很有希望,但有点神秘。我也不太喜欢那样使用MVC的一部分。我只有几个小时的时间来解决这个问题,所以我也传递了这个问题。

Then I found this link somewhere, and thought "Yes! this is more like what I want!". In other words attack the model binding problem by using a custom binder. Replace the buggy one with something else, and use MVC's built-in capability to do so. Unfortunately, this did not work as my use case was List of T, and T was my model. This totally did not work with the sample. So I hacked away at it and ultimately failed.

然后我在某个地方找到了这个链接,并想“是的!这更像我想要的!”。换句话说,通过使用自定义绑定器来攻击模型绑定问题。用其他东西替换有缺陷的那个,并使用MVC的内置功能来实现。不幸的是,这不起作用,因为我的用例是T的列表,而T是我的模型。这完全不适用于样本。所以我砍掉了它,最终失败了。

Then, I got a lightbulb moment - JSON.NET does not have this problem. I use it all the time for doing all sorts of things, from cloning objects, to logging, to REST service endpoints. Why not model binding? So I ultimately ended up with this and my problem was solved. I think it should work with just about anything - I trust JSON.NET =)

然后,我得到了一个灯泡时刻--JSON.NET没有这个问题。我一直用它来做各种各样的事情,从克隆对象到日志记录,再到REST服务端点。为什么不绑定模型?所以我最终得到了这个,我的问题解决了。我认为它应该适用于任何事情 - 我相信JSON.NET =)

/// <summary>
/// Custom binder that maps JSON data in the request body to a model class using JSON.NET.
/// </summary>
/// <typeparam name="T">Model type being bound</typeparam>
/// <remarks>
/// This binder is very useful when your MVC3 model contains dictionaries, something that it can't map (this is a known bug, fixed with MVC 4)
/// </remarks>
public class CustomJsonModelBinder<T> : DefaultModelBinder
    where T : class
{
    /// <summary>
    /// Binds the model by using the specified controller context and binding context.
    /// </summary>
    /// <returns>
    /// The bound object.
    /// </returns>
    /// <param name="controllerContext">The context within which the controller operates. The context information includes the controller, HTTP content, request context, and route data.</param><param name="bindingContext">The context within which the model is bound. The context includes information such as the model object, model name, model type, property filter, and value provider.</param><exception cref="T:System.ArgumentNullException">The <paramref name="bindingContext "/>parameter is null.</exception>
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        HttpRequestBase request = controllerContext.HttpContext.Request;
        request.InputStream.Position = 0;
        var input = new StreamReader(request.InputStream).ReadToEnd();
        T modelObject = JsonConvert.DeserializeObject<T>(input);
        return modelObject;
    }
}

To apply the binder, I added an attribute to my model parameter. This causes MVC3 to use my binder instead of the default. Something like this:

为了应用活页夹,我在模型参数中添加了一个属性。这会导致MVC3使用我的活页夹而不是默认值。像这样的东西:

public ActionResult SomeAction(
    [ModelBinder(typeof(CustomJsonModelBinder<List<MyModel>>))] // This custom binder works around a known dictionary binding bug in MVC3
    List<MyModel> myModelList, int someId)
    {

One caveat - I was using POST with content type "application/json". If you're doing something like form or multipart data instead it will probably crash horribly.

一个警告 - 我使用内容类型为“application / json”的POST。如果你正在做类似表格或多部分数据的事情,它可能会崩溃。