What is the opposite/negate of [Compare(" ")] data annotation" in ASP.NET?
i.e: two properties must hold different values.
public string UserName { get; set; }
[Something["UserName"]]
public string Password { get; set; }
You can use the [NotEqualTo] data annotation operator included in MVC Foolproof Validation. I used it right now and it works great!
MVC Foolproof is an open source library created by #nick-riggs and has a lot of available validators. Besides doing server side validation it also does client side unobtrusive validation.
Full list of built in validators you get out of the box:
Included Operator Validators
[Is]
[EqualTo]
[NotEqualTo]
[GreaterThan]
[LessThan]
[GreaterThanOrEqualTo]
[LessThanOrEqualTo]
Included Required Validators
[RequiredIf]
[RequiredIfNot]
[RequiredIfTrue]
[RequiredIfFalse]
[RequiredIfEmpty]
[RequiredIfNotEmpty]
[RequiredIfRegExMatch]
[RequiredIfNotRegExMatch]
This is the implementation (server side) of the link that #Sverker84 referred to.
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class UnlikeAttribute : ValidationAttribute
{
private const string DefaultErrorMessage = "The value of {0} cannot be the same as the value of the {1}.";
public string OtherProperty { get; private set; }
public UnlikeAttribute(string otherProperty)
: base(DefaultErrorMessage)
{
if (string.IsNullOrEmpty(otherProperty))
{
throw new ArgumentNullException("otherProperty");
}
OtherProperty = otherProperty;
}
public override string FormatErrorMessage(string name)
{
return string.Format(ErrorMessageString, name, OtherProperty);
}
protected override ValidationResult IsValid(object value,
ValidationContext validationContext)
{
if (value != null)
{
var otherProperty = validationContext.ObjectInstance.GetType()
.GetProperty(OtherProperty);
var otherPropertyValue = otherProperty
.GetValue(validationContext.ObjectInstance, null);
if (value.Equals(otherPropertyValue))
{
return new ValidationResult(
FormatErrorMessage(validationContext.DisplayName));
}
}
return ValidationResult.Success;
}
}
Usage:
public string UserName { get; set; }
[Unlike("UserName")]
public string AlternateId { get; set; }
Details about this implementation, and how to implement it client-side can be found here:
http://www.devtrends.co.uk/blog/the-complete-guide-to-validation-in-asp.net-mvc-3-part-2
http://www.macaalay.com/2014/02/25/unobtrusive-client-and-server-side-not-equal-to-validation-in-mvc-using-custom-data-annotations/
The complete code for both server side and client side validation is as follows:
[AttributeUsage(AttributeTargets.Property)]
public class UnlikeAttribute : ValidationAttribute, IClientModelValidator
{
private string DependentProperty { get; }
public UnlikeAttribute(string dependentProperty)
{
if (string.IsNullOrEmpty(dependentProperty))
{
throw new ArgumentNullException(nameof(dependentProperty));
}
DependentProperty = dependentProperty;
}
protected override ValidationResult IsValid(object value,
ValidationContext validationContext)
{
if (value != null)
{
var otherProperty = validationContext.ObjectInstance.GetType().GetProperty(DependentProperty);
var otherPropertyValue = otherProperty.GetValue(validationContext.ObjectInstance, null);
if (value.Equals(otherPropertyValue))
{
return new ValidationResult(ErrorMessage);
}
}
return ValidationResult.Success;
}
public void AddValidation(ClientModelValidationContext context)
{
MergeAttribute(context.Attributes, "data-val", "true");
MergeAttribute(context.Attributes, "data-val-unlike", ErrorMessage);
// Added the following code to account for the scenario where the object is deeper in the model's object hierarchy
var idAttribute = context.Attributes["id"];
var lastIndex = idAttribute.LastIndexOf('_');
var prefix = lastIndex > 0 ? idAttribute.Substring(0, lastIndex + 1) : string.Empty;
MergeAttribute(context.Attributes, "data-val-unlike-property", $"{prefix}{DependentProperty}");
}
private void MergeAttribute(IDictionary<string, string> attributes,
string key,
string value)
{
if (attributes.ContainsKey(key))
{
return;
}
attributes.Add(key, value);
}
}
Then include the following in JavaScript:
$.validator.addMethod('unlike',
function (value, element, params) {
var propertyValue = $(params[0]).val();
var dependentPropertyValue = $(params[1]).val();
return propertyValue !== dependentPropertyValue;
});
$.validator.unobtrusive.adapters.add('unlike',
['property'],
function (options) {
var element = $(options.form).find('#' + options.params['property'])[0];
options.rules['unlike'] = [element, options.element];
options.messages['unlike'] = options.message;
});
Usage is as follows:
public int FromId { get; set; }
[Unlike(nameof(FromId), ErrorMessage = "From ID and To ID cannot be the same")]
public int ToId { get; set; }
Use this in your get/set logic:
stringA.Equals(stringB) == false
In addition to solution given by #Eitan K, If you want to use other property's display name instead of other property's name, use this snippet:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class UnlikeAttribute : ValidationAttribute
{
private const string DefaultErrorMessage = "The value of {0} cannot be the same as the value of the {1}.";
public string OtherPropertyDisplayName { get; private set; }
public string OtherProperty { get; private set; }
public UnlikeAttribute(string otherProperty)
: base(DefaultErrorMessage)
{
if (string.IsNullOrEmpty(otherProperty))
{
throw new ArgumentNullException("otherProperty");
}
OtherProperty = otherProperty;
}
public override string FormatErrorMessage(string name)
{
return string.Format(ErrorMessageString, name, OtherPropertyDisplayName);
}
protected override ValidationResult IsValid(object value,
ValidationContext validationContext)
{
if (value != null)
{
var otherProperty = validationContext.ObjectInstance.GetType()
.GetProperty(OtherProperty);
var otherPropertyValue = otherProperty
.GetValue(validationContext.ObjectInstance, null);
if (value.Equals(otherPropertyValue))
{
OtherPropertyDisplayName = otherProperty.GetCustomAttribute<DisplayAttribute>().Name;
return new ValidationResult(
FormatErrorMessage(validationContext.DisplayName));
}
}
return ValidationResult.Success;
}
}
Related
I have a public property which is an object that contains numerous properties itself. Using ASP.net MVC, when I serialize the JSON data I simply add the [JsonIgnore] attribute wherever I use the object so it doesn't display the contents.
Is there a way to add the [JsonIgnore] attribute to the class so it never is serialized?
//[JsonIgnore] ??
public class DataObj
{
public string ConnectionName { get; set; }
public string Query { get; set; }
...
}
public class Customer
{
public string First { get; set; }
public string Last { get; set; }
[JsonIgnore]
public DataObj Foo { get; set; }
}
public class ShipAddress
{
public string Street { get; set; }
public string City { get; set; }
public string Country { get; set; }
[JsonIgnore]
public DataObj Foo { get; set; }
}
My solution after receiving the code provided by jvanrhyn.
Also, here is a link that explains more.
public class DataObjFilterContractResolver : DefaultContractResolver
{
public static readonly DataObjFilterContractResolver Instance = new DataObjFilterContractResolver();
protected override JsonProperty CreateProperty(MemberInfo member,MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (property.DeclaringType.Name.StartsWith("DataObj") || property.PropertyName == "DataObj")
{
property.ShouldSerialize = instance => false;
}
return property;
}
}
public class UtcJsonResult : JsonResult
{
public UtcJsonResult(object data)
{
Data = data;
JsonRequestBehavior = JsonRequestBehavior.AllowGet;
}
private const string DateFormat = #"yyyy-MM-dd HH:mm:ssZ";
public override void ExecuteResult(ControllerContext context)
{
if (context == null) throw new ArgumentNullException("context");
if (Data == null) return;
var response = context.HttpContext.Response;
response.ContentType = !string.IsNullOrEmpty(ContentType) ? ContentType : "application/json";
if (ContentEncoding != null) response.ContentEncoding = ContentEncoding;
var isoConvert = new IsoDateTimeConverter {DateTimeFormat = DateFormat};
JsonConvert.DefaultSettings =
() => new JsonSerializerSettings
{ ContractResolver = new DataObjFilterContractResolver()}; //<--- Used here
var json = JsonConvert.SerializeObject(Data, isoConvert);
response.Write(json);
}
}
You can add a Contract Resolver in your project.
public class ShouldSerializeContractResolver : DefaultContractResolver
{
public new static readonly ShouldSerializeContractResolver Instance =
new ShouldSerializeContractResolver();
protected override JsonProperty CreateProperty(MemberInfo member,
MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
if (property.DeclaringType == typeof(DataObj))
{
property.ShouldSerialize =
instance =>
{
return false;
};
}
return property;
}
}
Summary:
I want a data annotation validator to reference another property in the same class (TitleAuthorAndPublishingConfiguration).
However, DB.SaveChanges() is not being called on this class directly. Rather it is being called on the parent of this class (WebsiteConfiguration).
Therefore validationContext.ObjectType is returning WebsiteConfiguration and I am unable to refer to properties of TitleAuthorAndPublishingConfiguration within the data annotation validator.
WebsiteConfiguration.cs
public class WebsiteConfiguration
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public TitleAuthorAndPublishingConfiguration TitleAuthorAndPublishing { get; set; }
public BookChaptersAndSectionsConfiguration BookChaptersAndSections { get; set; }
public SocialMediaLoginsConfiguration SocialMediaLogins { get; set; }
public TagGroupsConfiguration TagGroups { get; set; }
}
public class TitleAuthorAndPublishingConfiguration
{
public string BookTitle { get; set; }
public bool IsPublished { get; set; }
// how do I access a property of current model when calling DB.SaveChanges() on parent?
[RequiredIfOtherFieldIsEnabled("IsPublished")]
public string Publisher { get; set; }
}
// ... and other sub models...
ApplicationDbContext.cs
DbSet<WebsiteConfiguration> WebsiteConfiguration {get;set;}
Example Update Code
public void SeedWebsiteConfiguration()
{
var titleAuthorAndPublishingConfiguration = new TitleAuthorAndPublishingConfiguration()
{
// seed values
};
var bookChaptersAndSectionsConfiguration = new BookChaptersAndSectionsConfiguration()
{
// seed values
};
var socialMediaLoginConfiguration = new SocialMediaLoginsConfiguration()
{
// seed values
};
var tagGroupsConfiguration = new TagGroupsConfiguration()
{
// seed values
};
var websiteConfiguration = new WebsiteConfiguration()
{
TitleAuthorAndPublishing = titleAuthorAndPublishingConfiguration,
BookChaptersAndSections = bookChaptersAndSectionsConfiguration,
SocialMediaLogins = socialMediaLoginConfiguration,
TagGroups = tagGroupsConfiguration
};
DB.WebsiteConfiguration.Add(websiteConfiguration);
DB.SaveChanges();
}
Validator Code
public class RequiredIfOtherFieldIsEnabledAttribute : ValidationAttribute
{
private string _ifWhatIsEnabled { get; set; }
public RequiredIfOtherFieldIsEnabledAttribute(string IfWhatIsEnabled)
{
_ifWhatIsEnabled = IfWhatIsEnabled;
}
protected override ValidationResult IsValid(object currentPropertyValue, ValidationContext validationContext)
{
var isEnabledProperty = validationContext.ObjectType.GetProperty(_ifWhatIsEnabled);
if (isEnabledProperty == null)
{
return new ValidationResult(
string.Format("Unknown property: {0}", _ifWhatIsEnabled)
);
}
var isEnabledPropertyValue = (bool)isEnabledProperty.GetValue(validationContext.ObjectInstance, null);
if (isEnabledPropertyValue == true)
{
if (String.IsNullOrEmpty(currentPropertyValue.ToString()))
{
return new ValidationResult(String.Format("This field is required if {0} is enabled", isEnabledProperty));
}
}
return ValidationResult.Success;
}
}
Questions
Is there a way for me to access child model properties from validationContext?
Am I misguided in my approach? Is there a better way to store multiple models as part of a larger model in a single DB table?
I was hoping not to have multiple config tables and calls to the DB. (There are 4 child models in this example, but there may be 10+ in the next app.)
The setup above meets my needs in so many ways. But I don't want to give up the functionality of DataAnnotations on the sub models!
Bonus Question
I have come across a few posts like this one:
How can I tell the Data Annotations validator to also validate complex child properties?
But that is 4 years old, and I'm wondering if anything has changed since then.
Am I trying to do something that is basically impossible (or at least very difficult)?
Am I trying to do something that is basically impossible (or at least
very difficult)?
No, there is a very simple solution that integrates perfectly with the framework and technologies using DataAnnotations.
You can create a custom ValidationAttribute that is called by EF Validation and call Validator.TryValidateObject inside. This way, when CustomValidation.IsValid is called by EF you launch child complex object validation by hand and so on for the whole object graph. As a bonus, you can gather all errors thanks to CompositeValidationResult.
i.e.
using System;
using System.ComponentModel.DataAnnotations;
using System.Collections.Generic;
public class Program
{
public static void Main() {
var person = new Person {
Address = new Address {
City = "SmallVille",
State = "TX",
Zip = new ZipCode()
},
Name = "Kent"
};
var context = new ValidationContext(person, null, null);
var results = new List<ValidationResult>();
Validator.TryValidateObject(person, context, results, true);
PrintResults(results, 0);
Console.ReadKey();
}
private static void PrintResults(IEnumerable<ValidationResult> results, Int32 indentationLevel) {
foreach (var validationResult in results) {
Console.WriteLine(validationResult.ErrorMessage);
Console.WriteLine();
if (validationResult is CompositeValidationResult) {
PrintResults(((CompositeValidationResult)validationResult).Results, indentationLevel + 1);
}
}
}
}
public class ValidateObjectAttribute: ValidationAttribute {
protected override ValidationResult IsValid(object value, ValidationContext validationContext) {
var results = new List<ValidationResult>();
var context = new ValidationContext(value, null, null);
Validator.TryValidateObject(value, context, results, true);
if (results.Count != 0) {
var compositeResults = new CompositeValidationResult(String.Format("Validation for {0} failed!", validationContext.DisplayName));
results.ForEach(compositeResults.AddResult);
return compositeResults;
}
return ValidationResult.Success;
}
}
public class CompositeValidationResult: ValidationResult {
private readonly List<ValidationResult> _results = new List<ValidationResult>();
public IEnumerable<ValidationResult> Results {
get {
return _results;
}
}
public CompositeValidationResult(string errorMessage) : base(errorMessage) {}
public CompositeValidationResult(string errorMessage, IEnumerable<string> memberNames) : base(errorMessage, memberNames) {}
protected CompositeValidationResult(ValidationResult validationResult) : base(validationResult) {}
public void AddResult(ValidationResult validationResult) {
_results.Add(validationResult);
}
}
public class Person {
[Required]
public String Name { get; set; }
[Required, ValidateObject]
public Address Address { get; set; }
}
public class Address {
[Required]
public String Street1 { get; set; }
public String Street2 { get; set; }
[Required]
public String City { get; set; }
[Required]
public String State { get; set; }
[Required, ValidateObject]
public ZipCode Zip { get; set; }
}
public class ZipCode {
[Required]
public String PrimaryCode { get; set; }
public String SubCode { get; set; }
}
Does there exist an enum validation attribute which validates a certain enum value?
Or should I do that manually:
if(viewModel.State == State.None) base.AddModelError("","wrong enum selected...");
How would you do that?
Enum:
public enum SomeEnum
{
Right = 1,
Wrong = 2,
Other = 3
}
ViewModel:
public class TestModel
{
[Range((int)(SomeEnum.Right), (int)(SomeEnum.Right), ErrorMessage = "Please select an Enum value")]
public SomeEnum Value { get; set; }
}
No other code needed. Enums are basically integers so you can use the Range attribute to validate them.
I would create my own validation attribute something like (untested):
public enum SomeEnum
{
Right,
Wrong
}
[AttributeUsage(AttributeTargets.Property)]
public sealed class EnumValueAttribute : ValidationAttribute
{
private const string DefaultErrorMessage = "Cannot be that value";
public SomeEnum EnumVal { get; private set; }
public EnumValueAttribute(SomeEnum enumVal)
: base(DefaultErrorMessage)
{
EnumVal = enumVal;
}
public override string FormatErrorMessage(string name)
{
return string.Format(ErrorMessageString, name, EnumVal);
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var enumVal = (SomeEnum) Enum.Parse(typeof (SomeEnum), value.ToString());
if (enumVal != EnumVal)
{
return new ValidationResult(DefaultErrorMessage);
}
return ValidationResult.Success;
}
}
Usage:
public class TestModel
{
[EnumValue(SomeEnum.Right)]
public SomeEnum Value { get; set; }
}
UPDATED
So this is as generic as you can reasonably take it, I've not tested this code, but it does compile. Notice that I've assigned number values to the enums.
public enum SomeEnum
{
Right = 1,
Wrong = 2,
Other = 3
}
[AttributeUsage(AttributeTargets.Property)]
public sealed class DisallowEnumValueAttribute : ValidationAttribute
{
public object DissallowedEnum { get; private set; }
public Type EnumType { get; private set; }
public DisallowEnumValueAttribute(Type enumType, object dissallowedEnum)
{
if (!enumType.IsEnum)
throw new ArgumentException("Type must be an enum", "enumType");
DissallowedEnum = dissallowedEnum;
EnumType = enumType;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var disallowed = Convert.ChangeType(DissallowedEnum, EnumType);
var enumVal = Convert.ChangeType(value, EnumType);
if (disallowed == null || enumVal == null)
throw new Exception("Something is wrong"); //or return validation result
if (enumVal == disallowed)
{
return new ValidationResult("This value is not allowed");
}
return ValidationResult.Success;
}
}
public class TestModel
{
[DisallowEnumValue(typeof(SomeEnum), SomeEnum.Wrong)]
public SomeEnum Thing { get; set; }
}
My enum validation was like this and works in dataannotation validation
public enum CreatedBySelfOrOthersEnumValues
{
Self,
Others
}
public class CampaignRegisterValidationModel
{
[Required]
public string Name { get; set; }
[Required]
public CreatedBySelfOrOthersEnumValues CreatedForSelfOrOthers { get; set; }
[Required]
public int CountryCode { get; set; }
public string Phone { get; set; }
public string Email { get; set; }
}
Then validating it
if (ModelState.IsValid)
{
}
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).
Currently I have the model FooModel
public class FooModel
{
[Range(0.001, 10000, ErrorMessage = "{0} must be a decimal/number between {1} and {2}.")]
[RangeValidator("BarMax")]
public decimal? Bar { get; set; }
public decimal BarMax { get; set; }
}
Following suggestions I created a custom range validator
public class RangeValidator : ValidationAttribute
{
private readonly string dependentPropery;
public RangeValidator(string dependentpropery)
: base()
{
this.dependentPropery = dependentpropery;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
System.Reflection.PropertyInfo property = validationContext.ObjectType.GetProperty(this.dependentPropery);
if (property == null)
{
return new ValidationResult(string.Format("Unknown Property {0}", this.dependentPropery));
}
var value1 = property.GetValue(validationContext.ObjectInstance, null) as decimal?;
if (!value1.HasValue)
{
return null;
}
decimal actualValue;
if (value == null)
{
return new ValidationResult("Value can not be empty.");
}
decimal.TryParse(value.ToString(), out actualValue);
if (actualValue <= 0)
{
return new ValidationResult(string.Format("{0} Value can not be empty.", this.dependentPropery));
}
return actualValue > value1.Value ? new ValidationResult(string.Format("Value cannot exceed {0} {1}.", this.dependentPropery, value1.Value)) : null;
}
}
You can't in the range attribute, but you could add the MVC Foolproof Validation library.
Then you can set your range to be the max that your storage or primitive type will hold, then use foolproof's lessthan to constrain it to the BarMax property.