Creating a custom validation through DataAnnotations? - c#

Ok so when I want to have a property be validated, I might write something like this:
[Required]
[StringLength(255)]
[DataType(DataType.EmailAddress)]
[RegularExpression(RegexStrings.Email, ErrorMessage = "Email is not valid.")]
[DataMember(IsRequired = true, Name="Email", Order = 1)]
public string Email { get; set; }
I like this because in this case, I point it to the regex strings that we have in our common library and specify an error message if it fails. Simple and clean.
My situation is this. This is in a WCF RESTful service. One of the properties that I want validated in this fashion needs to be validated using a custom method which validates using some business logic (it checks the string length and byte length). Can I (and how do i) set up this custom validation so that I can use it like it used in the above example; so it looks something like:
[StreamValidation(ValidationClass, ErrorMessage = "Serial number is invalid")]
public string Ksn { get; set; }
UPDATE:
I have constructed the following class to be my attribute:
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public class KsnValidation : ValidationAttribute
{
public override bool IsValid(object value)
{
if (!(value is string)) return false;
var val = (string) value;
var bytes = Enumerable
.Range(0, val.Length / 2)
.Select(x => Byte.Parse(val.Substring(2 * x, 2), NumberStyles.HexNumber))
.ToArray();
return val.Length == 20 && bytes.Length == 10;
}
public override string FormatErrorMessage(string name)
{
return base.FormatErrorMessage(name);
}
}
Then decorated the following property:
[KsnValidation(ErrorMessage = "Wrong Name")]
public string Ksn { get; set; }
But I'm not sure how to unit test this

This SO answer givens an answer for MVC2.
And here is a post for MVC3 and up.
Basically you create an Attribute:
public class MyValidationAttribute: ValidationAttribute
{
public MyValidationAttribute()
{ }
protected override ValidationResult IsValid(
object value, ValidationContext validationContext)
{
...
if (somethingWrong)
{
return new ValidationResult(errorMessage);
}
return null; // everything OK
}
}

The documentation for ValidationAttribute shows how to create your custom validation attribute.

Related

Apply data annotations validation to all properties of same primitive datatype in a viewmodel

