4

リストを引数として受け入れる ActionResult メソッドがあります。

[HttpPost]
public ActionResult MyMethod (List<ClassA> json)
{
   ...
}

これにより、入力された ClassA オブジェクトの一般的なリストに入ってくる json 文字列がバインドされます。

問題は、入ってくる json が json オブジェクトの配列ではなく、単一の json オブジェクトである場合があることです。

ClassA vs List に直接バインドできるように、これを先取りする方法はありますか? または、私が使用できる他のテクニックはありますか?

JSON が (配列として) 送信される方法は次のとおりです。

var myjsonarray = [{
  "ID": "1",
  "FirstName": "John",
  "LastName": "Doe",
}, {
  "ID": "2",
  "FirstName": "Jane",
  "LastName": "Doe",
}];

$.ajax({
 type: "POST",
 contentType: "application/json; charset=utf-8",
 url: "/Home/MyPage",
 data: JSON.stringify(myjsonarray),
 dataType: 'json'
});

上記のプロセスは正常です。これも機能します:

var myjsonarray = [{
  "ID": "1",
  "FirstName": "John",
  "LastName": "Doe",
}];

しかし、配列にラップされていない単一のオブジェクトとして送信すると:

var myjsonarray = {
  "ID": "1",
  "FirstName": "John",
  "LastName": "Doe",
};

ActionResult メソッドのパラメーターが null です:

json == null
4

3 に答える 3

3

あなたの質問からは明らかではありませんが、他の回答のコメントから、この JSON を送信しているクライアント コードを制御できないようです。(もしそうなら、最も簡単な解決策は、他の人がすでに提案しているように、送信する前に単一のオブジェクトを配列にラップすることです。)

この状況を処理するために、MVC でコントローラーにメソッド オーバーロードを追加できるようになれば素晴らしいことです。たとえば、次のようになります。

[HttpPost]
public ActionResult MyMethod (List<ClassA> list)
{
   ...
}

[HttpPost]
public ActionResult MyMethod (ClassA single)
{
   ...
}

これは正常にコンパイルされますが、残念ながらSystem.Reflection.AmbiguousMatchException、実行時にメソッドをヒットするとエラーが発生します。

したがって、問題を解決するにはカスタムIModelBinderを作成する必要があるようです。これまでカスタム モデル バインダーを実装したことはありませんでしたが、挑戦として次のことを思いつきました。明らかに自分のニーズに合わせて微調整する必要がありますが、あなたの例ではうまくいくようです。

コードは次のとおりです。

public class CustomModelBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        IValueProvider provider = bindingContext.ValueProvider;

        // Check whether we have a list or a single object.  If we have a list
        // then all the properties will be prefixed with an index in square brackets.
        if (provider.ContainsPrefix("[0]"))
        {
            // We have a list.  Since that is what the controller method is 
            // expecting, just use the default model binder to do the work for us.
            return ModelBinders.Binders.DefaultBinder.BindModel(controllerContext, bindingContext);
        }
        else
        {
            // We have a single object.
            // Bind it manually and return it in a list.
            ClassA a = new ClassA();
            a.ID = GetValue<int>(provider, "ID");
            a.FirstName = GetValue<string>(provider, "FirstName");
            a.LastName = GetValue<string>(provider, "LastName");
            return new List<ClassA> { a };
        }
    }

    private T GetValue<T>(IValueProvider provider, string key)
    {
        ValueProviderResult result = provider.GetValue(key);
        return (result != null ? (T)result.ConvertTo(typeof(T)) : default(T));
    }
}

このカスタム バインダーを MVC パイプラインに挿入するにApplication_Start()は、Global.asax.cs.

ModelBinders.Binders.Add(typeof(List<ClassA>), new CustomModelBinder());

明らかに、これは一般的な解決策ではありません。それは処理するために特別に調整されており、他にはClassA何もありません。あらゆるタイプのリストに対してこの「単一またはリスト」の状況を処理する一般的なソリューションを作成できると信じなければなりませんが、それははるかに複雑で、この回答の範囲を超えています。そのためには、おそらくカスタムも作成する必要がありますIModelBinderProvider。そのレベルまで上げたい場合は、自分で調査する必要があります。これは、バインドが舞台裏でどのように機能するかについての洞察を与える可能性のあるMSDN の記事です。

于 2013-09-05T23:36:40.370 に答える
1

API を変更せずに、このロジックを JavaScript コードに追加して、常に同じ型をリストとして渡すことをお勧めします。メンテナンスとテストが容易になります。

if(typeof(myjsonarray) === 'object'){
    myjsonarray = [myjsonarray];
}
于 2013-09-05T19:57:02.137 に答える