Can I dynamically turn off validation in MVC? - asp.net-mvc

I have a search form where I can search on one of two fields. Only one of the two is required. Is there a way to cleanly do this in the MVC provided validation?

If this is server validation, you could create a Custom Validation Attribute for this:
[ConditionalRequired("Field1","Field2")]
public class MyModel()
{
public string Field1 { get; set; }
public string Field2 { get; set; }
}
Then you can create a custom validation attribute.. something like the following:
[AttributeUsage( AttributeTargets.Class, AllowMultiple = true, Inherited = true )]
public sealed class ConditionalRequiredAttribute : ValidationAttribute
{
private const string _defaultErrorMessage = "Either '{0}' or '{1}' must be provided.";
public string FirstProperty { get; private set; }
public string SecondProperty { get; private set; }
public ConditionalRequiredAttribute( string first, string second )
: base( _defaultErrorMessage )
{
FirstProperty = first;
SecondProperty = second;
}
public override string FormatErrorMessage( string name )
{
return String.Format( CultureInfo.CurrentUICulture, ErrorMessageString,
FirstProperty, SecondProperty );
}
public override bool IsValid( object value )
{
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties( value );
string originalValue = properties.Find( FirstProperty, true ).GetValue( value ).ToString(); // ToString() MAY through a null reference exception.. i haven't tested it at all
string confirmValue = properties.Find( SecondProperty, true ).GetValue( value ).ToString( );
return !String.IsNullOrWhiteSpace( originalValue ) || !String.IsNullOrWhiteSpace( confirmValue ); // ensure at least one of these values isn't null or isn't whitespace
}
}
Its a little verbose, but it gives you the flexibility to able this attribute directly to your model as opposed to applying it to each individual field

Related

ASP.NET MVC - Pass model value to data annotation parameter

I want to pass a value from one of my properties in my model to my data annotation to validate my password property, but I have no idea how I can achieve this. When I am doing this at this way I get the following error:
an attribute argument must be a constant expression typeof expression or array
My model:
public class LoginModel
{
public string Voornaam { get; set; }
public string Achternaam { get; set; }
public string Gebruikersnaam { get; set; }
[Password(AttributeVoornaam = this.Voornaam, AttributeAchternaam = this.Achternaam, AttributeGebruikersnaam = this.Gebruikersnaam)]
public string Wachtwoord { get; set; }
}
And in my data annotation I am doing this:
public class PasswordAttribute : ValidationAttribute
{
public string AttributeVoornaam { get; set; }
public string AttributeAchternaam { get; set; }
public string AttributeGebruikersnaam { get; set; }
public override bool IsValid(object value)
{
string strValue = value.ToString();
if (strValue.Contains(AttributeVoornaam.ToLower()) || strValue.Contains(AttributeAchternaam.ToLower()) ||
strValue.Contains(AttributeGebruikersnaam.ToLower()))
{
ErrorMessage = "Uw wachtwoord mag niet uw voornaam, achternaam of gebruikersnaam bevatten.";
return false;
}
else
{
return true;
}
}
}
You can't pass variable values (values that are not evaluated at compile-time) into attributes. They have to be literal values or constant values.
What you can pass into attributes, though, are the names of the properties of your model that you want to evaluate at run-time, and then have your IsValid method evaluate these values at run-time by accessing the ValidationContext in the override that returns a ValidationResult of ValidationAttribute.
Or, if you are always evaluating these same properties, then you can just grab the reference to your model, and use that directly:
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
LoginModel loginModel = (LoginModel)validationContext.ObjectInstance;
string strValue = value.ToString();
if (strValue.Contains(loginModel.Voornaam.ToLower()) ||
strValue.Contains(loginModel.Achternaam.ToLower()) ||
strValue.Contains(loginModel.Gebruikersnaam.ToLower()))
{
ErrorMessage = "Uw wachtwoord mag niet uw voornaam, achternaam of gebruikersnaam bevatten.";
return false;
}
else
{
return true;
}
}
It's not possible, because the attributes, including their data, are placed into the metadata of the assembly at compile-time. See Attribute parameter types on MSDN.
Instead you can pass a name of the dependent property as a string. I will show you a sample with one property and you will add others the same way:
public class PasswordAttribute : ValidationAttribute
{
public PasswordAttribute(string voornaamPropertyName)
{
if(string.IsNullOrEmpty(voornaamPropertyName))
throw new ArgumentNullException("voornaamPropertyName");
VoornaamPropertyName = voornaamPropertyName;
}
public string VoornaamPropertyName { get; set; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
PropertyInfo voornaamPropertyInfo = validationContext.ObjectType.GetProperty(VoornaamPropertyName);
if (voornaamPropertyInfo == null)
{
return new ValidationResult(String.Format(CultureInfo.CurrentCulture, "Could not find a property named {0}", VoornaamPropertyName));
}
var voornaamProperty = voornaamPropertyInfo.GetValue(validationContext.ObjectInstance); // here you have the value of the property
...
}
}
Then
[Password("Voornaam")]
public string Wachtwoord { get; set; }
As far as I know, you can't pass the variable values into attributes. You could add custom validation rule to your model:
public class LoginModel: IValidatableObject
{
public string Voornaam {get;set;}
public string Achternaam {get;set;}
public string Gebruikersnaam {get;set;}
public string Wachtwoord { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var results = new List<ValidationResult>();
var pwd = this.Wachtwoord.ToLower();
if(pwd.Contains(this.Voornaam.ToLower()) || pwd.Contains(this.Achternaam.ToLower()) || pwd.Contains(this.Gebruikersnaam.ToLower())){
results.Add(new ValidationResult("Uw wachtwoord mag niet uw voornaam, achternaam of gebruikersnaam bevatten."));
}
return results;
}
}
You could also change it into multiple if statements and add seperate ValidationResults (one for Voornaam, one for Achternaam and one for Gebruikersnaam).

