Reputation: 2352
Is there a way to switch off validation of some model properties based on the bound model value. Example:
public class ContactModel
{
public string Name { get; set; }
public string ContactType { get; set; }
[Required]
public string Phone { get; set; }
[Required]
public string Email { get; set; }
}
This is some view model, which should retrieve the name of the person, and either his Phone or Email. If user select one of these, it should validate only the selected one. But when Phone is selected, and Email
is empty, the ModelState.IsValid == false
and vice versa.
I have tried to hook into the validation process (custom ValidationAttribute
, IValidatableObject
) but with no success, as the ValidationContext doesn't provide any useful context - I need to access the ContactType
value when validating Phone/Email
. I have even tried to create custom binder, but supressing validation in that stage doesn't provide intended results as it only sets the state of validation to Unvalidated, which is considered as ModelState.IsValid
.
Only solution I have found so far is to create ActionFilter/PageFilter
which will find if there is any model of ContactModel
type, and remove the validation errors for unused fields from ModelState. But I don't feel comfortable with such solution (doesn't seems right to me).
Have any ideas?
Upvotes: 1
Views: 320
Reputation: 28032
According to your description, I suggest you could consider using custom CustomModelValidator and custom ModelValidator to achieve your requirement.
By using this, we could get the whole model by using its container value.
More details, you could refer to below codes:
CustomModelValidatorProvider class:
public void CreateValidators(ModelValidatorProviderContext context)
{
if (context.ModelMetadata.ContainerType == typeof(ContactModel))
{
context.Results.Add(new ValidatorItem
{
Validator = new ContactModelValidator(),
IsReusable = true
});
}
}
public class ContactModelValidator : IModelValidator
{
private static readonly object _emptyValidationContextInstance = new object();
public IEnumerable<ModelValidationResult> Validate(ModelValidationContext validationContext)
{
var validationResults = new List<ModelValidationResult>();
if (validationContext.ModelMetadata.Name == "Name" && validationContext.Model == null)
{
var validationResult = new ModelValidationResult("", "Name is required");
validationResults.Add(validationResult);
}
if (validationContext.ModelMetadata.Name == "ContactType" && validationContext.Model == null)
{
var validationResult = new ModelValidationResult("", "ContactType is required");
validationResults.Add(validationResult);
}
if (validationContext.ModelMetadata.Name == "ContactType" && validationContext.Model == null)
{
var validationResult = new ModelValidationResult("", "ContactType is required");
validationResults.Add(validationResult);
}
if (validationContext.ModelMetadata.Name == "Phone" || validationContext.ModelMetadata.Name == "Email")
{
if (((ContactModel)validationContext.Container).Phone == null && ((ContactModel)validationContext.Container).Email == null)
{
var validationResult = new ModelValidationResult("", "Phone or Email is required");
validationResults.Add(validationResult);
}
}
return validationResults;
}
}
Then add it into startup.cs ConfigureServices method:
services.AddControllers(options=> {
options.ModelValidatorProviders.Add(new CustomModelValidatorProvider());
});
Result:
Upvotes: 1