Custom Validation attribute not working on client side - c#

I have create following custom requiredif attribute and added client side script also validation is working on server side. tag is updated with data-val attributes but client side validation is not triggering. Couldn't figure aout what's I am missing here
public class RequiredIfAttribute : ValidationAttribute, IClientModelValidator//ValidationAttribute
{
private string dependentProperty;
private object targetValue;
public RequiredIfAttribute(string _DependentProperty, object _TargetValue)
{
this.dependentProperty=_DependentProperty ;
this.targetValue=_TargetValue ;
}
public void AddValidation(ClientModelValidationContext context)
{
MergeAttribute(context.Attributes, "data-val-requiredif-dependentproperty", dependentProperty);
MergeAttribute(context.Attributes, "data-val-requiredif-targetvalue","true");
MergeAttribute(context.Attributes, "data-val-requiredif",GetErrorMessage(context));
}
bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
{
if (attributes.ContainsKey(key))
{
return false;
}
attributes.Add(key, value);
return true;
}
private string GetErrorMessage(ClientModelValidationContext context)
{
return string.Format("{0} is not a valid",
context.ModelMetadata.GetDisplayName());
}
protected override ValidationResult IsValid(object value, System.ComponentModel.DataAnnotations.ValidationContext validationContext)
{
var propertyTestedInfo = validationContext.ObjectType.GetProperty(this.dependentProperty);
if (propertyTestedInfo == null)
{
return new ValidationResult(string.Format("{0} needs to be exist in this object.", this.dependentProperty));
}
var dependendValue = propertyTestedInfo.GetValue(validationContext.ObjectInstance, null);
if (dependendValue == null)
{
return new ValidationResult(string.Format("{0} needs to be populated.", this.dependentProperty));
}
if (!dependendValue.Equals(this.targetValue))
{
var fieldValue = validationContext.ObjectType.GetProperty(validationContext.MemberName).GetValue(validationContext.ObjectInstance, null);
if (fieldValue != null)
{
return ValidationResult.Success;
}
else
{
return new ValidationResult(string.Format("{0} cannot be null", validationContext.MemberName));
}
}
else
{
// Must be ignored
return ValidationResult.Success;
}
}
}
Javascript Code
<script>
$.validator.addMethod('requiredif',
function (value, element, parameters) {
var id = '#' + parameters['dependentproperty'];
// get the target value (as a string,
// as that's what actual value will be)
var targetvalue = parameters['targetvalue'];
targetvalue =
(targetvalue == null ? '' : targetvalue).toString();
// get the actual value of the target control
// note - this probably needs to cater for more
// control types, e.g. radios
var control = $(id);
var controltype = control.attr('type');
var actualvalue =
controltype === 'checkbox' ?
control.attr('checked').toString() :
control.val();
// if the condition is true, reuse the existing
// required field validator functionality
if (targetvalue === actualvalue)
return $.validator.methods.required.call(
this, value, element, parameters);
return true;
}
);
$.validator.unobtrusive.adapters.add(
'requiredif',
['dependentproperty', 'targetvalue'],
function (options) {
options.rules['requiredif'] = {
dependentproperty: options.params['dependentproperty'],
targetvalue: options.params['targetvalue']
};
options.messages['requiredif'] = options.message;
});
</script>
Generated Tag and class property are as follows
<select data-val-requiredif="ToPositionID is not a valid" data-val-requiredif-dependentproperty="chkToPosReq" data-val-requiredif-targetvalue="true" id="ToPositionID" name="ToPositionID">
Class
public class VMWorkflow
{
[RequiredIf("chkToPosReq", "1")]
public int? ToPositionID { get; set; }
}

Related

ASP.NET MVC custom multiple fields validation

