In MVC when we post a model to an action we do the following in order to validate the model against the data annotation of that model:
if (ModelState.IsValid)
If we mark a property as [Required], the ModelState.IsValid will validate that property if contains a value or not.
My question: How can I manually build and run custom validator?
P.S. I am talking about backend validator only.
In .NET Core, you can simply create a class that inherits from ValidationAttribute. You can see the full details in the ASP.NET Core MVC Docs.
Here's the example taken straight from the docs:
public class ClassicMovieAttribute : ValidationAttribute
{
private int _year;
public ClassicMovieAttribute(int Year)
{
_year = Year;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
Movie movie = (Movie)validationContext.ObjectInstance;
if (movie.Genre == Genre.Classic && movie.ReleaseDate.Year > _year)
{
return new ValidationResult(GetErrorMessage());
}
return ValidationResult.Success;
}
}
I've adapted the example to exclude client-side validation, as requested in your question.
In order to use this new attribute (again, taken from the docs), you need to add it to the relevant field:
[ClassicMovie(1960)]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
Here's another, simpler example for ensuring that a value is true:
public class EnforceTrueAttribute : ValidationAttribute
{
public EnforceTrueAttribute()
: base("The {0} field must be true.") { }
public override bool IsValid(object value) =>
value is bool valueAsBool && valueAsBool;
}
This is applied in the same way:
[EnforceTrue]
public bool ThisShouldBeTrue { get; set; }
Edit: Front-End Code as requested:
<div asp-validation-summary="All" class="text-danger"></div>
The options are All, ModelOnly or None.
To create a custom validation attribute in .Net Core, you need to inherit from IModelValidator and implement Validate method.
Custom validator
public class ValidUrlAttribute : Attribute, IModelValidator
{
public string ErrorMessage { get; set; }
public IEnumerable<ModelValidationResult> Validate(ModelValidationContext context)
{
var url = context.Model as string;
if (url != null && Uri.IsWellFormedUriString(url, UriKind.Absolute))
{
return Enumerable.Empty<ModelValidationResult>();
}
return new List<ModelValidationResult>
{
new ModelValidationResult(context.ModelMetadata.PropertyName, ErrorMessage)
};
}
}
The model
public class Product
{
public int ProductId { get; set; }
[Required]
public string ProductName { get; set; }
[Required]
[ValidUrl]
public string ProductThumbnailUrl { get; set; }
}
Will this approach give opportunity to work with "ModelState.IsValid" property in controller action method?
Yes! The ModelState object will correctly reflect the errors.
Can this approach be applied to the model class? Or it can be used with model class properties only?
I don't know if that could be applied onto class level. I know you can get the information about the class from ModelValidationContext though:
context.Model: returns the property value that is to be validated
context.Container: returns the object that contains the property
context.ActionContext: provides context data and describes the action method that processes the request
context.ModelMetadata: describes the model class that is being validated in detail
Notes:
This validation attribute doesn't work with Client Validation, as requested in OP.
In my view I have a check box and a text box if the check box is checked then I require the text box to be filled with some text. To do this I call
ModelState.AddModelError("item", "Please enter some text.");
only if the checkbox returns true and the text box isempty
when my page re-displays I receive the proper message where I have
#Html.ValidationMessageFor(model => model.item)
but I would like the text to go away after a use types something in the text box, without the user having to hit submit like it does with data annotation. How can I fix this?
I'm using c# Asp.net 4 with entity framework 5
ModelState.AddModelError is server-side validation, so the error message will not go away until you post to the server.
If you want the functionality you describe, you can define a custom validation attribute and apply it both client side and server-side. For example, you can define a "RequiredIf" custom validation attribute, which would make a field required if a certain other condition is met (in this case, if another property is true):
public class RequiredIfAttribute : RequiredAttribute
{
private String PropertyName { get; set; }
private Object DesiredValue { get; set; }
public RequiredIfAttribute(String propertyName, Object desiredvalue)
{
PropertyName = propertyName;
DesiredValue = desiredvalue;
}
protected override ValidationResult IsValid(object value, ValidationContext context)
{
Object instance = context.ObjectInstance;
Type type = instance.GetType();
Object proprtyvalue = type.GetProperty(PropertyName).GetValue(instance, null);
if (proprtyvalue.ToString() == DesiredValue.ToString())
{
ValidationResult result = base.IsValid(value, context);
return result;
}
return ValidationResult.Success;
}
}
Register it in your global.asax:
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(RequiredIfAttribute),typeof(RequiredAttributeAdapter);
Then you can use it like this:
public class YourModel {
// This is the property tied to your checkbox
public bool YourBooleanProperty { get; set; }
[RequiredIf("YourBooleanProperty", true)]
public string Item { get; set; }
}
You could also leverage the JQuery Validate plugin to perform the same conditional validation client-side.
Try with jquery, attach an eventListener to the field and remove the class CSS that MVC added to the field and hide the validation label
Before I start ... I can't easily migrate the project to MVC3. So.
The problem I'm having is that I've defined a custom validator attribute to check the max AND min length of a string property, StringLengthInRangeAttribute.
When the Controller calls ModelState.IsValid, on a list of Passengers only the validation of a Date property is throwing invalid, when nothing has been supplied. I guess that means my problem is not with the custom validator but all validation?
Update (additional info for clarity):
I have two symptoms of this problem :
1.The Required validator on the strings doesn't fire when they are empty
and
2.My custom validator never gets called (a breakpoint I set never gets hit).
Model:
public class Passenger
{
[Required(ErrorMessageResourceType = typeof(Resources.Messages.Passenger),
ErrorMessageResourceName = "RequireNumber")]
public int Number { get; set; }
[Required(ErrorMessageResourceType = typeof(Resources.Messages.Passenger),
ErrorMessageResourceName = "RequireSurname")]
[StringLengthInRange(MinLength = 2, MaxLength = 30, ErrorMessageResourceType = typeof(Resources.Messages.Passenger),
ErrorMessageResourceName = "MaxLengthSurname")]
public string Surname { get; set; }
}
Custom Validator:
public class StringLengthInRangeAttribute:ValidationAttribute
{
public int MinLength { get; set; }
public int MaxLength { get; set; }
public override bool IsValid(object value)
{
if (((string)value).Length < MinLength)
{
return false;
}
if (((string)value).Length > MaxLength)
{
return false;
}
return true;
}
}
Controller Action:
public ViewResult TailorHoliday(List<SearchAndBook.Models.ViewModels.Passenger> passengers,
int leadPassengerIndex)
{
if(!ModelState.IsValid)
{
return View("PassengerDetails", GetBookingState(_currentSession));
}
//...
return View();
}
Any advice appreciated. This is the first time I've used Data Annotations, so I'm quite prepared to feel stupid for missing something!
If you check the content of the ModelState property (in the debugger) you should be able to see every property that gets validated in ModelState.Keys and for each value you see the actual state in the ModelState.Values. If I look at your controller you seem to post a whole list of passengers. You should check as well, whether your values are really posted (use Firebug or Fiddler). Maybe your fields are outside the form.
Maybe you should show part of your view as well to spot the bug.
I am having problems with my custom validation in asp.net mvc 3.0
What I want to it to do.
Be set on a property (right now I only can figure out how to make it on the class)
Be smart enough to realize there are multiple instances being used.
Scenario
textbox ( id = "textbox1")
dropdownlist (id ="ddl1")
textbox (id = "textbox2")
4 dropdownlist (id = "ddl2")
the values in the dropdownlist are the same. ("Days" and "Minutes")
Now user types in textbox1 30 and chooses "days" in ddl1. He then types in textbox2 10000 and "days" in ddl2. First one is valid second one is invalid as there is only 365 days in a year.
Scenario 2
User types in texbox1 99 and chooses "minutes" in ddl1. This of course is invalid and should fail.
So there could be any combination of where they might choose a valid day and invalid minute time.
Or both could be valid
So I have this so far
My viewmodel
[ReminderFormat(ErrorMessage = "test")]
public class ReminderViewModel
{
public List<SelectListItem> DurationTypes { get; set; }
public DurationTypes SelectedDurationType { get; set; }
public string ReminderLength { get; set; }
}
This will be in a list of view models and generates the number of ones I need
List<ReminderViewModel> viewModel = new List<ReminderViewModel>()
// add ReminderviewModel to this collection
View
// do a for loop through the viewModel ( List<ReminderViewModel> )
// generate a textbox html box and drop down list for each one
Data annotation
// set attribute to class for now but I want it on a property but I can't
// figure out how to get SelectedDurationType and pass it in. if I have my attribute on ReminderLength
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class ReminderFormatAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
var reminderViewModel = (ReminderViewModel)value;
switch (reminderViewModel.SelectedDurationType)
{
case DurationTypes.Days:
int length = Convert.ToInt32(reminderViewModel.ReminderLength);
if (length > 30)
{
return false;
}
else
{
return true;
}
default:
throw new ArgumentOutOfRangeException();
}
}
Problem 1
So as you can see I have the annotation on the class and I rather have it on the ReminderLength property.
Problem 2
Right now I just have days at 30 so it's less to type(I will change it later). The problem I am finding right now is that if textbox1 has 31 in it this will fail my validation.
This is correct. But if I have textbox2 that has 1 as the value it will pass validation. This is also correct.
What is not correct is that it will override the first invalid validation. So now it thinks all validation passed and goes into my action method. When it should reject it and go back to the view and tell the user that textbox1 failed validation.
Here's what I would do for your validation attribute:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public class ReminderFormatAttribute: ValidationAttribute, IClientValidatable
{
public string DurationProperty { get; set; }
public ReminderFormatAttribute(string durationProperty)
{
DurationProperty = durationProperty;
ErrorMessage = "{0} value doesn't work for selected duration";
}
public override string FormatErrorMessage(string propName)
{
return string.Format(ErrorMessage, propName);
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var durationType = validationContext.ObjectType.GetProperty(DurationProperty).GetValue(validationContext.ObjectInstance, null);
var reminderLength = value;
// Do your switch statement on durationType here
// Here is a sample of the correct return values
switch (durationType)
{
case DurationTypes.Days:
if (reminderLength > 30)
{
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
else
{
return null;
}
}
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
ValidationType = "reminderformat",
};
rule.ValidationParameters["durationproperty"] = DurationProperty;
yield return rule;
}
}
Now in your ViewModel you can annotate the ReminderLength property like so:
[ReminderFormat("SelectedDurationType")]
public string ReminderLength { get; set; }
This is how I usually do it when the validation depends on the value of another property. The GetClientValidationRules method is the server side piece you need to tie into the unobtrusive client validation via jquery validate.
Check out this link for another ValidationAttribute example: http://www.devtrends.co.uk/blog/the-complete-guide-to-validation-in-asp.net-mvc-3-part-2
The example on this site also goes into writing the necessary jQuery to tie in the client validation
How to use data annotations to do a conditional validation on model?
For example, lets say we have the following model (Person and Senior):
public class Person
{
[Required(ErrorMessage = "*")]
public string Name
{
get;
set;
}
public bool IsSenior
{
get;
set;
}
public Senior Senior
{
get;
set;
}
}
public class Senior
{
[Required(ErrorMessage = "*")]//this should be conditional validation, based on the "IsSenior" value
public string Description
{
get;
set;
}
}
And the following view:
<%= Html.EditorFor(m => m.Name)%>
<%= Html.ValidationMessageFor(m => m.Name)%>
<%= Html.CheckBoxFor(m => m.IsSenior)%>
<%= Html.ValidationMessageFor(m => m.IsSenior)%>
<%= Html.CheckBoxFor(m => m.Senior.Description)%>
<%= Html.ValidationMessageFor(m => m.Senior.Description)%>
I would like to be the "Senior.Description" property conditional required field based on the selection of the "IsSenior" propery (true -> required). How to implement conditional validation in ASP.NET MVC 2 with data annotations?
There's a much better way to add conditional validation rules in MVC3; have your model inherit IValidatableObject and implement the Validate method:
public class Person : IValidatableObject
{
public string Name { get; set; }
public bool IsSenior { get; set; }
public Senior Senior { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (IsSenior && string.IsNullOrEmpty(Senior.Description))
yield return new ValidationResult("Description must be supplied.");
}
}
Read more at Introducing ASP.NET MVC 3 (Preview 1).
I have solved this by handling the "ModelState" dictionary, which is contained by the controller. The ModelState dictionary includes all the members that have to be validated.
Here is the solution:
If you need to implement a conditional validation based on some field (e.g. if A=true, then B is required), while maintaining property level error messaging (this is not true for the custom validators that are on object level) you can achieve this by handling "ModelState", by simply removing unwanted validations from it.
...In some class...
public bool PropertyThatRequiredAnotherFieldToBeFilled
{
get;
set;
}
[Required(ErrorMessage = "*")]
public string DepentedProperty
{
get;
set;
}
...class continues...
...In some controller action ...
if (!PropertyThatRequiredAnotherFieldToBeFilled)
{
this.ModelState.Remove("DepentedProperty");
}
...
With this we achieve conditional validation, while leaving everything else the same.
UPDATE:
This is my final implementation: I have used an interface on the model and the action attribute that validates the model which implements the said interface. Interface prescribes the Validate(ModelStateDictionary modelState) method. The attribute on action just calls the Validate(modelState) on IValidatorSomething.
I did not want to complicate this answer, so I did not mention the final implementation details (which, at the end, matter in production code).
I had the same problem yesterday but I did it in a very clean way which works for both client side and server side validation.
Condition: Based on the value of other property in the model, you want to make another property required. Here is the code
public class RequiredIfAttribute : RequiredAttribute
{
private String PropertyName { get; set; }
private Object DesiredValue { get; set; }
public RequiredIfAttribute(String propertyName, Object desiredvalue)
{
PropertyName = propertyName;
DesiredValue = desiredvalue;
}
protected override ValidationResult IsValid(object value, ValidationContext context)
{
Object instance = context.ObjectInstance;
Type type = instance.GetType();
Object proprtyvalue = type.GetProperty(PropertyName).GetValue(instance, null);
if (proprtyvalue.ToString() == DesiredValue.ToString())
{
ValidationResult result = base.IsValid(value, context);
return result;
}
return ValidationResult.Success;
}
}
Here PropertyName is the property on which you want to make your condition
DesiredValue is the particular value of the PropertyName (property) for which your other property has to be validated for required
Say you have the following
public class User
{
public UserType UserType { get; set; }
[RequiredIf("UserType", UserType.Admin, ErrorMessageResourceName = "PasswordRequired", ErrorMessageResourceType = typeof(ResourceString))]
public string Password
{
get;
set;
}
}
At last but not the least , register adapter for your attribute so that it can do client side validation (I put it in global.asax, Application_Start)
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(RequiredIfAttribute),typeof(RequiredAttributeAdapter));
I've been using this amazing nuget that does dynamic annotations ExpressiveAnnotations
You could validate any logic you can dream of:
public string Email { get; set; }
public string Phone { get; set; }
[RequiredIf("Email != null")]
[RequiredIf("Phone != null")]
[AssertThat("AgreeToContact == true")]
public bool? AgreeToContact { get; set; }
You can disable validators conditionally by removing errors from ModelState:
ModelState["DependentProperty"].Errors.Clear();
Thanks Merritt :)
I've just updated this to MVC 3 in case anyone finds it useful: Conditional Validation in ASP.NET MVC 3.
There is now a framework that does this conditional validation (among other handy data annotation validations) out of the box:
http://foolproof.codeplex.com/
Specifically, take a look at the [RequiredIfTrue("IsSenior")] validator. You put that directly on the property you want to validate, so you get the desired behavior of the validation error being associated to the "Senior" property.
It is available as a NuGet package.
You need to validate at Person level, not on Senior level, or Senior must have a reference to its parent Person. It seems to me that you need a self validation mechanism that defines the validation on the Person and not on one of its properties. I'm not sure, but I don't think DataAnnotations supports this out of the box. What you can do create your own Attribute that derives from ValidationAttribute that can be decorated on class level and next create a custom validator that also allows those class-level validators to run.
I know Validation Application Block supports self-validation out-of the box, but VAB has a pretty steep learning curve. Nevertheless, here's an example using VAB:
[HasSelfValidation]
public class Person
{
public string Name { get; set; }
public bool IsSenior { get; set; }
public Senior Senior { get; set; }
[SelfValidation]
public void ValidateRange(ValidationResults results)
{
if (this.IsSenior && this.Senior != null &&
string.IsNullOrEmpty(this.Senior.Description))
{
results.AddResult(new ValidationResult(
"A senior description is required",
this, "", "", null));
}
}
}
I had the same problem, needed a modification of [Required] attribute - make field required in dependence of http request.The solution was similar to Dan Hunex answer, but his solution didn't work correctly (see comments). I don't use unobtrusive validation, just MicrosoftMvcValidation.js out of the box.
Here it is. Implement your custom attribute:
public class RequiredIfAttribute : RequiredAttribute
{
public RequiredIfAttribute(/*You can put here pararmeters if You need, as seen in other answers of this topic*/)
{
}
protected override ValidationResult IsValid(object value, ValidationContext context)
{
//You can put your logic here
return ValidationResult.Success;//I don't need its server-side so it always valid on server but you can do what you need
}
}
Then you need to implement your custom provider to use it as an adapter in your global.asax
public class RequreIfValidator : DataAnnotationsModelValidator <RequiredIfAttribute>
{
ControllerContext ccontext;
public RequreIfValidator(ModelMetadata metadata, ControllerContext context, RequiredIfAttribute attribute)
: base(metadata, context, attribute)
{
ccontext = context;// I need only http request
}
//override it for custom client-side validation
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
//here you can customize it as you want
ModelClientValidationRule rule = new ModelClientValidationRule()
{
ErrorMessage = ErrorMessage,
//and here is what i need on client side - if you want to make field required on client side just make ValidationType "required"
ValidationType =(ccontext.HttpContext.Request["extOperation"] == "2") ? "required" : "none";
};
return new ModelClientValidationRule[] { rule };
}
}
And modify your global.asax with a line
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(RequiredIfAttribute), typeof(RequreIfValidator));
and here it is
[RequiredIf]
public string NomenclatureId { get; set; }
The main advantage for me is that I don't have to code custom client validator as in case of unobtrusive validation. it works just as [Required], but only in cases that you want.
Check out Simon Ince's Conditional Validation in MVC.
I am working through his example project right now.
Typical usage for conditional removal of error from Model State:
Make conditional first part of controller action
Perform logic to remove error from ModelState
Do the rest of the existing logic (typically Model State validation, then everything else)
Example:
public ActionResult MyAction(MyViewModel vm)
{
// perform conditional test
// if true, then remove from ModelState (e.g. ModelState.Remove("MyKey")
// Do typical model state validation, inside following if:
// if (!ModelState.IsValid)
// Do rest of logic (e.g. fetching, saving
In your example, keep everything as is and add the logic suggested to your Controller's Action. I'm assuming your ViewModel passed to the controller action has the Person and Senior Person objects with data populated in them from the UI.
I'm using MVC 5 but you could try something like this:
public DateTime JobStart { get; set; }
[AssertThat("StartDate >= JobStart", ErrorMessage = "Time Manager may not begin before job start date")]
[DisplayName("Start Date")]
[Required]
public DateTime? StartDate { get; set; }
In your case you would say something like "IsSenior == true".
Then you just need to check the validation on your post action.