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.
Related
I am developing Web Applications using ASP.Net MVC 5 (.net framework), now trying develop my next Project using .NET Core 3.1 with Entity framework Core (with Code first approach).
I am trying separate business logic in a separate Wrapper class like:
public interface IValidationDictionary
{
void AddError(string key, string errorMessage);
bool IsValid { get; }
}
public class ModelStateWrapper : IValidationDictionary
{
private ModelStateDictionary _modelState;
public ModelStateWrapper(ModelStateDictionary modelState)
{
_modelState = modelState;
}
#region IValidationDictionary Members
public void AddError(string key, string errorMessage)
{
_modelState.AddModelError(key, errorMessage);
}
public bool IsValid
{
get { return _modelState.IsValid; }
}
#endregion
}
In the EmployeeRepo class:
private Models.IValidationDictionary _modelState;
public EmployeeRepo(Models.IValidationDictionary modelState)
{
_modelState = modelState;
}
public int Create(Models.Employee ObjToCreate)
{
if (!Validate(ObjToCreate))
return 0;
_context.Employee.Add(ObjToCreate);
_context.SaveChanges();
return ObjToCreate.Id;
}
protected bool Validate(Models.Employee objToValidate)
{
if (objToValidate.Name.Trim().Length == 0)
_modelState.AddError("Name", "Name is required.");
if (null == objToValidate.DOB)
_modelState.AddError("DOB", "DOB is required");
return _modelState.IsValid;
}
In the Controller:
private Repository.IEmployeeRepo repo;
public EmployeesController(ApplicationDbContext context)
{
_context = context;
repo = new Repository.EmployeeRepo(new Models.ModelStateWrapper(this.ModelState));
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Create([Bind("Name,DOB,Department")] Employee employee)
{
var respId = repo.Create(employee);
if (0 != respId.Id)
{
return RedirectToAction(nameof(Details), new { id = respId.Id });
}
return View(employee);
}
I am expecting ModelState errors to be update in the controller which is added by the wrapper class, but model validation error not updating in the Controller.
Thanks for your time and for any response.
With Best Regards,
Raju Mukherjee
You just want to use a custom verification method to verify these two
fields, right?
The method you wrote is too complicated.
To bind the corresponding error message, you need to add your custom verification attribute above the corresponding fields like [Required].
In fact, you only need to inherit the ValidationAttribute method and rewrite the IsValid method.
public class Employee
{
public int Id { get; set; }
[CustomValidate]
public string Name { get; set; }
[CustomValidate]
public string DOB { get; set; }
public string Department{ get; set; }
}
Custom Method:
public class CustomValidateAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value == null || string.IsNullOrEmpty(value.ToString().Trim()))
{
return new ValidationResult(validationContext.DisplayName + " is required.");
}
return ValidationResult.Success;
}
}
Create:
[HttpPost]
public IActionResult Create([Bind("Name,DOB,Department")]Employee employee)
{
if (ModelState.IsValid)
{
_context.Employee.Add(employee);
_context.SaveChanges();
return RedirectToAction(nameof(Details), new { id = employee.Id });
}
return View(employee);
}
Here is the test result:
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;
}
}
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).
I am comparing a username using a custom validation. I am checking if it's the same as the old value or if it passes the regex it is acceptable but if it is not then throw an error. How do i get the UserID from the viewmodel if possible?
[EmailValidation]
public string Username{ get; set; }
public int UserID { get; set; }
public class EmailValidationAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
User user= User.getUserByID(UserID); //How to get UserID?
string username= value.ToString();
if (user.Username== username || IsValid(username))
{
return ValidationResult.Success;
}
else
{
return new ValidationResult("Error");
}
}
You can get UserID from ObjectInstance of ValidationContext. In your case it will be instance of your User class.
public class EmailValidationAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
UserDto dto = (UserDto)validationContext.ObjectInstance;
User user= User.getUserByID(dto.UserID);
string username = value.ToString();
if (user.Username == username || IsValid(username))
{
return ValidationResult.Success;
}
else
{
return new ValidationResult("Error");
}
}
}
If you want something more universal you can add property that specify property name and use reflection to get the value.
The easiest way is to move your validation to the class instead of being a field validator.
For reference: http://jeffhandley.com/archive/2009/10/16/validator.aspx
class MyClass : IValidatableObject {
public string EmailAddress { get; set; }
public int UserID { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
User user= User.getUserByID(UserID); // UserID is available because this is a method on the object
string username= value.ToString();
if (user.Username== username || IsValid(username)) {
// it's fine?
} else {
yield return new ValidationResult("Error");
}
}
}
I would recommend a 2 step process here so that you may use this attribute in the future with any new classes that come up:
1) Create an abstract class that contains the UserID in it, and have any classes that have this EmailValidationAttribute extend that class.
2) In your validation you can cast value to the abstract class type, and just grab the UserID from there.