I searched google and SO for my scenario but not able to find an answer. I want to create a regular expression data annotation validation in a viewmodel class properties which are of double type. Since I have around 20 properties of type double. So I want to create a custom regular expression validation and apply to all double type properties without explicitly specifying on each property like:
[RegularExpression(#"^[0-9]{1,6}(\.[0-9]{1,2})?$", ErrorMessage ="Invalid Input")]
public double Balance { get; set; }
I am expecting thing like this:
[ApplyRegExpToAllDoubleTypes]
public class MyModel
{
public double Balance { get; set; }
public double InstallmentsDue { get; set; }
}
That's an interesting question. Here is how it can be done:
Define a custom ValidationAttribute and apply it at the class level by setting AttributeTargets.Class. Inside the ValidationAttribute, use reflection to get the double properties, then validate the value of each property. If any of the validations fail, return a validation failed message.
[ApplyRegExpToAllDoubleTypes]
public class MyModel {
public double Balance { get; set; }
public double InstallmentsDue { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public class ApplyRegExpToAllDoubleTypes : ValidationAttribute {
protected override ValidationResult IsValid(object currentObject, ValidationContext validationContext) {
if (currentObject == null) {
return new ValidationResult("Object can't be null");
}
var properties = validationContext.ObjectType.GetProperties().Where(x => x.PropertyType == typeof(double));
foreach (var property in properties) {
//Here I compare the double property value against '5'
//Replace the following with the custom regex check
if ((double)property.GetValue(currentObject) < 5) {
return new ValidationResult("All double properties must be greater than 5");
}
}
return ValidationResult.Success;
}
}

Custom API ValidationAttribute for class property

I have a class Student that contains the list of property 'TextPair' as shown below:
public class Student
{
public List<TextPair> Hobbies { get; set; }
public List<TextPair> Languages { get; set; }
public List<TextPair> Majors { get; set; }
}
public class TextPair
{
[StringLength(2, ErrorMessage = "The value length is invalid")]
public string Value { get; set; }
public string Text { get; set; }
}
Here, I validate the value for maximum length 2 using the StringLength AttributeValidator and decorate in the property 'Value' inside TextPair model.
The problem for me is that the length is always fixed and length is always mandatory.
In my use case, I want the different flavor of Value in different part of the application (or, different property of same type) to support different lengths.
I was looking for something like below where I could pass the validation in my class where I declare my property 'TextPair'
[i.e. I don't want to make the validation mandatory always and also not hard-code the value 2]
public class Student
{
//Any length of the value is accepted for hobbies
public List<TextPair> Hobbies{ get; set; }
[ValuesLength(Length = 2, ErrorMessage = "Language code length must be 2 characters max")]
public List<TextPair> Languages { get; set; }
[ValuesLength(Length = 128, ErrorMessage = "The major should be within 128 characters length")]
public List<TextPair> Majors{ get; set; }
}
Is there any efficient way to approach this solution?
Maybe try subclassing StringLengthAttribute to create your desired ValuesLength attribute. Please note that this code is not tested and is just suggestion to final implementation.
public class ValuesLength : StringLengthAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var isValid = true;
var pair = value as TextPair;
if (pair != null && pair.Value != null)
{
var pairValue = pair.Value;
isValid = pairValue.Length < MaximumLength && pairValue.Length > MinimumLength;
}
return IsValid ? ValidationResult.Success : new ValidationResult(ErrorMessage);
}
}
One of the solution approached is as follows:
public class Student
{
//Any length of the value is accepted for hobbies
public List<TextPair> Hobbies{ get; set; }
[ValuesLength(MaximumLength = 2, ErrorMessage = "Language code length must be 2 characters max")]
public List<TextPair> Languages { get; set; }
[ValuesLength(MaximumLength = 128, ErrorMessage = "The major should be within 128 characters length")]
public List<TextPair> Majors{ get; set; }
}
My Custom Attribute validation is checking the list and verifying if anyone of the element values are exceeding the provided length as:
public class ValuesLengthAttribute : ValidationAttribute
{
public int MaximumLength { get; set; }
public override Boolean IsValid(object value)
{
Boolean isValid = true;
var list = value as List<TextPair>;
if (list != null && list.Count>0)
foreach (var item in list)
{
if (item.Value.Length > MaximumLength)
isValid = false;
}
return isValid;
}
}

Passing parameter to a custom validation attribute in ASP.NET MVC

I'm using ASP.NET MVC and I wanna create a custom validation attribute to validate StartTime and EndTime which refer from the text inputs.
I have tried:
Model:
public class MyModel
{
public bool GoldTime { get; set; }
[TimeValidation(#"^\d{1,2}:\d{1,2}$", GoldTime, ErrorMessage = "Start time is invalid.")]
public string StartTime { get; set; }
[TimeValidation(#"^\d{1,2}:\d{1,2}$", GoldTime, ErrorMessage = "End time is invalid.")]
public string EndTime { get; set; }
}
Validation attribute:
public class TimeValidationAttribute : ValidationAttribute
{
private readonly string _pattern;
private readonly bool _useGoldTime;
public TimeValidationAttribute(string pattern, bool useGoldTime)
{
_pattern = pattern;
_useGoldTime = useGoldTime;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (_useGoldTime)
{
var regex = new Regex(_pattern);
if (!regex.IsMatch(value.ToString()))
{
return new ValidationResult(ErrorMessage);
}
}
return ValidationResult.Success;
}
}
But I'm getting this error message:
An object reference is required for the non-static field, method, or
property 'MyModel.GoldTime'
Then, I've tried again by changing GoldTime (in the model) to true, the error message would disappear.
So, my question is: How can I pass the parameter GoldTime to the attribute constructor? I need to use the GoldTime as a key to enable validating the value of StartTime and EndTime.
Thank you!
It is complaining about using a model property within the attribute definition. Instead, within your custom attribute, you can use properties on the ValidationContext class to get the underlying model, I think via validationContext.ObjectInstance.
Obviously, you don't want to hard-code the type of model but you could use reflection:
bool goldTime;
var prop = validationContext.ObjectInstance.GetType().GetProperty("GoldTime");
if (prop != null)
goldTime = (bool)prop.GetValue(validationContext.ObjectInstance, null);
Or, define an interface for the model:
public interface ITimeModel
{
bool GoldTime { get; set; }
}
And look for that:
bool goldTime;
if (validationContext.ObjectInstance is ITimeModel)
goldTime = ((ITimeModel)validationContext.ObjectInstance).GoldTime;

c# data annotations grouping

I wish to try and join couple of validators into a same one just to allow me avoid missing validator.
consider,just an example, i have an Id field of type string which i want to validate.
i need to apply couple of validators such as Required, MaxLength, MinLength and some other parsing stuff.
Now, to make it more intersting, i want to add EmailAddress validation to all my Id fields, so Im relying on the fact that the validator is already exists so i just want to add it to the validation group.
I have lots of models with Id in them, I want to make some new validator which actually validates couple of things together so it will be easiler and more right to apply validators on the fields.
Couldn't find something about it.
lets see an example: (ignore the fact that it will fail..just to get the idea)
[Required]
[EmailAddress]
[StringLength(6)]
[MinLength(5)]
[CustomA]
[CustomB]
public string Id { get; set; }
i want to simply write
[IdValidator]
public string Id { get; set; }
and in somewhere else, IdValidator will validate all of them and more/less whenever i decide to change it.
i want the change to occur in only 1 place.
Why not create you own grouped attribute? You can add the attributes you need to the _attributes array.
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
sealed public class GroupedValidationAttribute : ValidationAttribute, IClientValidatable
{
private readonly ValidationAttribute[] _attributes;
public GroupedValidationAttribute(int minLength, int maxLength)
{
_attributes = new ValidationAttribute[]
{
new RequiredAttribute(),
new EmailAddressAttribute(),
new StringLengthAttribute(maxLength),
new MinLengthAttribute(minLength),
new CustomAAttribute(),
new CustomBAttribute()
};
}
public override bool IsValid(object value)
{
return _attributes.All(a => a.IsValid(value));
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
return _attributes
.OfType<IClientValidatable>()
.SelectMany(x => x.GetClientValidationRules(metadata, context));
}
}
You can create custom data validation attribute and implement required behavior:
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
sealed public class IdentifierValidationAttribute : ValidationAttribute
{
public int MinLength { get; set; }
public int MaxLength { get; set; }
public IdentifierValidationAttribute(int minLength, int maxLength)
{
MinLength = minLength;
MaxLength = maxLength;
}
public override bool IsValid(object value)
{
var stringValue = value as string;
if(string.IsNullOrEmpty(stringValue))
return false;
var length = stringValue.Length;
if(length > MaxLength || length < MinLength)
return false;
return true;
}
}
Also you can make composite attribute like next one:
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public sealed class IdentifierValidationAttribute : ValidationAttribute
{
private readonly ValidationAttribute[] attributes;
public IdentifierValidationAttribute(int minLength, int maxLength)
{
attributes = new ValidationAttribute[] { new EmailAddressAttribute(), new MinLengthAttribute(minLength), new MaxLengthAttribute(maxLength) };
}
public override bool IsValid(object value)
{
return attributes.All(a => a.IsValid(value));
}
}

Can I use a Model property as part of a Range validation attribute

In my model I have an object that has the following property.
[Range(typeof(int), "2014", "2024", ErrorMessage = "{0} can only be beteween {1} and {2}")]
public int FiscalYear { get; set; }
The lower and upper range values are 2014 and 2024 respectively. However, rather than use these fixed values, I'd like them to be based on another property in the model.
So, for example, if I had a property, CurrentFiscalYear, my hypothetical Range attribute would look like this.
[Range(typeof(int), CurrentFiscalYear, CurrentFiscalYear + 10, ErrorMessage = "{0} can only be beteween {1} and {2}")]
public int FiscalYear { get; set; }
Is something like this possible? Or must the lower and upper values be provided at compile time?
No, this isn't possible. Attribute parameter values just be "compile-time constant" values. In other words, the actual value of the parameter must be known when you compile the program.
From MSDN - Attributes tutorial:
Attribute parameters are restricted to constant values of the following types:
Simple types (bool, byte, char, short, int, long, float, and double)
string
System.Type
enums
object (The argument to an attribute parameter of type object must be a constant value of one of the above types.)
One-dimensional arrays of any of the above types
This is documentation for .NET 1.1, but has not changed.
Workaround
This isn't tested at all but you can create a custom ValidationAttribute which takes the range and also model property names who's values to add to the range values when testing for validity. You can create an internal standard RangeAttribute to do the work for you and even keep client validation working by implementing IClientValidatable:
public sealed class ShiftedRangeAttribute : ValidationAttribute
{
public string MinShiftProperty { get; private set; }
public string MaxShiftProperty { get; private set; }
public double Minimum { get; private set; }
public double Maximum { get; private set; }
public ShiftedRangeAttribute(double minimum, double maximum, string minShiftProperty, string maxShiftProperty)
{
this.Minimum = minimum;
this.Maximum = maximum;
this.MinShiftProperty = minShiftProperty;
this.MaxShiftProperty = maxShiftProperty;
}
public ShiftedRangeAttribute(int minimum, int maximum, string minShiftProperty, string maxShiftProperty)
{
this.Minimum = minimum;
this.Maximum = maximum;
this.MinShiftProperty = minShiftProperty;
this.MaxShiftProperty = maxShiftProperty;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
RangeAttribute attr = this.CreateRangeAttribute(validationContext.ObjectInstance);
return attr.GetValidationResult(value, validationContext);
}
internal RangeAttribute CreateRangeAttribute(object model)
{
double min = this.Minimum;
if (this.MinShiftProperty != null)
{
min += Convert.ToDouble(model.GetType().GetProperty(this.MinShiftProperty).GetValue(model));
}
double max = this.Maximum;
if (this.MaxShiftProperty != null)
{
max += Convert.ToDouble(model.GetType().GetProperty(this.MaxShiftProperty).GetValue(model));
}
return new RangeAttribute(min, max);
}
}
If you want it to work with client validation, you will also to create a DataAnnotationsModelValidator and register it in your global.asax Application_Start() to ensure the client validation HTML attributes are output. Again you can cheat and use the built-in RangeAttributeAdapter to help you because in Javascript it is ultimately just a range validator:
public class ShiftedRangeAttributeAdapter : DataAnnotationsModelValidator<ShiftedRangeAttribute>
{
public ShiftedRangeAttributeAdapter(ModelMetadata metadata, ControllerContext context, ShiftedRangeAttribute attribute)
: base(metadata, context, attribute)
{
}
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
RangeAttribute attr = this.Attribute.CreateRangeAttribute(this.Metadata.Container);
return new RangeAttributeAdapter(this.Metadata, this.ControllerContext, attr).GetClientValidationRules();
}
}
...
DataAnnotationsModelValidatorProvider.RegisterAdapter(
typeof(ShiftedRangeAttribute), typeof(ShiftedRangeAttributeAdapter));
Note that the client validation code only works if the class containing the properties is the top-level model class, which is stored in Metadata.Container. You cannot access the "parent" of the current property. You would need to do more work to create a custom jQuery validator to handle this properly.
You can then use it as so:
[ShiftedRange(0, 10, "CurrentFiscalYear", "CurrentFiscalYear", ErrorMessage = "{0} can only be beteween {1} and {2}")]
public int FiscalYear { get; set; }
EDIT: fixed some bugs after testing
This can be done by writing a custom ValidationAttribute, implementation could be done something like this:
public sealed class FiscalYearAttribute : ValidationAttribute
{
public string CurrentFiscalYear { get; set; }
public override bool IsValid(object value)
{
var currentFiscalYearString = HttpContext.Current.Request[CurrentFiscalYear];
var currentFiscalYear = int.Parse(currentFiscalYearString);
var fiscalYear = (int) value;
return fiscalYear >= currentFiscalYear && fiscalYear <= currentFiscalYear + 10;
}
public override string FormatErrorMessage(string name)
{
return name + " error description here.";
}
}
Usage:
[Required]
[Display(Name = "CurrentFiscalYear")]
public int CurrentFiscalYear { get; set; }
[Display(Name = "FiscalYear")]
[FiscalYear(CurrentFiscalYear = "CurrentFiscalYear")]
public int FiscalYear { get; set; }

Categories