RequiredIf Conditional Validation Attribute

你。 提交于 2019-11-26 01:27:45

问题


I was looking for some advice on the best way to go about implementing a validation attribute that does the following.

Model

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

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

}

I want to have atleast prop1 prop2 prop3 with a value and if prop3 is the only value filled it it should not equal false. How would i go about writing a validation attribute(s?) for this?

Thanks for any help!


回答1:


I had the same problem yesterday, but I did it in a very clean way which works for both client side and server side validation.

Condition: Based on the value of other property in the model, you want to make another property required. Here is the code

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 is the property on which you want to make your condition
DesiredValue is the particular value of the PropertyName (property) for which your other property has to be validated for required

Say you have the following:

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; }
}

At last but not the least, register adapter for your attribute so that it can do client side validation (I put it in global.asax, Application_Start)

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

EDITED

Some people was complaining that the client side fires no matter what or it does not work. So I modified the above code to do conditional client side validation with javascript as well. For this case you don't need to register adapter

 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;
        }
    }

And finally the javascript ( bundle it and renderit...put it in its own script file)

$.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;
});

You need obviously the unobstrusive validate jquery to be included as requirement




回答2:


I know the topic was asked some time ago, but recently I had faced similar issue and found yet another, but in my opinion a more complete solution. I decided to implement mechanism which provides conditional attributes to calculate validation results based on other properties values and relations between them, which are defined in logical expressions.

Using it you are able to achieve the result you asked about in the following manner:

[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; }

More information about ExpressiveAnnotations library can be found here. It should simplify many declarative validation cases without the necessity of writing additional case-specific attributes or using imperative way of validation inside controllers.




回答3:


I got it to work on ASP.NET MVC 5

I saw many people interested in and suffering from this code and i know it's really confusing and disrupting for the first time.

Notes

  • worked on MVC 5 on both server and client side :D
  • I didn't install "ExpressiveAnnotations" library at all.
  • I'm taking about the Original code from "@Dan Hunex", Find him above

Tips To Fix This Error

"The type System.Web.Mvc.RequiredAttributeAdapter must have a public constructor which accepts three parameters of types System.Web.Mvc.ModelMetadata, System.Web.Mvc.ControllerContext, and ExpressiveAnnotations.Attributes.RequiredIfAttribute Parameter name: adapterType"

Tip #1: make sure that you're inheriting from 'ValidationAttribute' not from 'RequiredAttribute'

 public class RequiredIfAttribute : ValidationAttribute, IClientValidatable { ...}

Tip #2: OR remove this entire line from 'Global.asax', It is not needed at all in the newer version of the code (after edit by @Dan_Hunex), and yes this line was a must in the old version ...

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

Tips To Get The Javascript Code Part Work

1- put the code in a new js file (ex:requiredIfValidator.js)

2- warp the code inside a $(document).ready(function(){........});

3- include our js file after including the JQuery validation libraries, So it look like this now :

@Scripts.Render("~/bundles/jqueryval")
<script src="~/Content/JS/requiredIfValidator.js"></script>

4- Edit the C# code

from

rule.ValidationParameters["dependentproperty"] = (context as ViewContext).ViewData.TemplateInfo.GetFullHtmlFieldId(PropertyName);

to

rule.ValidationParameters["dependentproperty"] = PropertyName;

and from

if (dependentValue.ToString() == DesiredValue.ToString())

to

if (dependentValue != null && dependentValue.ToString() == DesiredValue.ToString())

My Entire Code Up and Running

Global.asax

Nothing to add here, keep it clean

requiredIfValidator.js

create this file in ~/content or in ~/scripts folder

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


$(document).ready(function ()
{

    $.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;
    });
});

_Layout.cshtml or the View

@Scripts.Render("~/bundles/jqueryval")
<script src="~/Content/JS/requiredIfValidator.js"></script>

RequiredIfAttribute.cs Class

create it some where in your project, For example in ~/models/customValidation/

    using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;

