我正在尝试以下操作:内部带有字典的模型在第一个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 是相同的。我只能假设有东西在途中丢失了。
有人知道我做错了什么吗?
一个不幸的解决方法:
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
}
由于JsonValueProviderFactory的实现方式,不支持绑定字典。
我今天遇到了同样的问题,并提出了一个解决方案,除了注册一个新的模型绑定器之外不需要任何东西。这有点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());
}
我让它与自定义模型绑定程序一起使用,并更改了数据发送的方式;不使用 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();
获取 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);
}
}
使用更好的解串器即可。 我设置位置的第一行是因为 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 提供程序...所以它不会去任何地方。
这是我对类似问题的解决方案:
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 问候 武卡斯·杜达
将复杂对象作为字符串发布并在另一端反序列化。然而,这没有类型安全。这是一个带有字符串键和字符串数组值的字典。
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();
}
对于最近遇到此问题的任何人,只要您不需要控制器专门接受字典,您就可以执行以下操作:
HttpResponseMessage SomeMethod([FromBody] IEnumerable<KeyValuePair<Key, Value>> values)
{
Dictionary<Key, Value> dictionary = values.ToDictionary(x => x.Key, x = x.Value);
}
虽然有点hacky。
如果有人在 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
} );