web-dev-qa-db-ja.com

DataAnnotationsの「NotRequired」属性

モデルはちょっと複雑です。

いくつかのプロパティを持つUserViewModelがあり、そのうちの2つはHomePhoneWorkPhoneです。タイプPhoneViewModelの両方。 PhoneViewModelには、CountryCodeAreaCode、およびNumberのすべての文字列があります。 CountryCodeをオプションにしたいのですが、AreaCodeNumberは必須です。

これはうまくいきます。私の問題は、UserViewModelWorkPhoneは必須であり、HomePhoneは必須ではないということです。

とにかく、Requireプロパティに属性を設定することで、PhoneViewModelHomeWork属性を無効にできますか?

私はこれを試しました:

[ValidateInput(false)]

しかし、それはクラスとメソッドのためだけです。

コード:

public class UserViewModel
{
    [Required]
    public string Name { get; set; }

    public PhoneViewModel HomePhone { get; set; }

    [Required]    
    public PhoneViewModel WorkPhone { get; set; }
}

public class PhoneViewModel
{
    public string CountryCode { get; set; }

    public string AreaCode { get; set; }

    [Required]
    public string Number { get; set; }
}
12
Diego

[アイデアをより明確にするために2012年5月24日に更新]

これが正しいアプローチかどうかはわかりませんが、概念を拡張して、より一般的で再利用可能なアプローチを作成できると思います。

ASP.NET MVCでは、検証はバインド段階で行われます。サーバーにフォームを投稿する場合、DefaultModelBinderは、リクエスト情報からモデルインスタンスを作成し、検証エラーをModelStateDictionaryに追加するものです。

あなたの場合、バインディングがHomePhoneで発生する限り、検証が起動し、私は多くのことを行うことができないと思いますこれについては、カスタム検証属性または同様の種類を作成します。

私が考えているのは、(市外局番、国コード、数値または空)の形式で使用できる値がない場合、HomePhoneプロパティのモデルインスタンスをまったく作成しないことです。バインディングを制御します。検証を制御します。そのために、カスタムモデルバインダーを作成する必要があります。

カスタムモデルバインダーでは、プロパティがHomePhoneであるかどうか、フォームにそのプロパティの値が含まれているかどうか、含まれていない場合はチェックしています。プロパティをバインドしないでください。HomePhoneの検証は行われません。簡単に言うと、HomePhoneの値はUserViewModelではnullになります。

  public class CustomModelBinder : DefaultModelBinder
  {
      protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
      {
        if (propertyDescriptor.Name == "HomePhone")
        {
          var form = controllerContext.HttpContext.Request.Form;

          var countryCode = form["HomePhone.CountryCode"];
          var areaCode = form["HomePhone.AreaCode"];
          var number = form["HomePhone.Number"];

          if (string.IsNullOrEmpty(countryCode) && string.IsNullOrEmpty(areaCode) && string.IsNullOrEmpty(number))
            return;
        }

        base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
      }
  }

最後に、カスタムモデルバインダーをglobal.asax.csに登録する必要があります。

  ModelBinders.Binders.Add(typeof(UserViewModel), new CustomModelBinder());

これで、UserViewModelをパラメーターとして受け取るアクションができました。

 [HttpPost]
 public Action Post(UserViewModel userViewModel)
 {

 }

カスタムモデルバインダーが機能し、フォームはHomePhoneの-​​areacode、countrycode、numberの値を投稿しません。検証エラーは発生せず、userViewModel.HomePhone 無効です。フォームがこれらのプロパティの値のいずれかを少なくとも投稿する場合、検証は期待どおりにHomePhoneに対して行われます。

5
VJAI

私は動的な注釈を行うこの驚くべきnugetを使用しています: ExpressiveAnnotations

これまでは不可能だったことができるようになります。

[AssertThat("ReturnDate >= Today()")]
public DateTime? ReturnDate { get; set; }

あるいは