Customizing DisplayFormat beyond DataFormatString

I have a MVC 4 project where I would like to use functionality similar to DisplayFromat, but setting a DataFormatString is not enough. I would like a function to be called to format the string. Is that possible?
I have tested inheriting DisplayFormat but that just lets me set the DataFormatString.
I have looked at customizing DataAnnotationsModelMetadataProvider, but I don't see how I would make it call a custom function for formatting.
My particular case is that I need to format the integer 201351 as "w51 2013". I couldn't come up with a format string that does that.
The easiest way is to expose a read-only property on your Model:
public class Model{
public int mydata{get; set;}
public string formattedDate{
get{
string formattedval;
// format here
return formattedval;
};
}
}
You can create a custom ValidationAttribute. Here is some code I use to validation someone has selected a drop down value.
using System.ComponentModel.DataAnnotations;
public sealed class PleaseSelectAttribute : ValidationAttribute
{
private readonly string _placeholderValue;
public override bool IsValid(object value)
{
var stringValue = value.ToString();
if (stringValue == _placeholderValue || stringValue == "-1")
{
ErrorMessage = string.Format("The {0} field is required.", _placeholderValue);
return false;
}
return true;
}
public PleaseSelectAttribute(string placeholderValue)
{
_placeholderValue = placeholderValue;
}
}
Then Use it:
[Required]
[Display(Name = "Customer")]
[PleaseSelect("Customer")]
public int CustomerId { get; set; }

MVC disable unobtrusive validation for specific validator

