Conditionally Required Property Using Data Annotations

Conditionally required property using data annotations

Out of the box I think this is still not possible.

But I found this promising article about Mvc.ValidationToolkit (also here, unfortunately this is only alpha, but you probably could also just extract the method(s) you need from this code and integrate it on your own), it contains the nice sounding attribute RequiredIf which seems to match exactly your cause:

  • you download the project from the linked zip and build it
  • get the built dll from your build folder and reference it in the project you are using
  • unfortunately this seems to require reference to MVC, too (easiest way to have that is starting an MVC-Project in VS or install-package Microsoft.AspNet.Mvc)
  • in the files where you want to use it, you add using Mvc.ValidationToolkit;
  • then you are able to write things like [RequiredIf("DocumentType", 2)] or [RequiredIf("DocumentType", 1)], so objects are valid if neither name or name2 are supplied as long as DocumentType is not equal to 1 or 2

Conditional data annotation

You can make your model inherit from IValidatableObject and then put your custom logic into the Validate method. You'll have to remove the RequredAttribute from the property as well. You will have to write some custom javascript to validate this rule on the client as the Validate method doesn't translate into the unobtrusive validation framework. Note I changed your properties to strings to avoid casting.

Also, if you have other validation errors from attributes, those will fire first and prevent the Validate method from being run so you only detect these errors if the attribute-based validation is ok.

public class Party : IValidatableObject
{
[DisplayName("Your surname")]
public string surname { get; set; }

[DisplayName("Type")]
public string party_type { get; set; }
...

public IEnumerable<ValidationResult> Validate( ValidationContext context )
{
if (party_type == "P" && string.IsNullOrWhitespace(surname))
{
yield return new ValidationResult("Surname is required unless the party is for an organization" );
}
}
}

On the client you can do something like:

 <script type="text/javascript">
$(function() {
var validator = $('form').validate();
validator.rules('add', {
'surname': {
required: {
depends: function(element) {
return $('[name=party_type]').val() == 'P';
}
},
messages: {
required: 'Surname is required unless the party is for an organization.'
}
}
});
});
</script>

RequiredIf required for check validation required on data dataannotations

Create a class as below:

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

namespace BusinessModels
{
public class RequiredIfAttribute : ValidationAttribute, IClientValidatable
{
private readonly string _condition;

public RequiredIfAttribute(string condition)
{
_condition = condition;
}

protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
Delegate conditionFunction = CreateExpressionDelegate(validationContext.ObjectType, _condition);

bool conditionMet = (bool) conditionFunction.DynamicInvoke(validationContext.ObjectInstance);

if (conditionMet)
{
if (value == null)
{
return new ValidationResult(FormatErrorMessage(null));
}
}
return null;
}

private Delegate CreateExpressionDelegate(Type objectType, string expression)
{
// TODO - add caching
var lambdaExpression = CreateExpression(objectType, expression);
Delegate func = lambdaExpression.Compile();
return func;
}

private LambdaExpression CreateExpression(Type objectType, string expression)
{
// TODO - add caching
LambdaExpression lambdaExpression =
System.Linq.Dynamic.DynamicExpression.ParseLambda(
objectType, typeof(bool), expression);
return lambdaExpression;
}

public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
string errorMessage = FormatErrorMessage(metadata.GetDisplayName());

Expression expression = CreateExpression(metadata.ContainerType, _condition);
var visitor = new JavascriptExpressionVisitor();
string javascriptExpression = visitor.Translate(expression);

var clientRule = new ModelClientValidationRule
{
ErrorMessage = errorMessage,
ValidationType = "requiredif",
ValidationParameters =
{
{ "expression", javascriptExpression }
}
};

return new[] { clientRule };
}

}
}

and call on property like as below:

[RequiredIf("property_name==\"prop_value\"", ErrorMessageResourceType = typeof(Resources.resfilename), ErrorMessageResourceName = "ErrMessage")]
public int? propertyname { get; set; }

How to conditionally validate in Data Annotation in MVC?

Updated your Validation to validate if the dependent property is matched with target value then do the validation.

