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));
}
}
Related
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;
}
}
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;
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; }
C# enum values are not limited to only values listed in it's definition and may store any value of it's base type. If base type is not defined than Int32 or simply int is used.
I am developing a WCF serivice which needs to be confident that some enum has a value assigned as opposed to the default value for all enums of 0. I start with a unit test to find out whether [Required] would do right job here.
using System.ComponentModel.DataAnnotations;
using Xunit;
public enum MyEnum
{
// I always start from 1 in order to distinct first value from the default value
First = 1,
Second,
}
public class Entity
{
[Required]
public MyEnum EnumValue { get; set; }
}
public class EntityValidationTests
{
[Fact]
public void TestValidEnumValue()
{
Entity entity = new Entity { EnumValue = MyEnum.First };
Validator.ValidateObject(entity, new ValidationContext(entity, null, null));
}
[Fact]
public void TestInvalidEnumValue()
{
Entity entity = new Entity { EnumValue = (MyEnum)(-126) };
// -126 is stored in the entity.EnumValue property
Assert.Throws<ValidationException>(() =>
Validator.ValidateObject(entity, new ValidationContext(entity, null, null)));
}
}
It does not, the second test does not throw any exception.
My question is: is there a validator attribute to check that supplied value is in Enum.GetValues?
Update.
Make sure to use the ValidateObject(Object, ValidationContext, Boolean) with last parameter equal to True instead of ValidateObject(Object, ValidationContext) as done in my unit test.
There's EnumDataType in .NET4+ ...
Make sure you set the 3rd parameter, validateAllProperties=true in the call to ValidateObject
so from your example:
public class Entity
{
[EnumDataType(typeof(MyEnum))]
public MyEnum EnumValue { get; set; }
}
[Fact]
public void TestInvalidEnumValue()
{
Entity entity = new Entity { EnumValue = (MyEnum)(-126) };
// -126 is stored in the entity.EnumValue property
Assert.Throws<ValidationException>(() =>
Validator.ValidateObject(entity, new ValidationContext(entity, null, null), true));
}
What you're looking for is:
Enum.IsDefined(typeof(MyEnum), entity.EnumValue)
[Update+1]
The out of the box validator that does a lot of validations including this one is called EnumDataType. Make sure you set validateAllProperties=true as ValidateObject, otherwise your test will fail.
If you just want to check if the enum is defined, you can use a custom validator with the above line:
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Method, AllowMultiple = false)]
public sealed class EnumValidateExistsAttribute : DataTypeAttribute
{
public EnumValidateExistsAttribute(Type enumType)
: base("Enumeration")
{
this.EnumType = enumType;
}
public override bool IsValid(object value)
{
if (this.EnumType == null)
{
throw new InvalidOperationException("Type cannot be null");
}
if (!this.EnumType.IsEnum)
{
throw new InvalidOperationException("Type must be an enum");
}
if (!Enum.IsDefined(EnumType, value))
{
return false;
}
return true;
}
public Type EnumType
{
get;
set;
}
}
... but I suppose it's not out of the box then is it?
I solved my need simply based on this website https://kristinaalberto.com/making-enum-parameters-required-in-asp-net-core-mvc/
public enum CreatedBySelfOrOthersEnumValues
{
Self,
Others
}
public class CampaignRegisterValidationModel
{
[Required]
public string Name { get; set; }
[Required]
public CreatedBySelfOrOthersEnumValues CreatedForSelfOrOthers { get; set; }
[Required]
public int CountryCode { get; set; }
public string Phone { get; set; }
public string Email { get; set; }
}
Then validating
if (ModelState.IsValid)
{
}
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.