web-dev-qa-db-ja.com

ASP.Net MVCモデルバインダーが空のJSON配列をnullにバインドするのはなぜですか?

これが私のモデルクラスです:

public class MyModel
{
    public Employees[] MyEmpls{get;set;}
    public int Id{get;set;}
    public OrgName{get;set;}
}

以下のJSON構造オブジェクトをMyEmpls as empty arrayとともにMVCコントローラーに渡します。

["Id":12, "MyEmpls":[], "OrgName":"Kekran Mcran"]

コントローラ

[HttpPost]
public ActionResult SaveOrg(MyModel model)
{
  //model.MyEmpls is null here
}

mode.MyEmplsはnullではなく空のc#配列であると想定しています。空の配列を実現するためにカスタムモデルバインダーは必要ですか?

34
Billa

他の回答のいくつかは質問の意味を逃したと思います:なぜデフォルトのMVCモデルバインダーは空のJson配列を空のC#配列ではなくnullにバインドするのですか?

ええと、なぜ彼らがそれをしたのかは言えませんが、それがどこで起こるのかを示すことができます。 MVCのソースは、CodePlexの http://aspnetwebstack.codeplex.com/SourceControl/latest にあります。探しているファイルは ValueProviderResult.cs であり、次の場所にあります。

    private static object UnwrapPossibleArrayType(CultureInfo culture, object value, Type destinationType)
    {
        if (value == null || destinationType.IsInstanceOfType(value))
        {
            return value;
        }

        // array conversion results in four cases, as below
        Array valueAsArray = value as Array;
        if (destinationType.IsArray)
        {
            Type destinationElementType = destinationType.GetElementType();
            if (valueAsArray != null)
            {
                // case 1: both destination + source type are arrays, so convert each element
                IList converted = Array.CreateInstance(destinationElementType, valueAsArray.Length);
                for (int i = 0; i < valueAsArray.Length; i++)
                {
                    converted[i] = ConvertSimpleType(culture, valueAsArray.GetValue(i), destinationElementType);
                }
                return converted;
            }
            else
            {
                // case 2: destination type is array but source is single element, so wrap element in array + convert
                object element = ConvertSimpleType(culture, value, destinationElementType);
                IList converted = Array.CreateInstance(destinationElementType, 1);
                converted[0] = element;
                return converted;
            }
        }
        else if (valueAsArray != null)
        {
            // case 3: destination type is single element but source is array, so extract first element + convert
            if (valueAsArray.Length > 0)
            {
                value = valueAsArray.GetValue(0);
                return ConvertSimpleType(culture, value, destinationType);
            }
            else
            {
                // case 3(a): source is empty array, so can't perform conversion
                return null;
            }
        }
        // case 4: both destination + source type are single elements, so convert
        return ConvertSimpleType(culture, value, destinationType);
    }
}

興味深い部分は「ケース3」です。

else
{
    // case 3(a): source is empty array, so can't perform conversion
    return null;
}

コンストラクターでモデルの配列を初期化することで、この問題を回避できます。ソースを簡単に読んでも、なぜ空の配列を返せないのか、なぜ返さないのかはわかりませんが、興味深い読み物になるはずです。

32
Richiban

これはC#の参照型のデフォルト値であるため、null値を取得しています。空の配列を取得するには、コンストラクターを使用してモデルの配列を初期化する必要があります。ただし、初期化時に配列のサイズを定義する必要があるため、Listなどの別のタイプのコレクションを使用した方がよい場合があります。

public class MyModel
{
    public List<Employees> MyEmpls{get;set;}
    public int Id{get;set;}
    public OrgName{get;set;}

    public MyModel() 
    {
         MyEmpls = new List<Employees>();
    }
}

次に、jsonから空の配列が渡されると、空のリストが取得されます。

本当に配列を使用する必要がある場合は、サイズで初期化するだけです:

public class MyModel
{
    public Employees[] MyEmpls{get;set;}
    public int Id{get;set;}
    public OrgName{get;set;}

    public MyModel() 
    {
         MyEmpls = new Employees[/*enter size of array in here*/];
    }
}
30
James

Model.MyEmplsをnullとして取得している場合は、サーバー側で条件を作成して、次のような例外の発生を停止できます。

if(model.MyEmpls !=null){
...
}

MyEmplsはカスタムクラス配列であり、[]だけを送信しているため、nullになります。

これがお役に立てば幸いです。

0
Mehmood

値がnullかどうかをチェックするセッターを定義できます

public class MyModel
{
    private _myEmpls{get;set;}
    public Employees[] MyEmpls{
     get{return _myEmpls;}
     set{_myEmpls=(value==null?new List<Employees>():value);}
    }

    public int Id{get;set;}
    public OrgName{get;set;}
}
0
hmd.ai

以下のようにモデルバインダーを作成してみてください

public class MyModelBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        try
        {                
            var request = controllerContext.HttpContext.Request;

            return new MyModel
            {
                MyEmpls = request[] ?? new Employees[0],
                Id = request["Id"] ?? "",
                OrgName = request["OrgName"] ?? ""

            };
        }
        catch 
        {
            //do required exception handling
        }
    }
}

Application_Startでモデルバインダーを登録します。

ModelBinders.Binders.Add(typeof(MyModel), new MyModelBinder())

そしてコントローラを次のように変更します

[HttpPost]
public ActionResult SaveOrg([ModelBinder(typeof(MyModelBinder))] MyModel model)
{
  //model.MyEmpls is null here
}
0
HCJ
[HttpPost]
public ActionResult SaveOrg(MyModel model)
{
    var serializer = new JavaScriptSerializer();
    var stream = System.Web.HttpContext.Current.Request.InputStream;
    var reader = new StreamReader(stream);
    stream.Position = 0;
    var json = reader.ReadToEnd();
    model= serializer.Deserialize<MyModel>(json);
    //model.MyEmpls is [] here
}

JavaScriptSerializerは空の配列を適切にデシリアライズします。そのため、渡されたモデルを無視して、入力要求ストリームから再構築できます。おそらく正しい方法ではないかもしれませんが、一度だけ実行する必要がある場合は、いくらかの労力を節約できます。 System.Web.Extensionsを参照する必要があります。

0
Julian Mann