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

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;

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

How to get a Custom Validation Attribute to return different localized error messages depending on the situation?

I have a custom validation attribute for parsing dates and I want to return a different error message based on the different ways for a user to mess up the date. I have properties which I can set when creating the attribute that allow me to differentiate the error messages but I can't seem to pass in localized strings for them.
Here's the code:
public sealed class CustomAttribute : ValidationAttribute
{
public string InvalidDateFormat { get; set; }
public string InvalidDateFormatFuture { get; set; }
public string InvalidDateFormatPast { get; set; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
// [...]
if (date is invalid)
{
return new ValidationResult(InvalidDateFormat);
}
if (parsed date is too old)
{
return new ValidationResult(InvalidDateFormatPast);
}
if (if date is in the future)
{
return new ValidationResult(InvalidDateFormatFuture);
}
return ValidationResult.Success;
}
}
And here's how I'm using the attribute:
[DateValidation(ErrorMessageResourceType = typeof(Language), InvalidDateFormat = nameof(Language.InvalidDateFormat, InvalidDateFormatPast = nameof(Language.InvalidDateFormatPast), InvalidDateFormatFuture = nameof(Language.InvalidDateFormatFuture))]
public string Date { get; set; }
But when trying this out, I get just get the messages "InvalidDateFormat", "InvalidDateFormatPast", "InvalidDateFormatFuture"
Removing the nameof(X) gives me the following error:
An attribute argument must be a constant expression, typeof expression or array create expression of an attribute parameter type

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

Creating a custom validation through DataAnnotations?

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.

DataAnnotations "NotRequired" attribute

I've a model kind of complicated.
I have my UserViewModel which has several properties and two of them are HomePhone and WorkPhone. Both of type PhoneViewModel. In PhoneViewModel I have CountryCode, AreaCode and Number all strings. I want to make the CountryCode optional but AreaCode and Number mandatory.
This works great. My problem is that in the UserViewModel WorkPhone is mandatory, and HomePhone is not.
Is there anyway I can dissable Require attributs in PhoneViewModel by setting any attributes in HomeWork property?
I've tried this:
[ValidateInput(false)]
but it is only for classes and methods.
Code:
public class UserViewModel
{
[Required]
public string Name { get; set; }
public PhoneViewModel HomePhone { get; set; }
[Required]
public PhoneViewModel WorkPhone { get; set; }
}
public class PhoneViewModel
{
public string CountryCode { get; set; }
public string AreaCode { get; set; }
[Required]
public string Number { get; set; }
}
[UPDATED on 5/24/2012 to make the idea more clear]
I'm not sure this is the right approach but I think you can extend the concept and can create a more generic / reusable approach.
In ASP.NET MVC the validation happens at the binding stage. When you are posting a form to the server the DefaultModelBinder is the one that creates model instances from the request information and add the validation errors to the ModelStateDictionary.
In your case, as long as the binding happens with the HomePhone the validations will fire up and I think we can't do much about this by creating custom validation attributes or similar kind.
All I'm thinking is not to create model instance at all for HomePhone property when there are no values available in the form (the areacode, countrycode and number or empty), when we control the binding we control the validation, for that, we have to create a custom model binder.
In the custom model binder we are checking if the property is HomePhone and if the form contains any values for it's properties and if not we don't bind the property and the validations won't happen for HomePhone. Simply, the value of HomePhone will be null in the UserViewModel.
public class CustomModelBinder : DefaultModelBinder
{
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
{
if (propertyDescriptor.Name == "HomePhone")
{
var form = controllerContext.HttpContext.Request.Form;
var countryCode = form["HomePhone.CountryCode"];
var areaCode = form["HomePhone.AreaCode"];
var number = form["HomePhone.Number"];
if (string.IsNullOrEmpty(countryCode) && string.IsNullOrEmpty(areaCode) && string.IsNullOrEmpty(number))
return;
}
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
}
Finally you have to register the custom model binder in global.asax.cs.
ModelBinders.Binders.Add(typeof(UserViewModel), new CustomModelBinder());
So now of you have an action that takes UserViewModel as parameter,
[HttpPost]
public Action Post(UserViewModel userViewModel)
{
}
Our custom model binder come into play and of form doesn't post any values for the areacode, countrycode and number for HomePhone, there won't be any validation errors and the userViewModel.HomePhone is null. If the form posts atleast any one of the value for those properties then the validation will happen for HomePhone as expected.
I've been using this amazing nuget that does dynamic annotations: ExpressiveAnnotations
It allows you to do things that weren't possible before such as
[AssertThat("ReturnDate >= Today()")]
public DateTime? ReturnDate { get; set; }
or even
public bool GoAbroad { get; set; }
[RequiredIf("GoAbroad == true")]
public string PassportNumber { get; set; }
Update: Compile annotations in a unit test to ensure no errors exist
As mentioned by #diego this might be intimidating to write code in a string, but the following is what I use to Unit Test all validations looking for compilation errors.
namespace UnitTest
{
public static class ExpressiveAnnotationTestHelpers
{
public static IEnumerable<ExpressiveAttribute> CompileExpressiveAttributes(this Type type)
{
var properties = type.GetProperties()
.Where(p => Attribute.IsDefined(p, typeof(ExpressiveAttribute)));
var attributes = new List<ExpressiveAttribute>();
foreach (var prop in properties)
{
var attribs = prop.GetCustomAttributes<ExpressiveAttribute>().ToList();
attribs.ForEach(x => x.Compile(prop.DeclaringType));
attributes.AddRange(attribs);
}
return attributes;
}
}
[TestClass]
public class ExpressiveAnnotationTests
{
[TestMethod]
public void CompileAnnotationsTest()
{
// ... or for all assemblies within current domain:
var compiled = Assembly.Load("NamespaceOfEntitiesWithExpressiveAnnotations").GetTypes()
.SelectMany(t => t.CompileExpressiveAttributes()).ToList();
Console.WriteLine($"Total entities using Expressive Annotations: {compiled.Count}");
foreach (var compileItem in compiled)
{
Console.WriteLine($"Expression: {compileItem.Expression}");
}
Assert.IsTrue(compiled.Count > 0);
}
}
}
I wouldn't go with the modelBinder; I'd use a custom ValidationAttribute:
public class UserViewModel
{
[Required]
public string Name { get; set; }
public HomePhoneViewModel HomePhone { get; set; }
public WorkPhoneViewModel WorkPhone { get; set; }
}
public class HomePhoneViewModel : PhoneViewModel
{
}
public class WorkPhoneViewModel : PhoneViewModel
{
}
public class PhoneViewModel
{
public string CountryCode { get; set; }
public string AreaCode { get; set; }
[CustomRequiredPhone]
public string Number { get; set; }
}
And then:
[AttributeUsage(AttributeTargets.Property]
public class CustomRequiredPhone : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
ValidationResult validationResult = null;
// Check if Model is WorkphoneViewModel, if so, activate validation
if (validationContext.ObjectInstance.GetType() == typeof(WorkPhoneViewModel)
&& string.IsNullOrWhiteSpace((string)value) == true)
{
this.ErrorMessage = "Phone is required";
validationResult = new ValidationResult(this.ErrorMessage);
}
else
{
validationResult = ValidationResult.Success;
}
return validationResult;
}
}
If it is not clear, I'll provide an explanation but I think it's pretty self-explanatory.
Just some observation: the following code couse a problem if the binding is more than simple filed. I you have a case that in object have nested object it going to skip it and caouse that some filed not been binded in nested object.
Possible solution is
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
{
if (!propertyDescriptor.Attributes.OfType<RequiredAttribute>().Any())
{
var form = controllerContext.HttpContext.Request.Form;
if (form.AllKeys.Where(k => k.StartsWith(string.Format(propertyDescriptor.Name, "."))).Count() > 0)
{
if (form.AllKeys.Where(k => k.StartsWith(string.Format(propertyDescriptor.Name, "."))).All(
k => string.IsNullOrWhiteSpace(form[k])))
return;
}
}
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
much thanks to Altaf Khatri

Categories