I am using Range Validator to validate the date. My requirement is validate the input date should be between current date and current date -60 years.
This is what I tried so far,
[Range(typeof(DateTime),DateTime.UtcNow.ToString(),DateTime.UtcNow.AddYears(-60).Date.ToString())]
public DateTime? DOJ { get; set; }
But this throws error : An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type.
So I modified my code:
#region variables
public const string MaxDate = DateTime.UtcNow.ToString();
public const string MinDate = DateTime.UtcNow.AddYears(-60).Date.ToString();
#endregion
And Set property :
[Range(typeof(DateTime),maximum:MaxDate,minimum:MinDate)]
public DateTime? DOJ { get; set; }
Now the error is :The expression being assigned to 'MaxDate' must be constant.
Same for MinDate.
What's the solution?
You can't use variables in Attributes. All items in attributes must be constant. If you want to filter value based on dynamic values, then you can make new ValidationAttribute like this:
public class ValidateYearsAttribute : ValidationAttribute
{
private readonly DateTime _minValue = DateTime.UtcNow.AddYears(-60);
private readonly DateTime _maxValue = DateTime.UtcNow;
public override bool IsValid(object value)
{
DateTime val = (DateTime)value;
return val >= _minValue && val <= _maxValue;
}
public override string FormatErrorMessage(string name)
{
return string.Format(ErrorMessage, _minValue, _maxValue);
}
}
And then you just need to place it on your property:
[ValidateYears]
public DateTime? DOJ { get; set; }
You can update FormatErrorMessage based on what you need.
Related
I have a custom model that holds some DateTime values, and a custom DataAnnotation that's built to compare these values.
Here's the properties with their annotations:
[Required]
[DataType(System.ComponentModel.DataAnnotations.DataType.Date)]
[Display(Name = "Start Date")]
public DateTime StartTime { get; set; }
[DataType(System.ComponentModel.DataAnnotations.DataType.Date)]
[Display(Name = "End Date")]
[CompareTo(this.StartTime, CompareToAttribute.CompareOperator.GreaterThanEqual)]
public DateTime? EndTime { get; set; }
The CompareTo attribute is the one in question. I get an error:
Keyword 'this' is not available in the current context
I've tried placing only StartTime in the annotation with no luck. How can I pass in a property value from the same model class?
If anyone is still wondering how to compare two dates and use that in a validation DataAnnotation, you can simply add an extension method that will compare the start date and the end date like the following.
Assuming that this is your class:
using System;
using System.ComponentModel.DataAnnotations;
namespace Entities.Models
{
public class Periode
{
[Key]
public int PeriodeID { get; set; }
public string Name { get; set; }
[DataType(DataType.Date)]
[Display(Name ="Start Date")]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime StartDate { get; set; }
[DataType(DataType.Date)]
[Display(Name = "End Date")]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime EndDate { get; set; }
}
}
You simply add the following class as a validator:
namespace Entities.Models
{
public class StartEndDateValidator : ValidationAttribute
{
protected override ValidationResult
IsValid(object value, ValidationContext validationContext)
{
var model = (Models.Periode)validationContext.ObjectInstance;
DateTime EndDate = Convert.ToDateTime(model.EndDate);
DateTime StartDate = Convert.ToDateTime(value);
if (StartDate > EndDate)
{
return new ValidationResult
("The start date must be anterior to the end date");
}
else
{
return ValidationResult.Success;
}
}
}
}
And then you need to add that DataAnnotation on the StartDate as following
namespace Entities.Models
{
public class Periode
{
[Key]
public int PeriodeID { get; set; }
public string Name { get; set; }
[DataType(DataType.Date)]
[Display(Name ="Start Date")]
// You need to add the following line
[StartEndDateValidator]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime StartDate { get; set; }
[DataType(DataType.Date)]
[Display(Name = "End Date")]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime EndDate { get; set; }
}
}
I've tried placing only StartTime in the annotation with no luck. How
can I pass in a property value from the same model class?
That's impossible because attributes are metadata that is baked into the assembly at compile-time. This means that you can pass only CONSTANT parameters to an attribute. Yeah, that's a hell of a limitation because in order to perform such an obvious validation thing as comparing 2 values in your model you will have to write gazzilion of plumbing code such as what I have illustrated here for example: https://stackoverflow.com/a/16100455/29407 I mean, you will have to use reflection! Come on Microsoft! Are you serious?
Or just cut the crap of data annotations and start doing validation the right way: using FluentValidation.NET. It allows you to express your validation rules in a very elegant way, it greatly integrates with ASP.NET MVC and allows you to unit test your validation logic in isolation. It also doesn't rely on reflection so it is super fast. I have benchmarked it and using it in very heavy traffic production applications.
Data annotations just don't cut the mustard compared to imperative validation rules when you start writing applications that are a little more complicated than a Hello World and which require a little more complex validation logic than you would have in a Hello World application.
I like Hassen's answer.
Same as Hassen's example, but suggest:
1) aborting if no end date if end date is optional.
2) putting a validator on end date in case user only changes end date
Data Annotation:
[Required]
[Display(Name = "Start Effective Date", Description = "Start Date")]
[DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}")]
[DataType(DataType.Date)]
[StartDateValidator]
public DateTime StartEffectiveDate { get; set; }
[Display(Name = "End Effective Date", Description = "End Date")]
[DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}")]
[DataType(DataType.Date)]
[EndDateValidator]
public DateTime? EndEffectiveDate { get; set; }
Code:
public class StartDateValidator : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var model = (CostCenterAllocationHeader)validationContext.ObjectInstance;
if (model.EndEffectiveDate == null) // Abort if no End Date
return ValidationResult.Success;
DateTime EndDate = model.EndEffectiveDate.GetValueOrDefault();
DateTime StartDate = Convert.ToDateTime(value); // value = StartDate
if (StartDate > EndDate)
return new ValidationResult("The start date must be before the end date");
else
return ValidationResult.Success;
}
}
public class EndDateValidator : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var model = (CostCenterAllocationHeader)validationContext.ObjectInstance;
if (model.EndEffectiveDate == null) // Abort if no End Date
return ValidationResult.Success;
DateTime EndDate = Convert.ToDateTime(value); // value = EndDate
DateTime StartDate = model.StartEffectiveDate;
if (StartDate > EndDate)
return new ValidationResult("The start date must be before the end date");
else
return ValidationResult.Success;
}
}
Would have commented on Hassen's answer, but don't have enough reputation.
public class StartDateValidator : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var model = (CostCenterAllocationHeader)validationContext.ObjectInstance;
if (model.EndEffectiveDate == null) // Abort if no End Date
return ValidationResult.Success;
DateTime EndDate = model.EndEffectiveDate.GetValueOrDefault();
DateTime StartDate = Convert.ToDateTime(value); // value = StartDate
if (StartDate > EndDate)
return new ValidationResult("The start date must be before the end date");
else
return ValidationResult.Success;
}
}
In the above example there is one issue , This solution can't be used as a common solution for validating the dates . Because the type casting of below line is not generic
var model = (CostCenterAllocationHeader)validationContext.ObjectInstance;
It means that the validation can only be applied to the specific model "costCenterAllocationHeader" . What needs to be done by passing the member name to the constructor of the validator and get the value from the ValidationContext using reflection. By this method we can use this attribute as a generic solution and can be applied in any ViewModels.
How can I validate the values assigned to elements of a dropdown list? Normally I would assign ranges in the model and that field would be validated. However, if I have something like this I am not sure how to handle it.
Model
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Value { get; set; }
public DateTime Given { get; set; }
public TimeSpan TimeGiven { get; set; }
public string Phase { get; set; }
public bool Active { get; set; }
public int PersonId { get; set; }
}
The name in the model is a dropdown list of different products. I am not sure how to handle the validation for the Value since the different products will have different ranges. For example, Product Named X will have a valid range of 25-30 where product Y will have a valid range of .01 - .5. The Person can have many products assigned so I have a one to many relationship set up with Person and Product.
Is there a way to validate the value based on what product they select X, Y? I will have approximately 40 different products so Ideally I could do this without having to having a separate model for each product.
You can validate using custom business rules with a ValidationAttribute
It is very straightforward you just need to do the following:
Create a class that inherits from ValidationAttribute and override the IsValid method.
Decorate your property with the attribute you just created.
For example:
[AttributeUsage(AttributeTargets.Property, AllowMultiple =false, Inherited = false)]
public class MyBusinessRuleValidation: ValidationAttribute
{
protected override ValidationResult IsValid(object v, ValidationContext validationContext)
{
var Name = (string)v //since we decorated the property Name with this attribute;
//retrieve Value's value using validationContext
var value = (decimal) validationContext.ObjectType.GetProperty("Value").GetValue(validationContext.ObjectInstance, null);
//check whether you need to exit with error
if( name == ProductX) {
if(value > 10 && value < 25)
return new ValidationResult(ErrorMessage);
}
return ValidationResult.Success;
}
}
Use the validator:
public class Product
{
public int Id { get; set; }
[MyBusinessRuleValidation(ErrorMessage="Some ugly error")]
public string Name { get; set; }
....
}
I have a problem ...
I have a user registration form .. in this form I have 3 fields that represent the day, month and year of birth. I like to have 3 fields distinct (3 menus). how can I create a validator that allows me to check if a valid date? (not accept dates like 30/02/2011) I could do it in JavaScript (client side), but even if I wanted to have the validator as usual in model-vew-controller?
You can bind validators to classes instead of properties.
I would do something like that:
//The Model
[DateValidator]
public class Date
{
public string Month { get; set; }
public string Day { get; set; }
public string Year { get; set; }
}
//The DataAnnotation
[AttributeUsage(AttributeTargets.Class)]
class DateValidatorAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
var date = value as Date;
Debug.Assert(date != null);
var dateString = date.Month + date.Day + date.Year;
DateTime dateTime;
var isValid = DateTime.TryParseExact(dateString, "ddMMyyyy", CultureInfo.InvariantCulture, DateTimeStyles.None,
out dateTime);
return isValid;
}
}
Edit
DateTime.TryParseExact DOES check the validity of a DateTime (i.e. it will return false if you try to parse 30/02/2011).
Create your model binder which will get those 3 fields from context and validate them altogether.
I am using DataAnnotations for validation (including client side)
I have a form with multiple fields. Basic validation for individual fields work fine. Now there are a couple of fields of which atleast one needs to have a value (if there are 3 fields then either 1st or 2nd or 3rd field should have a value).
I have read quite a few posts on this site and couple of blog entries. But I couldn't find a solution that works in the above mentioned scenario. I might have missed something or doing it incorrectly.
Can you help with this please?
try this
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public sealed class EitherOr : ValidationAttribute
{
private const string _defaultErrorMessage = "'{0}' OR '{1}' OR '{2}' must have a value";
private readonly object _typeId = new object();
public EitherOr(string prop1, string prop2, string prop3)
: base(_defaultErrorMessage)
{
Prop1 = prop1;
Prop2 = prop2;
Prop3 = prop3;
}
public string Prop1 { get; private set; }
public string Prop2 { get; private set; }
public string Prop3 { get; private set; }
public override object TypeId
{
get
{
return _typeId;
}
}
public override string FormatErrorMessage(string name)
{
return String.Format(CultureInfo.CurrentUICulture, ErrorMessageString, Prop1, Prop2,Prop3);
}
public override bool IsValid(object value)
{
if(string.IsNullOrEmpty(Prop1)&&string.IsNullOrEmpty(Prop2) && string.IsNullOrEmpty(Prop3))
{
return false;
}
return true;
}
then mark your class with the EitherOr attribute:
[EitherOr("Bar","Stool","Hood", ErrorMessage = "please supply one of the properties")]
public class Foo
{
public string Bar{ get; set;}
public string Stool{ get; set;}
public string Hood{ get; set;}
}
Please note that i made use of string properties, if your property is of other type, makle sure to change the IsValid(object value) validation
Could someone help me with this issue. I'm trying to figure out how to check two values on a form, one of the two items has to be filled in. How do I do a check to ensure one or both of the items have been entered?
I'm using viewmodels in ASP.NET MVC 2.
Here's a little snip of code:
The view:
Email: <%=Html.TextBoxFor(x => x.Email)%>
Telephone: <%=Html.TextBoxFor(x => x.TelephoneNumber)%>
The viewmodel:
[Email(ErrorMessage = "Please Enter a Valid Email Address")]
public string Email { get; set; }
[DisplayName("Telephone Number")]
public string TelephoneNumber { get; set; }
I want either of these details to be provided.
Thanks for any pointers.
You can probably do this in much the same way as the PropertiesMustMatch attribute that comes as part of the File->New->ASP.NET MVC 2 Web Application.
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public sealed class EitherOrAttribute : ValidationAttribute
{
private const string _defaultErrorMessage = "Either '{0}' or '{1}' must have a value.";
private readonly object _typeId = new object();
public EitherOrAttribute(string primaryProperty, string secondaryProperty)
: base(_defaultErrorMessage)
{
PrimaryProperty = primaryProperty;
SecondaryProperty = secondaryProperty;
}
public string PrimaryProperty { get; private set; }
public string SecondaryProperty { get; private set; }
public override object TypeId
{
get
{
return _typeId;
}
}
public override string FormatErrorMessage(string name)
{
return String.Format(CultureInfo.CurrentUICulture, ErrorMessageString,
PrimaryProperty, SecondaryProperty);
}
public override bool IsValid(object value)
{
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(value);
object primaryValue = properties.Find(PrimaryProperty, true /* ignoreCase */).GetValue(value);
object secondaryValue = properties.Find(SecondaryProperty, true /* ignoreCase */).GetValue(value);
return primaryValue != null || secondaryValue != null;
}
}
The key part of this function is the IsValid function that determines if one of the two parameters has a value.
Unlike normal Property-based attributes, this is applied to the class level and can be used like so:
[EitherOr("Email", "TelephoneNumber")]
public class ExampleViewModel
{
[Email(ErrorMessage = "Please Enter a Valid Email Address")]
public string Email { get; set; }
[DisplayName("Telephone Number")]
public string TelephoneNumber { get; set; }
}
You should be able to add as many as these as you need per form, but if you want to force them to enter a value into one of more than two boxes (Email, Telephone or Fax for example), then you would probably be best changing the input to be more an array of values and parse it that way.