public bool GoAbroad { get; set; }
[RequiredIf("GoAbroad == true")]
public string PassportNumber { get; set; }

更新:ユニットテストで注釈をコンパイルして、エラーが存在しないことを確認します

@diegoが述べたように、これは文字列でコードを書くのは恐ろしいかもしれませんが、コンパイルエラーを探すすべての検証をユニットテストするために使用するものは次のとおりです。

namespace UnitTest
{
    public static class ExpressiveAnnotationTestHelpers
    {
        public static IEnumerable<ExpressiveAttribute> CompileExpressiveAttributes(this Type type)
        {
            var properties = type.GetProperties()
                .Where(p => Attribute.IsDefined(p, typeof(ExpressiveAttribute)));
            var attributes = new List<ExpressiveAttribute>();
            foreach (var prop in properties)
            {
                var attribs = prop.GetCustomAttributes<ExpressiveAttribute>().ToList();
                attribs.ForEach(x => x.Compile(prop.DeclaringType));
                attributes.AddRange(attribs);
            }
            return attributes;
        }
    }
    [TestClass]
    public class ExpressiveAnnotationTests
    {
        [TestMethod]
        public void CompileAnnotationsTest()
        {
            // ... or for all assemblies within current domain:
            var compiled = Assembly.Load("NamespaceOfEntitiesWithExpressiveAnnotations").GetTypes()
                .SelectMany(t => t.CompileExpressiveAttributes()).ToList();

            Console.WriteLine($"Total entities using Expressive Annotations: {compiled.Count}");

            foreach (var compileItem in compiled)
            {
                Console.WriteLine($"Expression: {compileItem.Expression}");
            }

            Assert.IsTrue(compiled.Count > 0);
        }


    }
}
3
Korayem

私はmodelBinderには行きません。カスタムValidationAttributeを使用します:

public class UserViewModel
{
    [Required]
    public string Name { get; set; }

    public HomePhoneViewModel HomePhone { get; set; }

    public WorkPhoneViewModel WorkPhone { get; set; }
}

public class HomePhoneViewModel : PhoneViewModel 
{
}

public class WorkPhoneViewModel : PhoneViewModel 
{
}

public class PhoneViewModel 
{
    public string CountryCode { get; set; }

    public string AreaCode { get; set; }

    [CustomRequiredPhone]
    public string Number { get; set; }
}

その後:

[AttributeUsage(AttributeTargets.Property]
public class CustomRequiredPhone : ValidationAttribute
{
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        ValidationResult validationResult = null;

        // Check if Model is WorkphoneViewModel, if so, activate validation
        if (validationContext.ObjectInstance.GetType() == typeof(WorkPhoneViewModel)
         && string.IsNullOrWhiteSpace((string)value) == true)
        {
            this.ErrorMessage = "Phone is required";
            validationResult = new ValidationResult(this.ErrorMessage);
        }
        else
        {
            validationResult = ValidationResult.Success;
        }

        return validationResult;
    }
}

はっきりしない場合は説明しますが、一目瞭然だと思います。

2
iappwebdev

いくつかの観察:バインディングが単純なファイル以上のものである場合、次のコードは問題を引き起こします。オブジェクトにネストされたオブジェクトがあり、それをスキップして、一部のファイルがネストされたオブジェクトにバインドされていないことが原因である場合があります。

考えられる解決策は

protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
     {
         if (!propertyDescriptor.Attributes.OfType<RequiredAttribute>().Any())
         {
             var form = controllerContext.HttpContext.Request.Form;

             if (form.AllKeys.Where(k => k.StartsWith(string.Format(propertyDescriptor.Name, "."))).Count() > 0)
             {
                 if (form.AllKeys.Where(k => k.StartsWith(string.Format(propertyDescriptor.Name, "."))).All(
                         k => string.IsNullOrWhiteSpace(form[k])))
                     return;
             }
         }

         base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
     }

どうもありがとう Altaf Khatri

1
lpastor