在 MVC 3 中针对 AJAX 请求反序列化字典时出现问题(一种与经典 ASP.NET Webform 一起开箱即用的方法)

发布于 2024-11-27 08:06:02 字数 2330 浏览 2 评论 0原文

我已经成功地使用相对复杂的参数集(使用 jQuery.ajax 调用)进行 AJAX 调用的 WebForms。我们尝试在 MVC 3 中使用相同的方法,但似乎在第一个障碍上就遇到了 MVC 未能成功反序列化字典数组的问题。

在 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)

下面是 MVC 3 的等效方法:(注意完全相同的名称/参数 - 不同的返回类型,但我认为这不相关)

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

使用 WebMethod所有数据都完美反序列化。然而,当调用 MVC 方法时,所有简单参数都可以很好地反序列化,但由于某种未知原因,字典数组作为空数组到达。

那么,接下来有很多问题:

  • 还有其他人遇到过 MVC 3 字典数组反序列化问题吗?
  • MVC 3 默认情况下不使用 System.Web.Script.Serialization.JavaScriptSerializer,我认为 ASP.NET WebMethods 在引擎盖下使用的是 System.Web.Script.Serialization.JavaScriptSerializer 吗?
  • 我可以强制 MVC 3 使用 System.Web.Script.Serialization.JavaScriptSerializer 而不是它正在使用的吗?
  • 或者我错过了什么/我的方法应该略有不同吗?请注意,至少现在我们需要在经典 ASP.NET WebMethods 和 MVC 3 之间共享客户端代码,因此我们希望尽可能保持原样。
  • 最后,我可以看到有一个可能的解决方法可以用来查看这个问题: POST json 字典 。这是镇上唯一的解决方法吗?或者自从提出这个问题以来情况有所改善?

jQuery AJAX 调用:

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

示例 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
}

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.

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

[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)

[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.

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

  • Has anyone else experienced problems with MVC 3 deserialization of arrays of dictionaries?
  • Does MVC 3 by default not use System.Web.Script.Serialization.JavaScriptSerializer which is I think what ASP.NET WebMethods use under the bonnet?
  • Can I force MVC 3 to use System.Web.Script.Serialization.JavaScriptSerializer instead of what it is using?
  • 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.
  • 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?

jQuery AJAX call:

$.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
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

初见 2024-12-04 08:06:02

我没有任何绑定到字典数组的经验,但一种可能的解决方案是使用自定义模型绑定器。 Scott Hanselman 有一篇关于此主题的博客文章,您可能会发现它有用:Splitting DateTime - Unit Test ASP.NET MVC Custom Model。

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.

小ぇ时光︴ 2024-12-04 08:06:02

更新这个已经很长时间了,但我想我应该分享一下我们的进展。问题被证明是一个错误 - 详细信息可以在这里找到:

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

解决方法:
POST json 字典

我们使用了上述的解决方法,效果很好。我不太清楚修复程序何时发布以及错误到底在哪里。 (是否依赖于 .NET/MVC 等)如果其他人知道我很想知道:-)

更新

我还没有听说这是否已发货(我假设它与MVC 4?)但在此期间这可能是一个替代解决方案:

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

<强>更新 2

现在已作为 MVC 4 的修复程序提供。该问题在 MVC 3 中仍未解决,因此我现在将其写为博客文章:

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

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

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 :-)

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:

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:

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

面犯桃花 2024-12-04 08:06:02

我也遇到了这个问题。找到这篇 SO 帖子后,我考虑升级到 MVC4,但在我的环境中一次完成所有操作风险太大,所以从头开始。

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

约翰尼的 JsonValueProviderFactory 链接 似乎也很有希望,但有点神秘。我也不太愿意胡闹 MVC 的一部分。我只有几个小时的时间来解决这个问题,所以我也放弃了这个问题。

然后我在某处找到了此链接,并想“是的!这更像是我想要的!”。换句话说,通过使用自定义绑定器来解决模型绑定问题。用其他东西替换有问题的东西,并使用 MVC 的内置功能来做到这一点。不幸的是,这不起作用,因为我的用例是 T 列表,而 T 是我的模型。这对样本完全不起作用。于是我就开始尝试,最终还是失败了。

然后,我恍然大悟——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;
    }
}

为了应用绑定器,我向模型参数添加了一个属性。这会导致 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)
    {

一个警告 - 我正在使用内容类型为“application/json”的 POST。如果您正在执行诸如表单或多部分数据之类的操作,它可能会严重崩溃。

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.

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'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.

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.

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 =)

/// <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:

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文