web-dev-qa-db-ja.com

ジェネリックリストプロパティに必要な属性

[必須]属性をList <>プロパティに配置することは可能ですか?

POSTの汎用リストにバインドし、プロパティに0個のアイテムがある場合にModelState.IsValid()を失敗させることができるかどうか疑問に思っていましたか?

31
Nick Reeve

Required属性をリストスタイルのプロパティに追加しても、実際には期待どおりの結果が得られません。リストが作成されていない場合は文句を言いますが、リストに0個のアイテムが含まれている場合は文句を言いません。

ただし、独自のデータ注釈属性を導出し、リストでCount> 0をチェックするのは簡単なはずです。次のようなもの(まだテストされていません):

_[AttributeUsage(AttributeTargets.Property)]
public sealed class CannotBeEmptyAttribute : ValidationAttribute
{
    private const string defaultError = "'{0}' must have at least one element.";
    public CannotBeEmptyAttribute ( ) : base(defaultError) //
    { 
    }

    public override bool IsValid ( object value )
    {
      IList list = value as IList;
      return ( list != null && list.Count > 0 );
    }

    public override string FormatErrorMessage ( string name )
    {
        return String.Format(this.ErrorMessageString, name);
    }
}
_

編集:

また、ビューでリストをバインドする方法にも注意する必要があります。たとえば、_List<String>_を次のようなビューにバインドする場合:

_<input name="ListName[0]" type="text" />
<input name="ListName[1]" type="text" />
<input name="ListName[2]" type="text" />
<input name="ListName[3]" type="text" />
<input name="ListName[4]" type="text" />
_

MVCモデルバインダーは常にリストに5つの要素を入れます。すべて_String.Empty_です。これがビューの動作方法である場合、Reflectionを使用してジェネリック型パラメーターをプルし、各リスト要素をdefault(T)などと比較するなど、属性をもう少し複雑にする必要があります。

より良い代替方法は、jQueryを使用して入力要素を動的に作成することです。

32

ミニマリストの例を探している人のために:

[AttributeUsage(AttributeTargets.Property)]
public sealed class CannotBeEmptyAttribute : RequiredAttribute
{
    public override bool IsValid(object value)
    {
        var list = value as IEnumerable;
        return list != null && list.GetEnumerator().MoveNext();
    }
}

これは、受け入れられた回答から変更されたコードです。 IEnumerableはSystem.Collections階層の上位にあるため、質問の場合、さらに多くの場合に適しています。さらに、RequiredAttributeから動作を継承するため、明示的にコーディングする必要はありません。

23
moudrick

C#6.0(およびそれ以降)を使用していて、ワンライナーを探している人の場合:

[AttributeUsage(AttributeTargets.Property)]
public sealed class CannotBeEmptyAttribute : RequiredAttribute
{
    public override bool IsValid(object value) => (value as IEnumerable)?.GetEnumerator().MoveNext() ?? false;
}
4
Nick N.

私の要件に合わせて@moudrickの実装を変更しました

リストとチェックボックスリストに必要な検証属性

[AttributeUsage(AttributeTargets.Property)]
public sealed class CustomListRequiredAttribute : RequiredAttribute
{
    public override bool IsValid(object value)
    {
        var list = value as IEnumerable;
        return list != null && list.GetEnumerator().MoveNext();
    }
}

チェックボックスリストがある場合

[AttributeUsage(AttributeTargets.Property)]
public sealed class CustomCheckBoxListRequiredAttribute : RequiredAttribute
{
    public override bool IsValid(object value)
    {
        bool result = false;

        var list = value as IEnumerable<CheckBoxViewModel>;
        if (list != null && list.GetEnumerator().MoveNext())
        {
            foreach (var item in list)
            {
                if (item.Checked)
                {
                    result = true;
                    break;
                }
            }
        }

        return result;
    }
}

これが私のビューモデルです

public class CheckBoxViewModel
{        
    public string Name { get; set; }
    public bool Checked { get; set; }
}

使用法

[CustomListRequiredAttribute(ErrorMessage = "Required.")]
public IEnumerable<YourClass> YourClassList { get; set; }

[CustomCheckBoxListRequiredAttribute(ErrorMessage = "Required.")]
public IEnumerable<CheckBoxViewModel> CheckBoxRequiredList { get; set; }
2
dnxit