67

次のことを行う検証属性を実装するための最良の方法について、アドバイスを探していました。

モデル

public class MyInputModel 
{
    [Required]
    public int Id {get;set;}

    public string MyProperty1 {get;set;}
    public string MyProperty2 {get;set;}
    public bool MyProperty3 {get;set;}

}

少なくとも prop1 prop2 prop3 に値を設定したいのですが、prop3 が唯一の値である場合、それは false であってはなりません。このための検証属性をどのように記述しますか?

助けてくれてありがとう!

4

7 に答える 7

109

昨日も同じ問題がありましたが、クライアント側とサーバー側の両方の検証で機能する非常にクリーンな方法でそれを行いました。

条件: モデル内の他のプロパティの値に基づいて、別のプロパティを必須にしたい。コードは次のとおりです。

public class RequiredIfAttribute : RequiredAttribute
{
  private String PropertyName { get; set; }
  private Object DesiredValue { get; set; }

  public RequiredIfAttribute(String propertyName, Object desiredvalue)
  {
    PropertyName = propertyName;
    DesiredValue = desiredvalue;
  }

  protected override ValidationResult IsValid(object value, ValidationContext context)
  {
    Object instance = context.ObjectInstance;
    Type type = instance.GetType();
    Object proprtyvalue = type.GetProperty(PropertyName).GetValue(instance, null);
    if (proprtyvalue.ToString() == DesiredValue.ToString())
    {
      ValidationResult result = base.IsValid(value, context);
      return result;
    }
    return ValidationResult.Success;
  }
}

PropertyNameは、条件を作成するプロパティです
DesiredValueは、他のプロパティが必須であるために検証する必要がある PropertyName (プロパティ) の特定の値です

次のものがあるとします。

public enum UserType
{
  Admin,
  Regular
}

public class User
{
  public UserType UserType {get;set;}

  [RequiredIf("UserType",UserType.Admin,
              ErrorMessageResourceName="PasswordRequired", 
              ErrorMessageResourceType = typeof(ResourceString))]
  public string Password { get; set; }
}

最後になりましたが、属性のアダプターを登録して、クライアント側の検証を実行できるようにします (global.asax、Application_Start に入れました)。

DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(RequiredIfAttribute),
                                                      typeof(RequiredAttributeAdapter));

編集済み

一部の人々は、クライアント側が何をしても発火したり、機能しないと不平を言っていました。そこで、上記のコードを変更して、Javascript で条件付きのクライアント側検証も行うようにしました。この場合、アダプタを登録する必要はありません

 public class RequiredIfAttribute : ValidationAttribute, IClientValidatable
    {
        private String PropertyName { get; set; }
        private Object DesiredValue { get; set; }
        private readonly RequiredAttribute _innerAttribute;

        public RequiredIfAttribute(String propertyName, Object desiredvalue)
        {
            PropertyName = propertyName;
            DesiredValue = desiredvalue;
            _innerAttribute = new RequiredAttribute();
        }

        protected override ValidationResult IsValid(object value, ValidationContext context)
        {
            var dependentValue = context.ObjectInstance.GetType().GetProperty(PropertyName).GetValue(context.ObjectInstance, null);

            if (dependentValue.ToString() == DesiredValue.ToString())
            {
                if (!_innerAttribute.IsValid(value))
                {
                    return new ValidationResult(FormatErrorMessage(context.DisplayName), new[] { context.MemberName });
                }
            }
            return ValidationResult.Success;
        }

        public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
        {
            var rule = new ModelClientValidationRule
            {
                ErrorMessage = ErrorMessageString,
                ValidationType = "requiredif",
            };
            rule.ValidationParameters["dependentproperty"] = (context as ViewContext).ViewData.TemplateInfo.GetFullHtmlFieldId(PropertyName);
            rule.ValidationParameters["desiredvalue"] = DesiredValue is bool ? DesiredValue.ToString().ToLower() : DesiredValue;

            yield return rule;
        }
    }

そして最後にJavaScript(バンドルしてrenderit...独自のスクリプトファイルに入れます)

$.validator.unobtrusive.adapters.add('requiredif', ['dependentproperty', 'desiredvalue'], function (options) {
    options.rules['requiredif'] = options.params;
    options.messages['requiredif'] = options.message;
});

