POST json 字典

问题描述 投票:0回答:11

我正在尝试以下操作:内部带有字典的模型在第一个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
11个回答
22
投票

一个不幸的解决方法:

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 lib 函数(使用 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
}

15
投票

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


6
投票

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

jSON:

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

控制器:

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

这是出现时显示的内容(Name 和 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());
    }

2
投票

我让它与自定义模型绑定程序一起使用,并更改了数据发送的方式;不使用 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();

2
投票

获取 System.Json 的以下 NuGet

 包,其中包括新的 
JsonValue
类型。
JsonValue
是一种灵活的新 JSON 代表类型,完全支持 C# 4 动态,如果您希望将有效负载视为字典/关联数组,也是一个
IEnumerable<KeyValuePair<string, JsonValue>>

您可以在此处使用 NuGet 获取 System.Json(测试版)。看来

System.Json
将原生包含在 .NET 4.5 中,如此处 文档页面所示

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

JSON、ASP.NET MVC 和 JQuery - 使用非类型化 JSON 变得容易

上面文章中的两段相关代码是 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);
    }
}

1
投票

使用更好的解串器即可。 我设置位置的第一行是因为 JsonValueProvider 在最后离开流。更多 MS JSON 失败。

Request.InputStream.Position = 0;
var reader = new StreamReader(Request.InputStream);

var model = Newtonsoft.Json.JsonConvert.DeserializeObject<CreativeUploadModel>(reader.ReadToEnd());

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

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 提供程序...所以它不会去任何地方。


1
投票

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

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 问候 武卡斯·杜达


1
投票

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

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

1
投票

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

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

虽然有点hacky。


0
投票

如果有人在 2023 年以后偶然发现这个线程,因为它是 Google 中的第一个答案 - 即使在 Dictionary 中使用 Dictionary,它现在也可以开箱即用(使用 .Net 8.0)。

控制器代码:

    public IActionResult TestModelParsing( Dictionary<string, Dictionary<string, decimal>> ajaxData )
    {
        return Json( new {} );
    }

Javascript:

let data = {
    ajaxData: {
        "fieldName1": {
            innerField1: 1,
            innerField2: 2
        },
        "fieldName2": {
            innerField1: 1,
            innerField2: 2
        }
    }
}

$.ajax( {
    url: "/test/TestModelParsing",
    data: data,
    dataType: "json",
    method : "POST",
    timeout: 600000
} );

© www.soinside.com 2019 - 2024. All rights reserved.