I'm currently building a MVC4 project which uses unobtrusive validation through data annotations.
I have a specific field, "PostalCode", which is both [Required] and has a [RegularExpression] validator attached to it.
The thing is, this regular expression should only be verified if a specific country is selected. (This country will be the default value and we can assume in nearly all cases this will be used)
Now I need some way to disable this regex validation when a different country is selected, while keeping the required validator active.
Nearly all sollutions I've found are using a jQuery.validator.defaults.ignore filter, but this would disable both validators on that item.
Any thoughts on how to best tackle this problem?
Edit: Small code snippet to show how this is working.
[Required]
[RegularExpression("^[1-9]\\d{3} ?[a-zA-Z]{2}$"] //Should only be verified if Country == "The Netherlands"
string PostalCode{get;set;}
[Required]
string Country {get;set;}
In the end I made up my own ValidationAttribute based on this blog post: http://thewayofcode.wordpress.com/2012/01/18/custom-unobtrusive-jquery-validation-with-data-annotations-in-mvc-3/
It's an elegant sollution and required way less work than I anticipated.
Edit: As per request, I provide the sollution created by myself:
// DependentRegularExpressionAttribute.cs
/// <summary>
/// Only performs a regular expression validation if a specified other property meets a validation requirement
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public class DependentRegularExpressionAttribute : ValidationAttribute, IClientValidatable
{
private readonly Regex _regex;
private readonly string _otherPropertyName;
private readonly Regex _otherPropertyRegex;
public DependentRegularExpressionAttribute(string regex, string otherPropertyName, string otherPropertyRegex)
{
_regex = new Regex(regex);
_otherPropertyName = otherPropertyName;
_otherPropertyRegex = new Regex(otherPropertyRegex);
}
/// <summary>
/// Format the error message filling in the name of the property to validate and the reference one.
/// </summary>
/// <param name="name">The name of the property to validate</param>
/// <returns>The formatted error message</returns>
public override string FormatErrorMessage(string name)
{
return string.Format(ErrorMessageString, name, _regex, _otherPropertyName, _otherPropertyRegex);
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var validationResult = ValidationResult.Success;
if (value == null || String.IsNullOrEmpty(value as string))
return validationResult;
// Using reflection we can get a reference to the other property
var otherPropertyInfo = validationContext.ObjectType.GetProperty(_otherPropertyName);
var otherPropertyValue = otherPropertyInfo.GetValue(validationContext.ObjectInstance, null);
if (otherPropertyValue == null || String.IsNullOrEmpty(otherPropertyValue as string))
return validationResult;
if (_otherPropertyRegex.IsMatch(otherPropertyValue.ToString()))
{
if (!_regex.IsMatch(value.ToString()))
validationResult = new ValidationResult(ErrorMessage);
}
return validationResult;
}
#region IClientValidatable Members
/// <summary>
///
/// </summary>
/// <param name="metadata"></param>
/// <param name="context"></param>
/// <returns></returns>
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
string errorMessage = FormatErrorMessage(metadata.DisplayName ?? metadata.PropertyName);
// The value we set here are needed by the jQuery adapter
var dependentRegexRule = new ModelClientValidationRule
{
ErrorMessage = errorMessage,
ValidationType = "dependentregex"
};
//"otherpropertyname" is the name of the jQuery parameter for the adapter, must be LOWERCASE!
dependentRegexRule.ValidationParameters.Add("otherpropertyname", _otherPropertyName);
dependentRegexRule.ValidationParameters.Add("regex", _regex);
dependentRegexRule.ValidationParameters.Add("otherpropertyregex", _otherPropertyRegex);
yield return dependentRegexRule;
}
#endregion
}
// customvalidation.js
$.validator.addMethod("dependentregex", function (value, element, params) {
var regex = new RegExp(params[0]);
var otherElement = document.getElementById(params[1]);
var otherElementRegex = new RegExp(params[2]);
if (!value || !otherElement.value)
return true;
if (otherElementRegex.test(otherElement.value)) {
if (!regex.test(element.value))
return false;
}
return true;
});
$.validator.unobtrusive.adapters.add("dependentregex", ["regex", "otherpropertyname", "otherpropertyregex"], function(options) {
options.rules["dependentregex"] = [options.params.regex,
options.params.otherpropertyname,
options.params.otherpropertyregex];
options.messages["dependentregex"] = options.message;
});
Inside my viewmodel I do the following:
[Display(Name = "Customer_PostalCode", ResourceType = typeof(Resources.DisplayNames))]
[DependentRegularExpression("^[1-9]\\d{3} ?[a-zA-Z]{2}$", "CorrespondenceCountry", "Nederland", ErrorMessageResourceType = typeof(Resources.Validation), ErrorMessageResourceName = "Customer_PostalCode")] //"Nederland" is a regular expression in this case
[Required(ErrorMessageResourceType = typeof(Resources.Validation), ErrorMessageResourceName = "Shared_RequiredField")]
public string CorrespondenceZipCode { get; set; }
In the end, the customvalidation.js method basically does the exact same thing as the C# code. A detailed explanation of what everything does can be found in the blogpost I referenced
It seems like you want a "required if" validation attribute. I would check out http://foolproof.codeplex.com/ - it has an implementation that you can use like so (lifted directly from the project's site):
private class Person
{
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
public bool Married { get; set; }
[RequiredIfTrue("Married")]
public string MaidenName { get; set; }
}
Take a look at this, I haven't used it myself, but it seems to suit your needs http://foolproof.codeplex.com/workitem/18974
They have an example that looks like this:
[RequiredIf("Country", Operator.RegExMatch, "(1033|4105)", ErrorMessage = "{0} is required")]
public string State { get; set; }
[Required(ErrorMessage = "{0} is required")]
public int Country { get; set; }