$.validator.addMethod('requiredif', function (value, element, parameters) {
    var desiredvalue = parameters.desiredvalue;
    desiredvalue = (desiredvalue == null ? '' : desiredvalue).toString();
    var controlType = $("input[id$='" + parameters.dependentproperty + "']").attr("type");
    var actualvalue = {}
    if (controlType == "checkbox" || controlType == "radio") {
        var control = $("input[id$='" + parameters.dependentproperty + "']:checked");
        actualvalue = control.val();
    } else {
        actualvalue = $("#" + parameters.dependentproperty).val();
    }
    if ($.trim(desiredvalue).toLowerCase() === $.trim(actualvalue).toLocaleLowerCase()) {
        var isValid = $.validator.methods.required.call(this, value, element, parameters);
        return isValid;
    }
    return true;
});

明らかに、目立たない検証jQueryを要件として含める必要があります

于 2013-04-12T15:57:52.833 に答える
33

トピックがしばらく前に尋ねられたことは知っていますが、最近、同様の問題に直面し、さらに別の問題を見つけましたが、私の意見では、より完全な解決策です。論理式で定義された他のプロパティ値とそれらの間の関係に基づいて検証結果を計算するための条件付き属性を提供するメカニズムを実装することにしました。

それを使用すると、次の方法で質問した結果を得ることができます。

[RequiredIf("MyProperty2 == null && MyProperty3 == false")]
public string MyProperty1 { get; set; }

[RequiredIf("MyProperty1 == null && MyProperty3 == false")]
public string MyProperty2 { get; set; }

[AssertThat("MyProperty1 != null || MyProperty2 != null || MyProperty3 == true")]
public bool MyProperty3 { get; set; }

ExpressiveAnnotations ライブラリの詳細については、こちらを参照してください。追加のケース固有の属性を記述したり、コントローラー内で必須の検証方法を使用したりする必要なく、多くの宣言型検証ケースを簡素化する必要があります。

于 2013-08-14T15:27:57.283 に答える
2

"ModelState.Remove" または "ModelState["Prop"].Errors.Clear()" を使用しようとすると、"ModelState.IsValid" スタイルは false を返します。

モデルからデフォルトの「必須」アノテーションを削除して、コントローラーの「Post」アクションの「ModelState.IsValid」の前にカスタム検証を作成しないのはなぜですか? このような:

if (!String.IsNullOrEmpty(yourClass.Property1) && String.IsNullOrEmpty(yourClass.dependantProperty))            
            ModelState.AddModelError("dependantProperty", "It´s necessary to select some 'dependant'.");
于 2016-08-25T12:39:15.813 に答える
1

ここでの他のソリューションとの主な違いは、これRequiredAttributeがサーバー側でロジックを再利用し、クライアント側でrequiredの検証メソッドdependsプロパティを使用することです。

public class RequiredIf : RequiredAttribute, IClientValidatable
{
    public string OtherProperty { get; private set; }
    public object OtherPropertyValue { get; private set; }

    public RequiredIf(string otherProperty, object otherPropertyValue)
    {
        OtherProperty = otherProperty;
        OtherPropertyValue = otherPropertyValue;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        PropertyInfo otherPropertyInfo = validationContext.ObjectType.GetProperty(OtherProperty);
        if (otherPropertyInfo == null)
        {
            return new ValidationResult($"Unknown property {OtherProperty}");
        }

        object otherValue = otherPropertyInfo.GetValue(validationContext.ObjectInstance, null);
        if (Equals(OtherPropertyValue, otherValue)) // if other property has the configured value
            return base.IsValid(value, validationContext);

        return null;
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        var rule = new ModelClientValidationRule();
        rule.ErrorMessage = FormatErrorMessage(metadata.GetDisplayName());
        rule.ValidationType = "requiredif"; // data-val-requiredif
        rule.ValidationParameters.Add("other", OtherProperty); // data-val-requiredif-other
        rule.ValidationParameters.Add("otherval", OtherPropertyValue); // data-val-requiredif-otherval

        yield return rule;
    }
}

$.validator.unobtrusive.adapters.add("requiredif", ["other", "otherval"], function (options) {
    var value = {
        depends: function () {
            var element = $(options.form).find(":input[name='" + options.params.other + "']")[0];
            return element && $(element).val() == options.params.otherval;
        }
    }
    options.rules["required"] = value;
    options.messages["required"] = options.message;
});
于 2017-01-09T23:10:13.723 に答える