I have a basic form which has a date field and I want to validate it inside the IValidatableObject. The type of the field is mapped to a DateTime property so if someone types in 26/15/2011, how do you pick that up in the Validate method? Strictly speaking its almost like validating a DateTime object with a DateTime which doesn't make sense. Any ideas on how to get around this or how to detect that its the wrong date?
Implement IValidatableObject on your method and make the validation for this field
e.g.
public class YourModel : IValidatableObject
{
public YourModel()
{
}
[Required(ErrorMessage = "date is required")]
public string Date { set; get; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
DateTime result;
bool parseDone = DateTime.TryParse(Date, out result);
if (!parseDone)
{
yield return new ValidationResult(Date + "is invalid", new[] { "Date" });
}
}
}
I suggest to use jquery validate and jqueryUI calendar for client side
Hope it helps
Related
I have the following property in my Model :
[StringLength(100, ErrorMessage = "Must be less than 100 Chars", MinimumLength = 3)]
public List<KeyValuePair<int, string>> Authors { get; set; }
How can I Validate each string into the above list with DataAnnotation Validation attribute in MVC3 ?
Is it possible at all ?
Custom validation to the rescue! You need to do the following:
Implement the IValidatableObject interface
Implement the IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
Implement your logic to determine that each string has less than 100 characters
Here's the code
public class YourModel : IValidatableObject
{
public List<KeyValuePair<int, string>> Authors { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
foreach(KeyValuePair<int, string> myKvp in Authors)
{
if(myKvp.Value.Length >= 100)
{
yield return new ValidationResult("Must be less than 100 characters");
}
}
}
}
That way you can do a call to if(Model.IsValid) in your controller action, and return any errors that are reported. If your KeyValuePair entries are referring to a specific entity you can even do something like:
yield return new ValidationResult("Must be less than 100 characters", new string[] { myKvp.Key.ToString() });
You'd need to tailor it to fit the ID of the attribute on your page. This way, the error message could be specific to an input on your page.
I override IValidatableObject in many places as there's many cases where I do validation that's dependent on the state of my object. Your case is a bit different, but it's certainly do-able as can be seen from the above example. (All that's off the top of my head, however, so may not be perfect!)
I have an HttpPost controller action that takes in a simple form DTO object.
[HttpPost]
public ViewResult Index(ResultQueryForm queryForm)
{
...
}
public class ResultQueryForm
{
public DateTime? TimestampStart { get; set; }
public DateTime? TimestampEnd { get; set; }
public string Name { get; set; }
}
The DTO object has nullable datetime fields used to create a range. The reason that it is set to nullable is because the form that is bound to the model is a query form, and the user doesn't have to enter a date value in the form.
The problem I'm running into is that if the user enters an invalid date, i would like the MVC default model binding to provide an error message. This happens flawlessly if I have a controller action that takes a DateTime? type as a argument, but since I'm passing a DTO that holds a DateTime? type the model binding appears to just set the DateTime? variable to null. This causes unexpected results.
Note:
[HttpPost]
public ViewResult Index(DateTime? startDate)
{
// If the user enters an invalid date, the controller action won't even be run because the MVC model binding will fail and return an error message to the user
}
Is there anyway to tell MVC model binding to "fail" if it can't bind the DateTime? value to the form DTO object, instead of just setting it to null? Is there a better way? Passing each individual form input to the controller is infeasible, due to the large amount of properties in the form/dto object (I've excluded many of them for easy reading).
You can validate your model in the controller action.
if(!Model.IsValid)
{
return View(); // ooops didn't work
}
else
{
return RedirectToAction("Index"); //horray
}
Of course you can put whatever you want in there, and return Json object if you want to display it on your page.
Also you need to add ValidateInput(true) up the top of your action method like this: [HttpPost, ValidateInput(true)]
I think you can create a custom ValidationAttribute for this.
[DateTimeFormat(ErrorMessage = "Invalid date format.")]
public DateTime? TimestampStart { get; set; }
[DateTimeFormat(ErrorMessage = "Invalid date format.")]
public DateTime? TimestampEnd { get; set; }
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class DateTimeFormatAttribute : ValidationAttribute
{
public override bool IsValid(object value) {
// allow null values
if (value == null) { return true; }
// when value is not null, try to convert to a DateTime
DateTime asDateTime;
if (DateTime.TryParse(value.ToString(), out asDateTime)) {
return true; // parsed to datetime successfully
}
return false; // value could not be parsed
}
}
Suppose you have a viewModel:
public class CreatePersonViewModel
{
[Required]
public bool HasDeliveryAddress {get;set;}
// Should only be validated when HasDeliveryAddress is true
[RequiredIf("HasDeliveryAddress", true)]
public Address Address { get; set; }
}
And the model Address will look like this:
public class Address : IValidatableObject
{
[Required]
public string City { get; set; }
[Required]
public string HouseNr { get; set; }
[Required]
public string CountryCode { get; set; }
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
[Required]
public string ZipCode { get; set; }
[Required]
public string Street { get; set; }
#region IValidatableObject Members
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
string[] requiredFields;
var results = new List<ValidationResult>();
// some custom validations here (I removed them to keep it simple)
return results;
}
#endregion
}
Some would suggest to create a viewmodel for Address and add some custom logic there but I need an instance of Address to pass to my EditorTemplate for Address.
The main problem here is that the validation of Address is done before the validation of my PersonViewModel so I can't prevent it.
Note: the RequiredIfAttribute is a custom attribute which does just what I want for simple types.
Would have been a piece of cake if you had used FluentValidation.NET instead of DataAnnotations or IValidatableObject which limit the validation power quite in complex scenarios:
public class CreatePersonViewModelValidator : AbstractValidator<CreatePersonViewModel>
{
public CreatePersonViewModelValidator()
{
RuleFor(x => x.Address)
.SetValidator(new AddressValidator())
.When(x => x.HasDeliveryAddress);
}
}
Simon Ince has an alpha release of Mvc.ValidationToolkit which seems to be able to do what you want.
Update
As I understand it, the 'problem' lies in the DefaultModelBinder class, which validates your model on the basis that if it finds a validation attribute it asks it if the value is valid (quite reasonable really!), it has no notion of hierarchy. In order to support your required functionality you'll have to write a custom model binder that binds and then validates, if required, as determined by your declarative markup.
If you do write such a class it may be a good candidate for MVC futures.
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 have a class like this
public class PageReference {
[ScaffoldColumn(false)]
public string Id { get; set; }
public string Name { get; set; }
}
and in my model I use it like this
[Required]
public PageReference PageLink { get; set; }
the required attribute does not fire if I add it to the pagelink property, how can this be solved?
The validation attribute is evaluated by the model binder against the data supplied by the value provider (often posted form fields). If you're posting a form that does not include that field, the binder won't touch that property of the model and so won't evaluate the validation attributes.
I think there is no recursive validation support in asp.net mvc