【问题标题】:POST json dictionaryPOST json 字典
【发布时间】:2011-01-17 06:56:50
【问题描述】:

我正在尝试以下操作:内部带有字典的模型在第一个 ajax 请求上发送它,然后将结果再次序列化并将其发送回控制器。

这应该测试我可以在我的模型中取回字典。它不起作用

这是我的简单测试:

public class HomeController : Controller
{
    public ActionResult Index (T a)
    {
      return View();
    }

    public JsonResult A(T t)
    {
      if (t.Name.IsEmpty())
      {
        t = new T();
        t.Name = "myname";
        t.D = new Dictionary<string, string>();
        t.D.Add("a", "a");
        t.D.Add("b", "b");
        t.D.Add("c", "c");
      }
      return Json(t);
    }
}

//model
public class T
{
  public string Name { get; set; }
  public IDictionary<string,string> D { get; set; }
}

javascript:

$(function () {
    var o = {
        Name: 'somename',
        "D": {
            "a": "b",
            "b": "c",
            "c": "d"
        }
    };

    $.ajax({
        url: actionUrl('/home/a'),
        contentType: 'application/json',
        type: 'POST',
        success: function (result) {
            $.ajax({
                url: actionUrl('/home/a'),
                data: JSON.stringify(result),
                contentType: 'application/json',
                type: 'POST',
                success: function (result) {
                }
            });
        }
    });
});

在 firebug 中接收到的 json 和发送的 json 是相同的。我只能假设有些东西在途中丢失了。

有人知道我做错了什么吗?

【问题讨论】:

标签: c# json asp.net-mvc-3 post dictionary


【解决方案1】:

一个不幸的解决方法:

data.dictionary = {
    'A': 'a',
    'B': 'b'
};

data.dictionary = JSON.stringify(data.dictionary);

. . .

postJson('/mvcDictionaryTest', data, function(r) {
    debugger;
}, function(a,b,c) {
    debugger;
});

postJSON js 库函数(使用 jQuery):

function postJson(url, data, success, error) {
    $.ajax({
        url: url,
        data: JSON.stringify(data),
        type: 'POST',
        contentType: 'application/json; charset=utf-8',
        dataType: 'json',
        success: success,
        error: error
    });
}

正在发布的 ViewModel 对象(可能比字典还多):

public class TestViewModel
{
    . . .
    //public Dictionary<string, string> dictionary { get; set; }
    public string dictionary { get; set; }
    . . .
}

控制器方法被发布到:

[HttpPost]
public ActionResult Index(TestViewModel model)
{
    var ser = new System.Web.Script.Serialization.JavascriptSerializer();
    Dictionary<string, string> dictionary = ser.Deserialize<Dictionary<string, string>>(model.dictionary);

    // Do something with the dictionary
}