Data validation with custom attributes (AttributeTargets.Class) on EF buddy classes

I have an Entity Framework generated class with the following properties:
public DateTime LaunchDate;
public DateTime ExpirationDate;
I need to enforce that ExpirationDate > LaunchDate.
I am using a buddy class as described in various posts.
I am applying custom validation attributes (on properties) to other properties in the same buddy class and these are working.
Since I need to compare two properties I am using an attribute directly on the class (AttributeTargets.Class)
Here is my custom validation attribute:
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public class PropertyMustBeGreaterThanAttribute : ValidationAttribute
{
private const string _defaultErrorMessage = "'{0}' must be greater than '{1}'!";
public PropertyMustBeGreaterThanAttribute(string property, string greaterThan)
: base(_defaultErrorMessage)
{
GreaterThan = greaterThan;
Property = property;
}
public string Property { get; private set; }
public string GreaterThan { get; private set; }
public override string FormatErrorMessage(string name)
{
return String.Format(CultureInfo.CurrentUICulture, ErrorMessageString,
GreaterThan, Property);
}
public override bool IsValid(object value)
{
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(value);
IComparable greaterThan = properties.Find(GreaterThan, true /* ignoreCase */).GetValue(value) as IComparable;
IComparable property = properties.Find(Property, true /* ignoreCase */).GetValue(value) as IComparable;
return greaterThan.CompareTo(property) > 0;
}
}
First I am unsure to which class I need to apply the attribute to:
[MetadataType(typeof(PromotionValidation))]
[PropertyMustBeGreaterThanAttribute("RetailPrice")] // apply it here
public partial class Promotion
{
[PropertyMustBeGreaterThanAttribute("ExpirationDate", "LaunchDate")] // or here ???
public class PromotionValidation
{
Second, it's not working and I have no clue why!!!
I've tried adding attribute to both classes. Breakpoints in the constructor are hit (PropertyMustBeGreaterThanAttribute ), but IsValid is never called.
Pulling my hair out already....
Thanks!
Got it to work by implementing TypeID.
I assigned the attribute to the partial class:
[MetadataType(typeof(PromotionValidation))]
[PropertyMustBeGreaterThanAttribute("RetailPrice", "DiscountedPrice")]
[PropertyMustBeGreaterThanAttribute("ExpirationDate", "LaunchDate")]
[PropertyMustBeGreaterThanAttribute("ClaimDeadline", "ExpirationDate")]
public partial class Promotion
{
...
Here is the listing in case anyone needs it:
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public class PropertyMustBeGreaterThanAttribute : ValidationAttribute
{
private const string _defaultErrorMessage = "'{1}' must be greater than '{0}'!";
private readonly object _typeId = new object();
public PropertyMustBeGreaterThanAttribute(string property, string greaterThan)
: base(_defaultErrorMessage)
{
GreaterThan = greaterThan;
Property = property;
}
public string Property { get; private set; }
public string GreaterThan { get; private set; }
public override string FormatErrorMessage(string name)
{
return String.Format(CultureInfo.CurrentUICulture, ErrorMessageString,
GreaterThan, Property);
}
public override bool IsValid(object value)
{
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(value);
IComparable greaterThan = properties.Find(GreaterThan, true /* ignoreCase */).GetValue(value) as IComparable;
IComparable property = properties.Find(Property, true /* ignoreCase */).GetValue(value) as IComparable;
return greaterThan.CompareTo(property) > 0;
}
public override object TypeId
{
get
{
return _typeId;
}
}
}

asp.net MVC DataAnnotations

Please ask if you can't understand what I'm asking.
I have created a custom ValidateAttribute for my ViewModel
i created it for validate properties which depend from another property of ViewModel
if (user checked "01" or "09" from QrupList) Then
Company name is needed
Name,surname and LastName are not needed
else
Company name is not needed
Name,surname and LastName are needed
I have ViewModel as below
[ValidateForGroupAttribute("Group", "CompanyName")]
public partial class AbonentViewModel
{
[DisplayName("Şirkət")]
public string CompanyName { get; set; }
[DisplayName("Soyadı")]
[Required(ErrorMessage = "Soyadı vacibdir")]
public string Surname { get; set; }
[DisplayName("Qrup")]
[Required(ErrorMessage = "Qrup vacibdir")]
public string Group{ get; set; }
public SelectList GroupList { get; set; }
}
My custom ValidationAttribute classes:
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public sealed class ValidateForGroupAttribute : ValidationAttribute
{
private const string _defaultErrorMessage = "'{0}' a müvafiq '{1}' daxil din";
public ValidateForGroupAttribute(string originalProperty, string confirmPropertyCompany)
: base(_defaultErrorMessage)
{
OriginalProperty = originalProperty;
ConfirmPropertyCompany = confirmPropertyCompany;
}
public string OriginalProperty { get; private set; }
public override string FormatErrorMessage(string name)
{
return String.Format(CultureInfo.CurrentUICulture, ErrorMessageString,
OriginalProperty, ConfirmPropertyCompany);
}
public override bool IsValid(object value)
{
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(value);
object originalValue = properties.Find(OriginalProperty, true).GetValue(value);
object confirmValueCompany = properties.Find(ConfirmPropertyCompany, true).GetValue(value);
if ((string)originalValue == "01" || (string)originalValue == "09")
return false;
else
return true;
}
}
How do I do it? What is wrong in my ValidationAttributes?
We looked at validation using data annotations a few months back, and decided it was better to use something like fluent validation, as we had complex business rules and logic that would have taken too much effort to realise with data annotations. Have a look at the documentation, and you will see fluent validation makes things like this easy.
Sorry, I did not get back sooner: Check fluent validation here
Your rule could look something like. Syntax not tested, but I am sure you will be able to figure it out.
public class AbonentViewModelValidator : AbstractValidator<AbonentViewModel> {
public AbonentViewModelValidator() {
RuleFor(model => model.CompanyName).NotEmpty().When(model => (model.GroupList.Id == 1 || model.GroupList.Id == 9 ));
RuleFor(model => model.Surname).NotEmpty().When(model => (model.GroupList.Id != 1 || model.GroupList.Id != 9 ));
RuleFor(model => model.Name).NotEmpty().When(model => (model.GroupList.Id != 1 || model.GroupList.Id != 9 ));
}
}

Resources