2011-01-17 16 views
20

私は次のことを試しています:最初のajaxリクエストでそれを送り、結果を再びシリアル化してコントローラに送り返します。POST json dictionary

これは私のモデルで辞書を返すことができることをテストする必要があります。 JavaScriptの

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

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

が放火犯でJSONを受信し、送信されたJSONが同一であることは

ここに私の簡単なテストです動作しません。私は途中で何かが失われているとしか思えません。

誰かが私が間違っていることについての考えがありますか?

+1

[ASP.NET MVCへのJSONデータの投稿]の可能な複製(http://stackoverflow.com/questions/4164114/posting-json-data-to-asp-net-mvc) –

答えて

13

JsonValueProviderFactoryが実装されているため、バインディング辞書はサポートされていません。

+0

詳細については詳しく説明しています?つまり、入力ストリームを読み込んでJavascriptSerializerに渡すだけです。他に何か変なことはありますか? – sirrocco

+2

@シロコ、それ以上のことをします。リフレクタで 'JsonValueProviderFactory'を見てください。その時点でモデルの型を知らないので、 'Deserialize'の代わりに' DeserializeObject'メソッドを使用していることがわかります。そして、完全に新しい 'DictionaryValueProvider'を構築し、' prefix.propertyName'と 'prefix [index]'表記を生成する 'MakePropertyKey'と' MakeArrayKey'専用関数だけが実装されています。 'prefix [index] .Key'と' prefix [index] .Value'の形式である必要がある辞書の大文字小文字を扱うものはありません。 –

+1

それはバグや実装されていない機能だと考えてください。あなたが好むように:-) –

20

不幸な回避策:

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 
} 
2

カスタムモデルのバインダーを使用してデータを送信する方法を変更しました。 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(); 
1

ちょうど良くデシリアライザを使用しています。 JsonValueProviderがストリームを最後に残すため、最初の行の位置が設定されています。より多くのMS JSONが失敗します。

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

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

だから、どこかでそのCreativeUploadModelオブジェクトグラフであり、このような小道具:

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

Newtonsoft JSONは、JSONデフォルトです。(例えば)から直列化復元され

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

WebAPIのプロバイダ...どこにも行かないので。

2

System.JsonNuGetパッケージには、新しいJsonValueタイプが含まれています。 JsonValueは、C#4のダイナミックを完全にサポートするフレキシブルなJSONの代表的なタイプで、ペイロードを辞書/連想配列として扱いたい場合はIEnumerable<KeyValuePair<string, JsonValue>>です。

System.Json (Beta) with NuGet hereを受け取ることができます。 documentation pages hereで示されるように、System.Jsonは.NET 4.5にネイティブに含まれているようです。

あなたはまた、適切にあなたのアクションメソッドのパラメータにJsonValueオブジェクトにデシリアライズするためのJSON HTTP体を得ることを支援するために、次の資料を参照することもできます。

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

記事からのコードの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); 
    } 
} 
4

今日、同じ問題を抱えて、新しいモデルバインダーを登録する必要はありませんでした。ちょっとハッキリですが、うまくいけば誰かを助けてくれるでしょう。

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

バインダーは下のGlobal.asaxファイルに登録されているのApplication_Start

protected void Application_Start() 
    { 
     AreaRegistration.RegisterAllAreas(); 

     RegisterGlobalFilters(GlobalFilters.Filters); 
     RegisterRoutes(RouteTable.Routes); 

     ModelBinders.Binders.Add(typeof(Dictionary<string,string>), new DictionaryModelBinder()); 
    } 
ここ
+0

このソリューションをありがとうございました。これは他のソリューションがここに掲載されていないとき私のために働いた。 – Andy

+0

このコードをかなりそのまま使用しました(代わりにDictionary が必要でした)、それは魅力的に機能しました。 –

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 挨拶 ŁukaszDuda

1

複雑なオブジェクトを文字列としてポストし、もう一方の端をデシリアライズします。しかし、このための型安全性はありません。ここには、文字列キーと文字列配列の値を持つ辞書があります。 ASPを使用

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

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#のコントローラ。NET 5とストレート私はこれをやっているボックスのうちMVC 6:

JSON:

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

コントローラー:

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

これは、それが(名前伝わってくるときまで表示するもので、アップ来るの誰のための

enter image description here

1

:Dはキー)ですこの問題について最近、まだ、限り、あなたは、具体的辞書を受け入れるようにコントローラを必要としないよう、次の操作を実行できます。

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

をそれは少しハックですが。