Reputation: 6332
I have created a custom validator for a DateTime
field in ASP.NET Core 3.1 as shown below:
[CustomDate]
public DateTime DOB { get; set; }
public class CustomDate : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
//… some logic
}
}
However, my problem is that this custom validator fires only when I put a date value in the text box control. It does not fire for invalid inputs like e.g. when I put a string 'aaa' in the text box.
My question is how to make this custom validator fire even for invalid inputs like 'string', etc.
The reason is I want to make this custom validator replace [Required]
, [ReqularExpression]
, etc. A sort of 'One ring (validator) to rule them all'. How can I achieve that?
Upvotes: 1
Views: 3190
Reputation: 7624
TL;DR: When you submit a value that can't be converted to DateTime
, model binding fails. Since there is already a validation error associated with the property, subsequent validation—including your CustomDate
validator—doesn't fire. Your property is still getting validated, however: If you enter a value of aaa
, ModelState.IsValid
will return false
.
The code you had originally posted should be working fine—but I suspect it's not working the way you're expecting it to. Most notably, your confusion likely stems from the following statement:
"…this custom validator fires only when I put a date value in the text box control."
That is also true! Let me walk through the process.
To help illustrate this, I hope you don't mind me resurrecting your original code sample, as it's useful to have concrete reference to work off of.
[CustomDate]
public DateTime DOB { get; set; }
public class CustomDate : Attribute, IModelValidator
{
public IEnumerable<ModelValidationResult> Validate(ModelValidationContext context)
{
if (Convert.ToDateTime(context.Model) > DateTime.Now)
return new List<ModelValidationResult> {
new ModelValidationResult("", "Invalid - future date")
};
else if (Convert.ToDateTime(context.Model) < new DateTime(1970, 1, 1))
return new List<ModelValidationResult> {
new ModelValidationResult("", "Invalid - date is less than 1970 year")
};
else
return Enumerable.Empty<ModelValidationResult>();
}
}
Before I walk through the process, there are four underlying considerations that are important to be aware of here:
IsValid
.ModelValidationContext.Model
property is typed to the validated property—so, in this case, a DateTime
value.Given these considerations, here's what's happening when you submit a value of e.g. aaa
in the field mapped to your validated DOB
property:
aaa
to a DateTime
property.ModelError
to your ModelStateDictionary
.CustomDate
validator never fires because the field has already failed validation.It's instructive to look at another test case. Instead of putting in aaa
, just don't put a value in at all. In this case, the process looks a bit different:
DateTime
property, so no binding occurs.DateTime
's default value of 0001-01-01 00:00:00
.CustomDate
validator fires, adding a ModelError
because "Invalid - date is less than 1970 year".As you can see above, it is true that your CustomDate
validator isn't firing when a bogus date is submitted. But that doesn't mean that validation isn't occurring. Instead, validation has already happened and failed. If you enter a valid date—or don't enter a date at all—then a model binding error won't occur, and your CustomDate
validator will be executed as expected.
"How to make this custom validator to fire even for invalid inputs like 'string' etc."
Ultimately, I haven't answered that question. But I think my answer will explain why that's happening, and why your input is still getting validated despite that. Keep in mind that even if your CustomDate
validator did fire, it would act the same as if you hadn't submitted a value at all, since the context.Model
value would have defaulted to 0001-01-01 00:00:00
. The main difference is that you're not getting the same error message, since the error is coming from a different source.
I don't recommend this, but if you really wanted your CustomDate
validator to fire, you could apply it to a string
property instead. In that case, model binding wouldn't fail and your CustomDate
validator would still get called. If you pursue this, you'll need to put in additional validation to ensure that the date is in the correct format (e.g., by preempting or handling InvalidFormatException
s). But, of course, your date would be stored as a string
, which likely isn't what you want.
This is a bit outside the scope of your original question, but while I'm here I'd recommend the following:
Convert.ToDateTime()
in your validator; your context.Model
field is already a DateTime
. You just need to cast it back to a DateTime
object (e.g., (DateTime)context.Model
) so your code knows that.<input type="date" />
(reference) which, on most browsers, will restrict input to a correct date while also providing a basic date picker.Upvotes: 3