Custom API ValidationAttribute for class property - c#

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

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

Get Enum name from the display attribute in c#?

I have an enumeration
public enum GTMType
{
[Display(Name = "CHANNEL_CHANNEL")]
ChannelChannel,
[Display(Name = "CHANNEL_WHOLESALE")]
ChannelWholesale,
[Display(Name = "ENTERPRISE_DIRECT")]
EnterpriseDirect,
[Display(Name = "ENTERPRISE_AGENT")]
EnterpriseAgent,
[Display(Name = "ENTERPRISE_SYSTEM_INTEGRATOR")]
EnterpriseSystemIntegrator
}
when I make an API call to another system to get data, The system returns the value which is a display attribute value.
public Account GetDataForAccountByID(string id)
{
AccountModel accountModel = GetDataFromAnotherSystem(id);
//after the call is successfull accountModel looks like
//{Email: "abc#xyz.com",GTMType:"CHANNEL_CHANNEL"}
var account = new Account
{
EmailAddress: = accountModel.Email,
GTMType = accountModel.GTMType
};
}
public class AccountModel
{
public string Email { get; set; }
public string GTMType { get; set; }
}
public class Account
{
public string EmailAddress { get; set; }
public GTMType GTMType { get; set;
}
How can I convert the string value which is of display attribute value can be converted into an enum.
You can use Enum.GetValues to iterate over possible values of the enum and then GetField representing the given value and get its attributes.
GTMType ParseGTMTypeFromAnotherSystem(string gtmType)
{
var type = typeof(GTMType);
foreach (var value in Enum.GetValues(type))
{
var fieldInfo = type.GetField(Enum.GetName(type, value));
DisplayAttribute[] attributes = fieldInfo.GetCustomAttributes(typeof(DisplayAttribute), false) as DisplayAttribute[];
if (attributes.Length != 1)
{
throw new Exception("Enum definition is wrong.");
}
var displayName = attributes[0].Name;
if (gtmType == displayName)
{
return value as GTMType;
}
}
throw new Exception("Unable to parse.");
}
You may want to handle the exception cases differently, e.g. iterate over all DisplayAttributes in case there's more than one and ignore fields that have none, or in case of a failed parse return a default value - depends on your use case.

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

Asp.net Web Api nested model validation

I'm running in to a bit of a problem in asp.net web api's model binding and validation (via data annotations).
It seems like if i have a model with property such as
Dictionary<string, childObject> obj { get; set; }
the childObject's validations don't seem to trigger. The data is bound from json with Json.Net serializer.
Is there some workaround or fix to this? Or have I misunderstood something else related to this?
I can't help but wonder why this doesn't result in errors:
public class Child
{
[Required]
[StringLength(10)]
public string name;
[Required]
[StringLength(10)]
public string desc;
}
//elsewhere
Child foo = new Child();
foo.name = "hellowrodlasdasdaosdkasodasasdasdasd";
List<ValidationResult> results = new List<ValidationResult>();
Validator.TryValidateObject(foo, new ValidationContext(foo), results, true);
// results.length == 0 here.
Oh god. I had forgotten to declare properties instead of fields.
There are 2 ways you can setup validation of the Dictionary Values. If you don't care about getting all the errors but just the first one encountered you can use a custom validation attribute.
public class Foo
{
[Required]
public string RequiredProperty { get; set; }
[ValidateDictionary]
public Dictionary<string, Bar> BarInstance { get; set; }
}
public class Bar
{
[Required]
public string BarRequiredProperty { get; set; }
}
public class ValidateDictionaryAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (!IsDictionary(value)) return ValidationResult.Success;
var results = new List<ValidationResult>();
var values = (IEnumerable)value.GetType().GetProperty("Values").GetValue(value, null);
values.OfType<object>().ToList().ForEach(item => Validator.TryValidateObject(item, new ValidationContext(item, null, validationContext.Items), results));
Validator.TryValidateObject(value, new ValidationContext(value, null, validationContext.Items), results);
return results.FirstOrDefault() ?? ValidationResult.Success;
}
protected bool IsDictionary(object value)
{
if (value == null) return false;
var valueType = value.GetType();
return valueType.IsGenericType && valueType.GetGenericTypeDefinition() == typeof (Dictionary<,>);
}
}
The other way is to create your own Dictionary as an IValidatableObject and do the validation in that. This solution gives you the ability to return all the errors.
public class Foo
{
[Required]
public string RequiredProperty { get; set; }
public ValidatableDictionary<string, Bar> BarInstance { get; set; }
}
public class Bar
{
[Required]
public string BarRequiredProperty { get; set; }
}
public class ValidatableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, IValidatableObject
{
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var results = new List<ValidationResult>();
Values.ToList().ForEach(item => Validator.TryValidateObject(item, new ValidationContext(item, null, validationContext.Items), results));
return results;
}
}
Validation always passes on fields because attributes can only be applied to properties. You need to change the fields name and desc into properties using auto implemented getter and setters.
These should then look something like
public string name { get; set; }

.net mvc 2 validation: summing up values of several attributes

I am using .NET 4 MVC 2 in my project. I basically have two classes, which I use for my validation. Class A is my (main) model, class B is an composite attribute which class A may have. The code looks like the following:
[Bind(Exclude = "A_ID")]
public class A_Validation
{
[Required(ErrorMessage = "something is missing")]
public string title { get; set; }
// some more attributes ...
public B b { get; set; }
}
All my validation based on class A is working very well. But now I want to validate the composite attribute B, which looks like the following.
[Bind(Exclude = "B_ID")]
public class B_Validation
{
[Required(ErrorMessage = "missing")]
[Range(1, 210, ErrorMessage = "range between 1 and 210")]
public int first { get; set; }
[Required(ErrorMessage = "missing")]
[Range(1, 210, ErrorMessage = "range between 1 and 210")]
public int second { get; set; }
[Required(ErrorMessage = "missing")]
[Range(1, 210, ErrorMessage = "range between 1 and 210")]
public int third { get; set; }
}
I am able to check the ranges of B's three attributes first,second and third. What I additionally want is to check if the sum of all three attributes first,second and third is below a certain threshold.
Any ideas how to proceed?
I think ViewModels might help, but i have no experience in using them.
Have you tried writing a custom validation attribute:
public class SumBelowAttribute : ValidationAttribute
{
private readonly int _max;
public SumBelowAttribute(int max)
{
_max = max;
}
public override bool IsValid(object value)
{
var b = value as B_Validation;
if (b != null)
{
return b.first + b.second + b.third < _max;
}
return base.IsValid(value);
}
}
and then decorate the B property with this attribute:
[SumBelow(123)]
public B b { get; set; }

Categories