私は次のことを試みています:最初のajax要求でsend内にディクショナリを持つモデルは、結果を再度シリアル化し、コントローラーに送り返します。
これで、モデルで辞書を取得できることをテストできます。動かない
これが私の簡単なテストです:
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は同じです。途中で何かが道に迷ったと思います。
誰かが私が間違っていることについてアイデアを持っていますか?
JsonValueProviderFactory の実装方法が原因で、バインディング辞書はサポートされていません。
不幸な回避策:
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
}
今日同じ問題に遭遇し、新しいモデルバインダーを登録する以外に何も必要としないソリューションを思いつきました。それは少しハックですが、うまくいけば誰かを助けるでしょう。
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;
}
}
バインダーは、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());
}
次の NuGet パッケージをSystem.Json
、これには新しいJsonValue
タイプが含まれます。 JsonValue
は、C#4動的を完全にサポートする柔軟な新しいJSON代表型であり、IEnumerable<KeyValuePair<string, JsonValue>>
ペイロードを辞書/連想配列として扱いたい場合。
System.Json(Beta)with NuGet here を取得できます。そうみたいです System.Json
は.NET 4.5にネイティブに組み込まれます。これは ドキュメントページはこちら で示されています。
また、次の記事を読んで、JSON HTTPボディがアクションメソッドパラメータのJsonValueオブジェクトに適切にデシリアライズされるようにすることもできます。
JSON、ASP.NET MVCおよびJQuery-型なしJSONの操作が容易になりました
上記の記事の2つの関連するコードは、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);
}
}
カスタムモデルバインダーを使用して、データの送信方法を変更しました。 Stringifyを使用せずにcontenttypeを設定します。
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();
より優れたデシリアライザを使用してください。私が位置を設定した最初の行は、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プロバイダーです...どこにも行きません。
最近この問題がまだ発生している場合は、コントローラーが辞書を特別に受け入れる必要がない限り、以下を実行できます。
HttpResponseMessage SomeMethod([FromBody] IEnumerable<KeyValuePair<Key, Value>> values)
{
Dictionary<Key, Value> dictionary = values.ToDictionary(x => x.Key, x = x.Value);
}
それは少しハッキーですが。
複雑なオブジェクトを文字列としてポストし、反対側でデシリアライズします。ただし、これには型保証はありません。これは、文字列キーと文字列配列値を持つ辞書です。
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();
}
これが同様の問題に対する私の解決策です:
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挨拶ŁukaszDuda