I'm developing an ASP.NET MVC 5.2.3 custom data annotation for validation in Visual Studio 2015. It needs to take any number of fields and ensure that if one has a value, they all must have a value; if they're all null/blank, it should be okay.
A few examples have helped:
ASP.NET MVC implement custom validator use IClientValidatable
MVC Form Validation on Multiple Fields
http://www.macaalay.com/2014/02/24/unobtrusive-client-and-server-side-age-validation-in-mvc-using-custom-data-annotations/
However, I'm not sure how to do the client-side validation where you have an unknown number of fields being validated.
How do you pass that to the client using the implementation of the GetClientValidationRules() method of the IClientValidatable interface?
Also, how do I apply this new data annotation to the properties on my view model? Would it look like this?
[MultipleRequired("AppNumber", "UserId", /* more fields */), ErrorMessage = "Something..."]
[DisplayName("App #")]
public int AppNumber { get; set; }
[DisplayName("User ID")]
public int UserId { get; set; }
Here's as far as I could get with the MultipleRequiredAttribute custom data annotation class:
public class MultipleRequiredAttribute : ValidationAttribute, IClientValidatable
{
private readonly string[] _fields;
public MultipleRequiredAttribute(params string[] fields)
{
_fields = fields;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
// If any field has value, then all must have value
var anyHasValue = _fields.Any(f => !string.IsNullOrEmpty(f));
if (!anyHasValue) return null;
foreach (var field in _fields)
{
var property = validationContext.ObjectType.GetProperty(field);
if (property == null)
return new ValidationResult($"Property '{field}' is undefined.");
var fieldValue = property.GetValue(validationContext.ObjectInstance, null);
if (string.IsNullOrEmpty(fieldValue?.ToString()))
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
return null;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
yield return new ModelClientValidationRule
{
ErrorMessage = ErrorMessage,
ValidationType = "multiplerequired"
};
}
}
Thank you.
In order to get client side validation, you need to pass the values of the 'other properties' in the ModelClientValidationRule by using the .Add() method of the rules ValidationParameters property, and then write the client side scripts to add the rules to the $.validator.
But first there are a few other issues to address with your attribute. First you should execute your foreach loop only if the value of the property you applied the attribute is null. Second, returning a ValidationResult if one of the 'other properties' does not exist is confusing and meaningless to a user and you should just ignore it.
The attribute code should be (note I changed the name of the attribute)
public class RequiredIfAnyAttribute : ValidationAttribute, IClientValidatable
{
private readonly string[] _otherProperties;
private const string _DefaultErrorMessage = "The {0} field is required";
public RequiredIfAnyAttribute(params string[] otherProperties)
{
if (otherProperties.Length == 0) // would not make sense
{
throw new ArgumentException("At least one other property name must be provided");
}
_otherProperties = otherProperties;
ErrorMessage = _DefaultErrorMessage;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value == null) // no point checking if it has a value
{
foreach (string property in _otherProperties)
{
var propertyName = validationContext.ObjectType.GetProperty(property);
if (propertyName == null)
{
continue;
}
var propertyValue = propertyName.GetValue(validationContext.ObjectInstance, null);
if (propertyValue != null)
{
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
}
}
return ValidationResult.Success;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ValidationType = "requiredifany",
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
};
/ pass a comma separated list of the other propeties
rule.ValidationParameters.Add("otherproperties", string.Join(",", _otherProperties));
yield return rule;
}
}
The scripts will then be
sandtrapValidation = {
getDependentElement: function (validationElement, dependentProperty) {
var dependentElement = $('#' + dependentProperty);
if (dependentElement.length === 1) {
return dependentElement;
}
var name = validationElement.name;
var index = name.lastIndexOf(".") + 1;
var id = (name.substr(0, index) + dependentProperty).replace(/[\.\[\]]/g, "_");
dependentElement = $('#' + id);
if (dependentElement.length === 1) {
return dependentElement;
}
// Try using the name attribute
name = (name.substr(0, index) + dependentProperty);
dependentElement = $('[name="' + name + '"]');
if (dependentElement.length > 0) {
return dependentElement.first();
}
return null;
}
}
$.validator.unobtrusive.adapters.add("requiredifany", ["otherproperties"], function (options) {
var element = options.element;
var otherNames = options.params.otherproperties.split(',');
var otherProperties = [];
$.each(otherNames, function (index, item) {
otherProperties.push(sandtrapValidation.getDependentElement(element, item))
});
options.rules['requiredifany'] = {
otherproperties: otherProperties
};
options.messages['requiredifany'] = options.message;
});
$.validator.addMethod("requiredifany", function (value, element, params) {
if ($(element).val() != '') {
// The element has a value so its OK
return true;
}
var isValid = true;
$.each(params.otherproperties, function (index, item) {
if ($(this).val() != '') {
isValid = false;
}
});
return isValid;
});