【讨论】:

    【解决方案2】:

    由于JsonValueProviderFactory 的实现方式,不支持绑定字典。

    【讨论】:

    • @sirrocco,它的作用远不止这些。查看带有反射器的JsonValueProviderFactory。您会看到它使用DeserializeObject 方法而不是Deserialize,因为此时它不知道模型的类型。然后它构建了一个全新的DictionaryValueProvider,你可以看到只有MakePropertyKeyMakeArrayKey私有函数被实现,它们生成prefix.propertyNameprefix[index]符号。没有什么可以处理需要采用prefix[index].Keyprefix[index].Value 形式的字典的情况。
    • 所以将其视为一个错误或未实现的功能。随你喜欢:-)
    • 值得注意的是,上述错误现在在 MS Connect 报告为已修复。是的。
    • 并确认 - 如果您升级到 ASP.Net 4.5 和 MVC 4,您可以通过 MVC 中的默认 ValueBinders 在 POST 上序列化 JSON 字典。
    • 有没有人尝试使用 MVC5 模拟绑定到 Dictionary 但它在我的控制器上不起作用
    【解决方案3】:

    直接使用 ASP.NET 5 和 MVC 6 我正在这样做:

    jSON:

    {
        "Name": "somename",
        "D": {
            "a": "b",
            "b": "c",
            "c": "d"
        }
    }
    

    控制器:

    [HttpPost]
    public void Post([FromBody]Dictionary<string, object> dictionary)
    {
    }
    

    这是通过时显示的内容(名称和 D 是键):

    【讨论】:

      【解决方案4】:

      我今天遇到了同样的问题,并想出了一个解决方案,它不需要任何东西,只需要注册一个新的模型活页夹。这有点hacky,但希望它可以帮助某人。

          public class DictionaryModelBinder : IModelBinder
          {
              /// <summary>
              /// Binds the model to a value by using the specified controller context and binding context.
              /// </summary>
              /// <returns>
              /// The bound value.
              /// </returns>
              /// <param name="controllerContext">The controller context.</param><param name="bindingContext">The binding context.</param>
              public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
              {
                  if (bindingContext == null)
                      throw new ArgumentNullException("bindingContext");
      
                  string modelName = bindingContext.ModelName;
                  // Create a dictionary to hold the results
                  IDictionary<string, string> result = new Dictionary<string, string>();
      
                  // The ValueProvider property is of type IValueProvider, but it typically holds an object of type ValueProviderCollect
                  // which is a collection of all the registered value providers.
                  var providers = bindingContext.ValueProvider as ValueProviderCollection;
                  if (providers != null)
                  {
                      // The DictionaryValueProvider is the once which contains the json values; unfortunately the ChildActionValueProvider and
                      // RouteDataValueProvider extend DictionaryValueProvider too, so we have to get the provider which contains the 
                      // modelName as a key. 
                      var dictionaryValueProvider = providers
                          .OfType<DictionaryValueProvider<object>>()
                          .FirstOrDefault(vp => vp.ContainsPrefix(modelName));
                      if (dictionaryValueProvider != null)
                      {
                          // There's no public property for getting the collection of keys in a value provider. There is however
                          // a private field we can access with a bit of reflection.
                          var prefixsFieldInfo = dictionaryValueProvider.GetType().GetField("_prefixes",
                                                                                            BindingFlags.Instance |
                                                                                            BindingFlags.NonPublic);
                          if (prefixsFieldInfo != null)
                          {
                              var prefixes = prefixsFieldInfo.GetValue(dictionaryValueProvider) as HashSet<string>;
                              if (prefixes != null)
                              {
                                  // Find all the keys which start with the model name. If the model name is model.DictionaryProperty; 
                                  // the keys we're looking for are model.DictionaryProperty.KeyName.
                                  var keys = prefixes.Where(p => p.StartsWith(modelName + "."));
                                  foreach (var key in keys)
                                  {
                                      // With each key, we can extract the value from the value provider. When adding to the dictionary we want to strip
                                      // out the modelName prefix. (+1 for the extra '.')
                                      result.Add(key.Substring(modelName.Length + 1), bindingContext.ValueProvider.GetValue(key).AttemptedValue);
                                  }
                                  return result;
                              }
                          }
                      }
                  }
                  return null;
              }
          }
      

      binder 在 application_start 下的 Global.asax 文件中注册

          protected void Application_Start()
          {
              AreaRegistration.RegisterAllAreas();
      
              RegisterGlobalFilters(GlobalFilters.Filters);
              RegisterRoutes(RouteTable.Routes);
      
              ModelBinders.Binders.Add(typeof(Dictionary<string,string>), new DictionaryModelBinder());
          }
      

      【讨论】:

      • 非常感谢您提供此解决方案。当此处发布的其他解决方案不起作用时,这对我有用。
      • 几乎一字不差地使用了这段代码(需要一个 Dictionary 代替),它就像一个魅力。
      【解决方案5】:

      我让它与自定义模型绑定器一起工作,并改变了数据的发送方式;不使用 Stringify 并设置内容类型。

      JavaScript:

          $(function() {
              $.ajax({
                  url: '/home/a',
                  type: 'POST',
                  success: function(result) {
                      $.ajax({
                          url: '/home/a',
                          data: result,
                          type: 'POST',
                          success: function(result) {
      
                          }
                      });
                  }
              });
          });
      

      自定义模型绑定器:

      public class DictionaryModelBinder : IModelBinder
      {          
          public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
          {
              if (bindingContext == null)
                  throw new ArgumentNullException("bindingContext");
      
              string modelName = bindingContext.ModelName;
              IDictionary<string, string> formDictionary = new Dictionary<string, string>();
      
              Regex dictionaryRegex = new Regex(modelName + @"\[(?<key>.+?)\]", RegexOptions.CultureInvariant);
              foreach (var key in controllerContext.HttpContext.Request.Form.AllKeys.Where(k => k.StartsWith(modelName + "[")))
              {
                  Match m = dictionaryRegex.Match(key);
                  if (m.Success)
                  {
                      formDictionary[m.Groups["key"].Value] = controllerContext.HttpContext.Request.Form[key];
                  }
              }
              return formDictionary;
          }
      }
      

      并通过在 Global.asax 中添加模型绑定器:

      ModelBinders.Binders[typeof(IDictionary<string, string>)] = new DictionaryModelBinder();
      

      【讨论】:

        【解决方案6】:

        System.Json 获取以下NuGet 包,其中包括新的JsonValue 类型。 JsonValue 是一种灵活的新 JSON 代表类型,完全支持 C# 4 动态,如果您希望将有效负载视为字典/关联数组,它也是 IEnumerable&lt;KeyValuePair&lt;string, JsonValue&gt;&gt;

        您可以接听System.Json (Beta) with NuGet hereSystem.Json 似乎将原生包含在 .NET 4.5 中,如 documentation pages here 所示。

        您可能还想阅读以下文章,以帮助让 JSON HTTP 主体正确反序列化为 Action 方法参数中的 JsonValue 对象:

        JSON, ASP.NET MVC and JQuery - Working with Untyped JSON made easy

        上面文章中的两段相关代码是 DynamicJsonBinder 和 DynamicJsonAttribute,在此复制以供后代使用:

        public class DynamicJsonBinder : IModelBinder  
        {  
            public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)  
            { 
                if (!controllerContext.HttpContext.Request.ContentType.StartsWith  
                      ("application/json", StringComparison.OrdinalIgnoreCase))  
                {  
                    // not JSON request  
                    return null;  
                }  
        
                var inpStream = controllerContext.HttpContext.Request.InputStream;  
                inpStream.Seek(0, SeekOrigin.Begin);  
        
                StreamReader reader = new StreamReader(controllerContext.HttpContext.Request.InputStream);  
                string bodyText = reader.ReadToEnd();  
                reader.Close();  
        
        
                if (String.IsNullOrEmpty(bodyText))  
                {  
                    // no JSON data  
                    return null;  
                }  
        
                return JsonValue.Parse(bodyText);  
            }  
        } 
        
        public class DynamicJsonAttribute : CustomModelBinderAttribute
        {
            public override IModelBinder GetBinder()
            {
                return new DynamicJsonBinder();
            }
        }
        

        一个相关的示例用例是:

        public class HomeController : Controller
        {
            public ActionResult Index (T a)
            {
              return View();
            }
        
            public JsonResult A([DynamicJson] JsonValue value)
            {
              dynamic t = value.AsDynamic();
        
              if (t.Name.IsEmpty())
              {
                t = new // t is dynamic, so I figure just create the structure you need directly
                {
                    Name = "myname",
                    D = new // Associative array notation (woot!): 
                    {
                        a = "a",
                        b = "b",
                        c = "c" 
                    }
                };
              }
        
              return Json(t);
            }
        }
        

        【讨论】:

          【解决方案7】:

          只需使用更好的反序列化器即可。 我设置位置的第一行是因为 JsonValueProvider 在最后离开了流。更多 MS JSON 失败。

          Request.InputStream.Position = 0;
          var reader = new StreamReader(Request.InputStream);
          
          var model = Newtonsoft.Json.JsonConvert.DeserializeObject<CreativeUploadModel>(reader.ReadToEnd());
          

          所以在 CreativeUploadModel 对象图中的某个地方有一个像这样的道具:

          public Dictionary<string, Asset> Assets { get; set; }
          

          反序列化自(例如):

          "assets":{"flash":{"type":"flash","value":"http://1234.cloudfront.net/1234.swf","properties":"{\"clickTag\":\"clickTAG\"}"}
          

          Newtonsoft JSON 是 WebAPI 的默认 JSON 提供程序...所以它不会去任何地方。

          【讨论】:

            【解决方案8】:

            这是我对类似问题的解决方案:

            using System.Collections.Generic;
            using System.IO;
            using System.Web.Mvc;
            using System.Web.Script.Serialization;
            
            namespace Controllers
            {
                public class DictionaryModelBinder : IModelBinder
                {
                    public object BindModel(ControllerContext context, ModelBindingContext bindingContext)
                    {
                        context.HttpContext.Request.InputStream.Seek(0, SeekOrigin.Begin);
                        using (TextReader reader = new StreamReader(context.HttpContext.Request.InputStream))
                        {
                            string requestContent = reader.ReadToEnd();
                            var arguments = new JavaScriptSerializer().Deserialize<Dictionary<string, object>>(requestContent);
                            return arguments[bindingContext.ModelName];
                        }
                    }
                }
            }
            
            using Controllers;
            using Moq;
            using NUnit.Framework;
            using System.Collections;
            using System.Collections.Generic;
            using System.IO;
            using System.Text;
            using System.Web;
            using System.Web.Mvc;
            
            namespace ControllersTest
            {
                [TestFixture]
                public class DictionaryModelBinderTest
                {
                    private ControllerContext controllerContext;
            
                    [Test]
                    public void ReturnsDeserializedPrimitiveObjectsAndDictionaries()
                    {
                        string input =
            @"{
                arguments: {
                    simple: 1,
                    complex: { a: 2, b: 3 },
                    arrayOfSimple: [{ a: 4, b: 5 }],
                    arrayOfComplex: [{ a: 6, b: 7 }, { a: 8, b: 9 }]},
                otherArgument: 1
            }";
                        SetUpRequestContent(input);
            
                        var binder = new DictionaryModelBinder();
                        var bindingContext = new ModelBindingContext();
                        bindingContext.ModelName = "arguments";
            
                        var model = (Dictionary<string, object>)binder.BindModel(controllerContext, bindingContext);
            
                        Assert.IsFalse(model.ContainsKey("otherArgument"));
                        Assert.AreEqual(1, model["simple"]);
                        var complex = (Dictionary<string, object>)model["complex"];
                        Assert.AreEqual(2, complex["a"]);
                        Assert.AreEqual(3, complex["b"]);
                        var arrayOfSimple = (ArrayList)model["arrayOfSimple"];
                        Assert.AreEqual(4, ((Dictionary<string, object>)arrayOfSimple[0])["a"]);
                        Assert.AreEqual(5, ((Dictionary<string, object>)arrayOfSimple[0])["b"]);
                        var arrayOfComplex = (ArrayList)model["arrayOfComplex"];
                        var complex1 = (Dictionary<string, object>)arrayOfComplex[0];
                        var complex2 = (Dictionary<string, object>)arrayOfComplex[1];
                        Assert.AreEqual(6, complex1["a"]);
                        Assert.AreEqual(7, complex1["b"]);
                        Assert.AreEqual(8, complex2["a"]);
                        Assert.AreEqual(9, complex2["b"]);
                    }
            
                    private void SetUpRequestContent(string input)
                    {
                        var stream = new MemoryStream(Encoding.UTF8.GetBytes(input));
                        stream.Seek(0, SeekOrigin.End);
            
                        var controllerContextStub = new Mock<ControllerContext>();
                        var httpContext = new Mock<HttpContextBase>();
                        httpContext.Setup(x => x.Request.InputStream).Returns(stream);
                        controllerContextStub.Setup(x => x.HttpContext).Returns(httpContext.Object);
                        this.controllerContext = controllerContextStub.Object;
                    }
                }
            }
            
            using Controllers;
            using PortalApi.App_Start;
            using System.Collections.Generic;
            using System.Web.Http;
            using System.Web.Mvc;
            using System.Web.Routing;
            
            namespace PortalApi
            {
                public class MvcApplication : System.Web.HttpApplication
                {
                    protected void Application_Start()
                    {
                        AreaRegistration.RegisterAllAreas();
            
                        WebApiConfig.Register(GlobalConfiguration.Configuration);
                        RouteConfig.RegisterRoutes(RouteTable.Routes);
                        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
                        ModelBinders.Binders.Add(typeof(Dictionary<string, object>), new DictionaryModelBinder());
                    }
                }
            }
            

            玩得开心! :-P 你好 Łukasz Duda

            【讨论】:

              【解决方案9】:

              将复杂对象作为字符串发布并在另一端反序列化。然而,这没有类型安全。这是一个包含字符串键和字符串数组值的字典。

              js:

              var data = { 'dictionary': JSON.stringify({'A': ['a', 'b'] }) };
              
              $.ajax({
                  url: '/Controller/MyAction',
                  data: JSON.stringify(data),
                  type: 'POST',
                  contentType: 'application/json',
                  dataType: 'json'
              });
              

              c#控制器:

              [HttpPost]
              public ActionResult MyAction(string dictionary)
              {
                  var s = new System.Web.Script.Serialization.JavaScriptSerializer();
                  Dictionary<string, string[]> d = s.Deserialize<Dictionary<string, string[]>>(dictionary);
                  return View();
              }
              

              【讨论】:

                【解决方案10】:

                对于最近仍然遇到此问题的任何人,只要您不需要控制器专门接受字典,您可以执行以下操作:

                HttpResponseMessage SomeMethod([FromBody] IEnumerable<KeyValuePair<Key, Value>> values)
                {
                    Dictionary<Key, Value> dictionary = values.ToDictionary(x => x.Key, x = x.Value);
                }
                

                虽然有点hacky。

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 2017-09-08
                  • 1970-01-01
                  • 1970-01-01
                  • 2013-04-20
                  • 1970-01-01
                  • 1970-01-01
                  • 2017-09-21
                  • 2018-01-08
                  相关资源
                  最近更新 更多