namespace Your_Project_Name.Models.CustomValidation
{
    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 != null && 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"] = PropertyName;
            rule.ValidationParameters["desiredvalue"] = DesiredValue is bool ? DesiredValue.ToString().ToLower() : DesiredValue;

            yield return rule;
        }
    }
}

The Model

    using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web;
using Your_Project_Name.Models.CustomValidation;

namespace Your_Project_Name.Models.ViewModels
{

    public class CreateOpenActivity
    {
        public Nullable<int> ORG_BY_CD { get; set; }

        [RequiredIf("ORG_BY_CD", "5", ErrorMessage = "Coordinator ID is required")] // This means: IF 'ORG_BY_CD' is equal 5 (for the example)  > make 'COR_CI_ID_NUM' required and apply its all validation / data annotations
        [RegularExpression("[0-9]+", ErrorMessage = "Enter Numbers Only")]
        [MaxLength(9, ErrorMessage = "Enter a valid ID Number")]
        [MinLength(9, ErrorMessage = "Enter a valid ID Number")]
        public string COR_CI_ID_NUM { get; set; }
    }
}

The View

Nothing to note here actually ...

    @model Your_Project_Name.Models.ViewModels.CreateOpenActivity
@{
    ViewBag.Title = "Testing";
}

@using (Html.BeginForm()) 
{
    @Html.AntiForgeryToken()

    <div class="form-horizontal">
        <h4>CreateOpenActivity</h4>
        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })

        <div class="form-group">
            @Html.LabelFor(model => model.ORG_BY_CD, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.ORG_BY_CD, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.ORG_BY_CD, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.COR_CI_ID_NUM, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.COR_CI_ID_NUM, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.COR_CI_ID_NUM, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </div>
    </div>
}

I may upload a project sample for this later ...

Hope this was helpful

Thank You




回答4:


Expanding on the notes from Adel Mourad and Dan Hunex, I amended the code to provide an example that only accepts values that do not match the given value.

I also found that I didn't need the JavaScript.

I added the following class to my Models folder:

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

    public RequiredIfNotAttribute(String propertyName, Object invalidValue)
    {
        PropertyName = propertyName;
        InvalidValue = invalidValue;
        _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() != InvalidValue.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 = "requiredifnot",
        };
        rule.ValidationParameters["dependentproperty"] = (context as ViewContext).ViewData.TemplateInfo.GetFullHtmlFieldId(PropertyName);
        rule.ValidationParameters["invalidvalue"] = InvalidValue is bool ? InvalidValue.ToString().ToLower() : InvalidValue;

        yield return rule;
    }

I didn't need to make any changes to my view, but did make a change to the properties of my model:

    [RequiredIfNot("Id", 0, ErrorMessage = "Please select a Source")]
    public string TemplateGTSource { get; set; }

    public string TemplateGTMedium
    {
        get
        {
            return "Email";
        }
    }

    [RequiredIfNot("Id", 0, ErrorMessage = "Please enter a Campaign")]
    public string TemplateGTCampaign { get; set; }

    [RequiredIfNot("Id", 0, ErrorMessage = "Please enter a Term")]
    public string TemplateGTTerm { get; set; }

Hope this helps!




回答5:


If you try to use "ModelState.Remove" or "ModelState["Prop"].Errors.Clear()" the "ModelState.IsValid" stil returns false.

Why not just removing the default "Required" Annotation from Model and make your custom validation before the "ModelState.IsValid" on Controller 'Post' action? Like this:

if (!String.IsNullOrEmpty(yourClass.Property1) && String.IsNullOrEmpty(yourClass.dependantProperty))            
            ModelState.AddModelError("dependantProperty", "It´s necessary to select some 'dependant'.");



回答6:


The main difference from other solutions here is that this one reuses logic in RequiredAttribute on the server side, and uses required's validation method depends property on the client side:

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;
});


来源:https://stackoverflow.com/questions/7390902/requiredif-conditional-validation-attribute

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!