6

私は非常に単純なクラスを持っています:

public class FilterItem
{
    public Dictionary<string, string> ItemsDictionary { get; set; }

    public FilterItem()
    {
        ItemsDictionary = new Dictionary<string, string>();
    }
}

クライアントのディクショナリにデータを入力し、JSON オブジェクトとしてコントローラー アクションに渡したいと考えています。ただし、クライアントで何を試しても、DefaultModelBinder は逆シリアル化できないようです。

私のアクションを呼び出すための JavaScript コードの例を次に示します。

var simpleDictionary = {"ItemsDictionary": {"1": "5", "2": "7"}};

$.ajax({ cache: false, type: "POST", data: JSON.stringify(simpleDictionary),
contentType: "application/json; charset=utf-8", 
url: "/Catalog7Spikes/GetFilteredProductsJson", success: function (data) {...});

そして、これが私のアクションメソッドの簡略化されたバージョンです:

[HttpPost]
public ActionResult GetFilteredProductsJson(FilterItem filterItem)
{   
    ProductsModel productsModel = new ProductsModel();
    return View("SevenSpikes.Nop.UI.Views.Products", productsModel);
}

反対の働きをすることに注意してください。JsonResult として渡されると、FilterItem オブジェクトは正常にシリアル化され、JSON オブジェクトとしてクライアントに渡されます。しかし、逆に行こうとしてもうまくいきません。

Connect のチケットを読んで、回避策が機能するだろうと考えましたが、機能しません。

ASP.NET MVC 3 で DefaultModelBinder を使用して .NET ディクショナリを逆シリアル化することはまったく可能ですか?

4

5 に答える 5

4

ハンセルマンはこれについて次のように語っています。

ソース: http://www.hanselman.com/blog/ASPNETWireFormatForModelBindingToArraysListsCollectionsDictionaries.aspx

DefaultModelBinder、辞書に対して最適とは言えない構文を想定しています。この種の構文を使用してみてください。

 {
 "dictionary[0]":{"Key":"a", "Value":"b"},
 "dictionary[1]":{"Key":"b", "Value":"b"}
 }

少しかさばりますが、バインドされます。以下も同様に機能しますが、個人的には上記の方が好みです。それは短いです。

 {
 "dictionary[0].Key":"a",
 "dictionary[0].Value":"b",
 "dictionary[1].Key":"b"
 "dictionary[1].Value":"b"
 }
于 2012-07-06T16:00:31.837 に答える
2

アップデート

Jeroen によるブログ投稿 (以下のリンク付きの彼の回答を参照) と、自分のコードを再確認した後の頭のひらめきに基づいて、ExtendedJsonValueProviderFactoryを更新し、トップレベルの BackingStore が常に適切に作成されるようにしました。 JSON 経由で送信された辞書。

このコードは GitHub のhttps://github.com/counsellorben/ASP.NET-MVC-JsonDictionaryBindingで入手でき、実際の例はhttp://oss.form.vu/json-dictionary-example/にあります。


現在のものを削除して、JsonValueProviderFactory辞書の作成を処理できるものに置き換えることで、辞書をバインドできます。まず、Keith が指摘したように、Javascript では、必ず "filterItem" 内に辞書をラップしてください。これは、コントローラー アクションのモデル変数の名前であり、JSON の場合、コントローラー アクションの変数の名前だからです。返される Json 要素の名前と一致する必要があります。また、クラスを渡す場合、ネストされた要素はクラス内のプロパティの名前と一致する必要があります。

次に、次のようにExtendedJsonValueProviderFactoryクラスを作成します。

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.IO;
using System.Web.Script.Serialization;

public sealed class ExtendedJsonValueProviderFactory : ValueProviderFactory
{

    private void AddToBackingStore(Dictionary<string, object> backingStore, string prefix, object value)
    {
        IDictionary<string, object> d = value as IDictionary<string, object>;
        if (d != null)
        {
            foreach (KeyValuePair<string, object> entry in d)
            {
                if (entry.Key.EndsWith("Dictionary", StringComparison.CurrentCulture))
                    CreateDictionary(backingStore, entry);
                else
                    AddToBackingStore(backingStore, MakePropertyKey(prefix, entry.Key), entry.Value);
            }
            return;
        }

        IList l = value as IList;
        if (l != null)
        {
            for (int i = 0; i < l.Count; i++)
            {
                AddToBackingStore(backingStore, MakeArrayKey(prefix, i), l[i]);
            }
            return;
        }

        // primitive
        backingStore[prefix] = value;
    }

    private void CreateDictionary(Dictionary<string, object> backingStore, KeyValuePair<string, object> source)
    {
        var d = source.Value as IDictionary<string, object>;
        var dictionary = new Dictionary<string, string>();
        foreach (KeyValuePair<string, object> entry in d)
            dictionary.Add(entry.Key, entry.Value.ToString());

        AddToBackingStore(backingStore, source.Key, dictionary);
        return;
    }

    private static object GetDeserializedObject(ControllerContext controllerContext)
    {
        if (!controllerContext.HttpContext.Request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
        {
            // not JSON request
            return null;
        }

        StreamReader reader = new StreamReader(controllerContext.HttpContext.Request.InputStream);
        string bodyText = reader.ReadToEnd();
        if (String.IsNullOrEmpty(bodyText))
        {
            // no JSON data
            return null;
        }

        JavaScriptSerializer serializer = new JavaScriptSerializer();
        object jsonData = serializer.DeserializeObject(bodyText);
        return jsonData;
    }

    public override IValueProvider GetValueProvider(ControllerContext controllerContext)
    {
        if (controllerContext == null)
        {
            throw new ArgumentNullException("controllerContext");
        }

        object jsonData = GetDeserializedObject(controllerContext);
        if (jsonData == null)
        {
            return null;
        }

        Dictionary<string, object> backingStore = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
        AddToBackingStore(backingStore, String.Empty, jsonData);

        return new DictionaryValueProvider<object>(backingStore, CultureInfo.CurrentCulture);
    }

    private static string MakeArrayKey(string prefix, int index)
    {
        return prefix + "[" + index.ToString(CultureInfo.InvariantCulture) + "]";
    }

    private static string MakePropertyKey(string prefix, string propertyName)
    {
        return (String.IsNullOrEmpty(prefix)) ? propertyName : prefix + "." + propertyName;
    }
}

このクラスは、 type の DictionaryValueProvider へのエントリを構築するための拡張機能を除いて、標準の JsonValueProviderFactory クラスとほとんど同じであることに気付くかもしれませんDictionary<string,string>また、ディクショナリとして処理されるためには、要素の名前が「Dictionary」で終わる必要があることにも注意してください(これは重大なコード臭だと思いますが、現時点では別の代替案を思いつきません ...私は提案を受け入れています)。

次に、以下をApplication_Startinに追加しGlobal.asax.csます。

var j = ValueProviderFactories.Factories.FirstOrDefault(f => f.GetType().Equals(typeof(JsonValueProviderFactory)));
if (j != null)
    ValueProviderFactories.Factories.Remove(j);
ValueProviderFactories.Factories.Add(new ExtendedJsonValueProviderFactory());

これにより、標準の JsonValueProviderFactory が削除され、拡張クラスに置き換えられます。

最後のステップ:良さを楽しむ。

于 2011-08-13T05:56:15.057 に答える
0

デフォルトのモデルバインダーはリストを処理できません。私はオープンソースプロジェクトでこの問題を解決しました:http://jsaction.codeplex.comそしてこの問題についての記事を書きました:ここを読んでください http://jsaction.codeplex.com/wikipage?title=AllFeatures&referringTitle=Documentation

... Asp.net MVCには、送信されたデータを強い型のオブジェクトに変換する機能が組み込まれています。ただし、送信するデータは正しい方法で準備する必要があります。これにより、デフォルトのデータバインダーがデータを作成し、コントローラーのアクションパラメーターオブジェクトのプロパティにデータを入力できるようになります。問題は、jQuery.ajax()関数呼び出しへのJSONオブジェクトの提供が機能しないことです。まったく。データはサーバーにバインドされたデータを取得しないため、コントローラーのアクションパラメーターのデフォルト値はおそらく無効です。問題は、JSONオブジェクトがjQueryによって変換されてクエリ文字列を要求し、第2レベルのプロパティ値がAsp.netMVCのデフォルトモデルバインダーが理解できない形式に変更されたことです...

于 2012-04-20T21:07:25.617 に答える
0

次のことを試しましたか?

var simpleDictionary = {"ItemsDictionary": {"1": "5", "2": "7"}};

$.ajax({ cache: false, type: "POST", data: {filterItem : JSON.stringify(simpleDictionary)},
contentType: "application/json; charset=utf-8", 
url: "/Catalog7Spikes/GetFilteredProductsJson", success: function (data) {...});
于 2011-08-12T20:56:01.537 に答える
0

昨日、JavaScript (JSON) 辞書をコントローラーのアクション メソッドに投稿しようとしたときに、まったく同じ問題が発生しました。直接 (アクション メソッド パラメーターで) またはモデル クラスに含まれる、異なる型引数を持つ汎用辞書を処理するカスタム モデル バインダーを作成しました。MVC 3でのみテストしました。

私の経験とカスタム モデル バインダーのソース コードの詳細については、http://buildingwebapps.blogspot.com/2012/01/passing-javascript-json-dictionary-to.htmlのブログ投稿を参照してください。

于 2012-01-19T09:44:00.653 に答える