Checking for Model Errors in Entity Framework

In my controller, I have an action that takes in 3 arguments (primary_key, property, and value) and uses reflection to set the value on corresponding model. (Found by its primary key)
I thought I could catch the model if it was invlaid with ModelState.IsValid but it evaluates as true. Now it goes to db.SaveChanges(); which throws exception.
The ModelState is valid. (Apparently it is no the model instance as found by the primary key and actually refers to my three inputs).
I thought I could check my model for errors with the following line...
if (System.Data.Entity.Validation.DbEntityValidationResult.ValidationErrors.Empty)
But I am getting a "missing object reference" error.
I have no idea what that means. (New to C# and everything else here.) Any help?
EDIT 1 - SHOW MORE CODE:
Validations
[Column("pilot_disembarked")]
[IsDateAfter(testedPropertyName: "Undocked",
allowEqualDates: true,
ErrorMessage = "End date needs to be after start date")]
public Nullable<System.DateTime> PilotDisembarked { get; set; }
Custom Validatior
public sealed class IsDateAfter : ValidationAttribute, IClientValidatable
{
private readonly string testedPropertyName;
private readonly bool allowEqualDates;
public IsDateAfter(string testedPropertyName, bool allowEqualDates = false)
{
this.testedPropertyName = testedPropertyName;
this.allowEqualDates = allowEqualDates;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var propertyTestedInfo = validationContext.ObjectType.GetProperty(this.testedPropertyName);
if (propertyTestedInfo == null)
{
return new ValidationResult(string.Format("unknown property {0}", this.testedPropertyName));
}
var propertyTestedValue = propertyTestedInfo.GetValue(validationContext.ObjectInstance, null);
if (value == null || !(value is DateTime))
{
return ValidationResult.Success;
}
if (propertyTestedValue == null || !(propertyTestedValue is DateTime))
{
return ValidationResult.Success;
}
// Compare values
if ((DateTime)value >= (DateTime)propertyTestedValue)
{
if (this.allowEqualDates)
{
return ValidationResult.Success;
}
if ((DateTime)value > (DateTime)propertyTestedValue)
{
return ValidationResult.Success;
}
}
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
}
Controller Action
[HttpPost]
public ActionResult JsonEdit(string name, int pk, string value)
{
Voyage voyage = db.Voyages.Find(pk);
var property = voyage.GetType().GetProperty(name);
if (Regex.Match(property.PropertyType.ToString(), "DateTime").Success)
{
try
{
if (Regex.Match(value, #"^\d{4}$").Success)
{
var newValue = DateTime.ParseExact(value, "HHmm", System.Globalization.CultureInfo.InvariantCulture);
property.SetValue(voyage, newValue, null);
}
else if (value.Length == 0)
{
property.SetValue(voyage, null, null);
}
else
{
var newValue = DateTime.ParseExact(value, "yyyy/MM/dd HHmm", System.Globalization.CultureInfo.InvariantCulture);
property.SetValue(voyage, newValue, null);
}
}
catch
{
Response.StatusCode = 400;
return Json("Incorrect Time Entry.");
}
}
else
{
var newValue = Convert.ChangeType(value, property.PropertyType);
property.SetValue(voyage, newValue, null);
}
if (ModelState.IsValid)
{
db.SaveChanges();
Response.StatusCode = 200;
return Json("Success!");
}
else
{
Response.StatusCode = 400;
return Json(ModelState.Keys.SelectMany(key => this.ModelState[key].Errors));
}
}
When any value of your model is null at that time ModelState.IsValid.So first check your model data.

not able to get values in client side custom validation function

I am doing client side custom validations using jQuery, I am stuck in getting values from
server side to do operations on client side ....
this is my server side custom validation function
public class SelctedValueCheckAttribute : ValidationAttribute , IClientValidatable
{
public SelctedValueCheckAttribute(string otherProperty): base("{0} is not in correct range")
{
OtherProperty = otherProperty;
}
public string OtherProperty { get; set; }
public SelctedValueCheckAttribute()
{
ErrorMessage = "Values must be in the Given Range";
}
public override string FormatErrorMessage(string name)
{
return "The Entered Value Must be in given range for " + name + "item";
}
protected override ValidationResult IsValid(object firstValue, ValidationContext validationContext)
{
string selecetdItemValue = firstValue as string ;
string userEnteredValue = GetSecondValue(validationContext);
if( string.Equals(selecetdItemValue, "Amount"))
{
int entry = Convert.ToInt32(userEnteredValue);
if (entry < 10 || entry > 20)
{
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
}
else if (string.Equals(selecetdItemValue, "Pound"))
{
int entry = Convert.ToInt32(userEnteredValue);
if (entry < 80 || entry > 90)
{
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
}
else if (string.Equals(selecetdItemValue, "Percent"))
{
int entry = Convert.ToInt32(userEnteredValue);
if (entry < 50 || entry > 60)
{
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
}
return ValidationResult.Success;
}
protected string GetSecondValue(ValidationContext validationContext)
{
var propertyInfo = validationContext.ObjectType.GetProperty(OtherProperty);
if (propertyInfo != null)
{
var secondValue = propertyInfo.GetValue(validationContext.ObjectInstance, null);
return secondValue as string;
}
return null;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
ModelClientValidationRule mcvr = new ModelClientValidationRule();
mcvr.ValidationType = "enteredvaluescheck";
mcvr.ErrorMessage = "Selected Value must be in given range";
mcvr.ValidationParameters.Add("other", OtherProperty);
mcvr.ValidationType = "selectedvaluewithenteredvaluecheck";
yield return mcvr;
}
}
and this is my client side custom validation
jquery.validator.unobtrusive.adapters.addSingleval("selectedvaluewithenteredvaluecheck", "other");
jQuery.validator.addMethod("selectedvaluewithenteredvaluecheck",
function(val,element,other)
{
if(val & other)
{
// here I am not getting the values..
//do I need to write any function to get the values
//is there any other approach that I need to follow
}
How can I get the values in client side function?
Would any one have any idea about this and any suggestions please suggest me.
You miss-spelled jquery and addSingleval. They should be capitalized Q and V.
jQuery.validator.unobtrusive.adapters.addSingleVal("selectedvaluewithenteredvaluecheck", "other");

Get value of a property that was passed in as a string

I am trying to create a custom validation that says "if the otherValue is true, then this value must be greater than 0. I am able to get the value in, but the way I currently have the otherValue set up, I only have the name of the property, not the value. Probably because it passed in as a string. This attribute is going to be on 5 or 6 different properties and each time, it will be calling a different otherValue. Looking for help on how to get the actual value (it is a bool) of otherValue.
Here is my current code:
public class MustBeGreaterIfTrueAttribute : ValidationAttribute, IClientValidatable
{
// get the radio button value
public string OtherValue { get; set; }
public override bool IsValid(object value)
{
// Here is the actual custom rule
if (value.ToString() == "0")
{
if (OtherValue.ToString() == "true")
{
return false;
}
}
// If all is ok, return successful.
return true;
}
======================EDIT=========================
Here is where I am at now, and it works! Now I need a refernce on how to make it so I can put in a different errorMessage when adding the attribute in the model:
public class MustBeGreaterIfTrueAttribute : ValidationAttribute, IClientValidatable
{
// get the radio button value
public string OtherProperty { get; set; }
protected override ValidationResult IsValid(object value, ValidationContext context)
{
var otherPropertyInfo = context.ObjectInstance.GetType();
var otherValue = otherPropertyInfo.GetProperty(OtherProperty).GetValue(context.ObjectInstance, null);
// Here is the actual custom rule
if (value.ToString() == "0")
{
if (otherValue.ToString().Equals("True", StringComparison.InvariantCultureIgnoreCase))
{
return new ValidationResult("Ensure all 'Yes' answers have additional data entered.");
}
}
// If all is ok, return successful.
return ValidationResult.Success;
}
// Add the client side unobtrusive 'data-val' attributes
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule();
rule.ValidationType = "requiredifyes";
rule.ErrorMessage = this.ErrorMessage;
rule.ValidationParameters.Add("othervalue", this.OtherProperty);
yield return rule;
}
}
So I should be able to do this:
[MustBeGreaterIfTrue(OtherProperty="EverHadRestrainingOrder", ErrorMessage="Enter more info on your RO.")]
public int? ROCounter { get; set; }
The ValidationAttribute has a pair of IsValid methods and for your scenario you have to use other guy.
public class MustBeGreaterIfTrueAttribute : ValidationAttribute
{
// name of the OtherProperty. You have to specify this when you apply this attribute
public string OtherPropertyName { get; set; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var otherProperty = validationContext.ObjectType.GetProperty(OtherPropertyName);
if (otherProperty == null)
return new ValidationResult(String.Format("Unknown property: {0}.", OtherPropertyName));
var otherPropertyValue = otherProperty.GetValue(validationContext.ObjectInstance, null);
if (value.ToString() == "0")
{
if (otherPropertyValue != null && otherPropertyValue.ToString() == "true")
{
return null;
}
}
return new ValidationResult("write something here");
}
}
Example Usage:
public class SomeModel
{
[MustBeGreaterIf(OtherPropertyName="Prop2")]
public string Prop1 {get;set;}
public string Prop2 {get;set;}
}
Ref: http://www.concurrentdevelopment.co.uk/blog/index.php/2011/01/custom-validationattribute-for-comparing-properties/
I'm having a little bit of trouble understanding what you want to do, but,
If you want to get the boolean representation of OtherValue, you could do
bool.Parse(this.OtherValue);
also, for string comparison, it sometimes helps to take case and whitespace out of the picture
if (this.OtherValue.ToString().ToLower().Trim() == "true")
validationContext has ObjectInstance which is an instance of SomeModel class so you can use:
(validationContext.ObjectInstance as SomeModel).Prop1/Prop2

MetadataType attributes are being Ignored in a Custom Validator

(.NET 4.0/WebForms/EF 4.1 POCO)
Hi,
I´m using a Custom Validator to use DataAnnotations with WebForms (source code is bellow).
Everything goes fine when I use DataAnnotations directly in the generated classes. But when I use the DataAnnotations in a Metadata class with a partial class, the DataAnnotations attributes seems to be bypassed in the validation. I know that the metadata was properly recognized, because when I save the data in the DbContext it is being validated and EntityValidationErrors returns the validated errors.
I did some searches and found this: (http://stackoverflow.com/questions/2657358/net-4-rtm-metadatatype-attribute-ignored-when-using-validator/2657644#2657644). Unfortunately my implementation did not worked. May be I don´t know where to call it. I´ve tried to call it in the constructor of the Metadata class but it didn´t work.
public static class MetadataTypesRegister
{
static bool installed = false;
static object installedLock = new object();
public static void Install()
{
if (installed)
{
return;
}
lock (installedLock)
{
if (installed)
{
return;
}
// TODO: for debug purposes only (please remove in production)
Assembly assembly = Assembly.GetExecutingAssembly();
Type[] types = assembly.GetTypes();
//------
foreach (Type type in Assembly.GetExecutingAssembly().GetTypes())
{
foreach (MetadataTypeAttribute attrib in type.GetCustomAttributes(typeof(MetadataTypeAttribute), true))
{
TypeDescriptor.AddProviderTransparent(
new AssociatedMetadataTypeTypeDescriptionProvider(type, attrib.MetadataClassType), type);
}
}
installed = true;
}
}
}
The model to be validated is located in DataLayer.dll and the DataAnnotationsValidator class is in Common.dll.
This is my DataAnnotationsValidator class:
[ToolboxData("<{0}:DataAnnotationsValidator runat=server></{0}:DataAnnotationsValidator>")]
public class DataAnnotationsValidator : BaseValidator
{
private string _propertyName = string.Empty;
public string PropertyName
{
get { return _propertyName; }
set { _propertyName = value; }
}
public string _sourceType = string.Empty;
public string SourceType
{
get { return _sourceType; }
set { _sourceType = value; }
}
public ValidationDataType _type = ValidationDataType.String;
public ValidationDataType Type
{
get { return _type; }
set { _type = value; }
}
public string _cssError = string.Empty;
public string CssError
{
get { return _cssError; }
set { _cssError = value; }
}
protected override bool EvaluateIsValid()
{
// get specified type for reflection
Type objectType = System.Type.GetType(_sourceType, true, true);
// get a property to validate
PropertyInfo prop = objectType.GetProperty(_propertyName);
// get the control to validate
TextBox control = this.FindControl(this.ControlToValidate) as TextBox;
object valueToValidate = null;
if (control.Text != String.Empty)
{
if (Type == ValidationDataType.Double)
valueToValidate = double.Parse(control.Text);
else if (Type == ValidationDataType.Integer)
valueToValidate = int.Parse(control.Text);
else if (Type == ValidationDataType.Date)
valueToValidate = DateTime.Parse(control.Text);
else if (Type == ValidationDataType.Currency)
valueToValidate = decimal.Parse(control.Text);
else
valueToValidate = control.Text;
}
CultureInfo currentCulture = Thread.CurrentThread.CurrentCulture;
bool result = true;
try
{
Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
// The custom validator can return only one error message. Because the field model being validated can have more than
// one DataAnnotation validation (Required, Range, RegularExpression, etc.) the DataAnnotationsValidator will return only the first
// error message that it evaluates.
foreach (ValidationAttribute attr in prop.GetCustomAttributes(typeof(ValidationAttribute), true).OfType<ValidationAttribute>())
{
Thread.CurrentThread.CurrentCulture = currentCulture;
if (!attr.IsValid(valueToValidate))
{
result = false;
var displayNameAttr = prop.GetCustomAttributes(typeof(DisplayNameAttribute), true).OfType<DisplayNameAttribute>().FirstOrDefault();
string displayName = displayNameAttr == null ? prop.Name : displayNameAttr.DisplayName;
ErrorMessage = attr.FormatErrorMessage(displayName);
break;
}
}
}
finally
{
Thread.CurrentThread.CurrentCulture = currentCulture;
if (result)
{
if (!string.IsNullOrEmpty(CssError))
control.RemoveCssClass(CssError);
}
else
{
if (!string.IsNullOrEmpty(CssError))
control.AddCssClass(CssError);
}
}
return result;
}
}
Thanks!!
I´ve found a solution here (http://stackoverflow.com/questions/5600707/how-do-you-do-web-forms-model-validation).
I modify the code of EvaluateIsValid method to include the code bellow to looking for Metadata Attributes:
// get specified type for reflection
Type objectType = System.Type.GetType(_sourceType, true, true);
// check for the types that have MetadataType attribute because
// it is they who have the DataAnnotations attributes
IEnumerable<MetadataTypeAttribute> mt = objectType.GetCustomAttributes(typeof(MetadataTypeAttribute), false).OfType<MetadataTypeAttribute>();
if (mt.Count() > 0)
{
objectType = mt.First().MetadataClassType;
}
And everything goes fine!

Categories

Resources