[AttributeUsage(AttributeTargets.Property)]
public class ValidateMustHaveAtLeastOne : ValidationAttribute, IClientValidatable {

#region Construnctor
public ValidateMustHaveAtLeastOne(string groupName, string validationMessage, string dependentProperty, object targetValue) {
GroupName = groupName;
ValidationMessage = validationMessage;

DependentProperty = dependentProperty;
TargetValue = targetValue;
}
#endregion

#region Properties
/// <summary>
/// Name of the group of the properties
/// </summary>
public string GroupName { get; private set; }

/// <summary>
/// Vaidation message
/// </summary>
public string ValidationMessage { get; private set; }

/// <summary>
/// Gets or sets the dependent property.
/// </summary>
/// <value>
/// The dependent property.
/// </value>
public string DependentProperty { get; set; }

/// <summary>
/// Gets or sets the target value.
/// </summary>
/// <value>
/// The target value.
/// </value>
public object TargetValue { get; set; }

#endregion

#region Public overrides
/// <summary>
/// Validates the group of properties.
/// </summary>
/// <param name="value">The value to validate.</param>
/// <param name="context">The context information about the validation operation.</param>
/// <returns>An instance of the ValidationResult class.</returns>
protected override ValidationResult IsValid(object value, ValidationContext context) {
var containerType = validationContext.ObjectInstance.GetType();
var field = containerType.GetProperty(this.DependentProperty);
if (field != null)
{
// get the value of the dependent property
var dependentvalue = field.GetValue(validationContext.ObjectInstance, null);

// compare the value against the target value
if ((dependentvalue == null && this.TargetValue == null)
|| (dependentvalue != null && dependentvalue.Equals(this.TargetValue)))
{
foreach (var property in GetGroupProperties(context.ObjectType)) {
var propertyValue = property.GetValue(context.ObjectInstance, null);
if (propertyValue != null) {
return null;
}
}
return new ValidationResult(ValidationMessage);
}
}

return ValidationResult.Success;
}
#endregion

#region Implementation of IClientValidateable
/// <summary>
/// To enable client side implementation of same validtion rule.
/// </summary>
/// <param name="metadata">The model metadata.</param>
/// <param name="context">The controller context.</param>
/// <returns>The client validation rules for this validator.</returns>

public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context) {
var groupProperties = GetGroupProperties(metadata.ContainerType);
List<string> propertyNames = new List<string>();
foreach (var property in groupProperties) {
propertyNames.Add(property.Name);
}
var rule = new ModelClientValidationRule {
ErrorMessage = this.ValidationMessage
};
rule.ValidationType = string.Format("group", GroupName.ToLower());
rule.ValidationParameters["propertynames"] = string.Join(",", propertyNames);

string depProp = this.BuildDependentPropertyId(metadata, context as ViewContext);
// find the value on the control we depend on; if it's a bool, format it javascript style
string targetValue = (this.TargetValue ?? string.Empty).ToString();
if (this.TargetValue.GetType() == typeof(bool))
{
targetValue = targetValue.ToLower();
}

rule.ValidationParameters["dependentproperty"] = depProp;
rule.ValidationParameters["targetvalue"] = targetValue;
yield return rule; // yield key word is used as return type is declared as IEnumerable<ModelClientValidationRule>
}
#endregion

/// Returns the group of properties that implements this attribute
/// </summary>
/// <param name="type">Type of the Model</param>
/// <returns>List of properties</returns>
private IEnumerable<PropertyInfo> GetGroupProperties(Type type) {
var propertyInfo = new List<PropertyInfo>();
foreach (PropertyInfo property in type.GetProperties()) {
if (property.GetCustomAttributes(typeof(ValidateMustHaveAtLeastOne), false).GetLength(0) > 0) {
propertyInfo.Add(property);
}
}
return propertyInfo;
}

private string BuildDependentPropertyId(ModelMetadata metadata, ViewContext viewContext)
{
string depProp = viewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(this.DependentProperty);

// This will have the name of the current field appended to the beginning, because the TemplateInfo's context has had this fieldname appended to it.
var thisField = metadata.PropertyName + "_";
if (depProp.StartsWith(thisField, StringComparison.OrdinalIgnoreCase))
{
depProp = depProp.Substring(thisField.Length);
}

return depProp;
}

}

Usage

 [ValidateMustHaveAtLeastOne("AtLeastOneShoudlbeThere", "", "PasswordCreationStatus", false)]

Conditional Range Data Annotation in .Net Core

You can implement your own custom validation attribute for this case.

Check out https://learn.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-5.0#custom-attributes.

You will have to take the bool flag (checkbox on or off) along with the range values as 'inputs' for that custom class.

Should be something like that:

public class NumberRangeByConditionAttribute : ValidationAttribute
{
float _minRange, _maxRange;

public NumberRangeByConditionAttribute (bool isChecked, float minWhenChecked, float maxWhenChecked, float minWhenUnchecked, float maxWhenUnchecked)
{
if (isChecked)
{
_minRange = minWhenChecked;
_maxRange = maxWhenChecked;
}
else
{
_minRange = minWhenUnchecked;
_maxRange = maxWhenUnchecked;
}
}

public string GetErrorMessage() =>
$"Number should be between {_minRange} and {_maxRange}.";

protected override ValidationResult IsValid(object value,
ValidationContext validationContext)
{
var number = (float)validationContext.ObjectInstance;

if (number > _maxRange || number < _minRange)
{
return new ValidationResult(GetErrorMessage());
}

return ValidationResult.Success;
}
}


Related Topics



Leave a reply



Submit