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.
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:
In my MVC project, i want to validate an input by other input value. For instance according by following code, it doesn't return validation message if employee is a female and user doesn't choose any military service value but if employee is male, it returns validation message but i am getting this error during the runtime;
"An object reference is required for the non-static field, method, or property HRManager.Models.Employee.CheckMilitary()"
public class Employee
{
public string Firstname { get; set; }
public string Lastname { get; set; }
public int Gender { get; set; }
[RequiredByValue(IsRequired = CheckMilitary())]
public string MilitaryService { get; set; }
private bool CheckMilitary()
{
// If gender is female return false
return (this.Gender == 0)? false : true;
}
}
public class RequiredByValue : ValidationAttribute
{
public bool IsRequired { get; set; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
//todo
return base.IsValid(value, validationContext);
}
}
It looks like you are calling a non static property from a static method. You will need to either make the CheckMilitary static, or create an instance of Employee
seems like you are trying to determine if the gender and thus make the MilitaryService field required before you know the 'Employee`
give us an example on how and when you are calling: HRManager.Models.Employee.CheckMilitary() and I'll give you proper example on how to fix.
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).
MVC 4 I have the option to put two email addresses at list one is compulsory
and a dropdownlist preferred email work/Alternative
on submit I want to validate the selected value of the dropdownlistfor and by the selected value to make the Work email address or Alternative email address field required
How do I do that ? is it possible to do that with a custom validation?
Model:
public string WorkEmail { get; set; }
public string AlternativeEmail { get; set; }
public List<SelectListItem> PreferredEmails { get; set; }
Here is solution
public class WorkModel
{
[CorrectMail]
public string WorkEmail { get; set; }
[CorrectMail]
public string AlternativeEmail { get; set; }
public string PreferredEmail { get; set; }
}
public class CorrectMail : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var model = validationContext.ObjectInstance as WorkModel;
if (model == null) return new ValidationResult("Model is empty");
if (model.PreferredEmail == "WorkEmail" && string.IsNullOrEmpty(model.WorkEmail))
{
return new ValidationResult("You have selected work email but did not fill it");
}
if (model.PreferredEmail == "AlternativeEmail" && string.IsNullOrEmpty(model.AlternativeEmail))
{
return new ValidationResult("You have selected alternative email but did not fill it");
}
return ValidationResult.Success;
}
}
Yes, it is possible to do a custom validation routine. My preferred way of doing it is by Validating in the Service Layer
There is a complex view model that needs to be validated on the client. Simplified:
public class Survey
{
public List<Question> Questions { get; set; }
}
public class Question
{
public List<Answer> Answers { get; set; }
}
public class Answer
{
public string FieldName { get; set; }
[Required("Please use a number")]
public int Number { get; set; }
}
Currently, the question is being validated correctly on the client. But we need to validate and display a contextual message using FieldName like:
The field 'Number of children' is required.
I implemented a CustomRequiredAttribute class (: RequiredAttribute, IClientValidatable) and decorated the Number property with it:
[CustomRequired("{0} is mandatory")]
public int Number { get; set; }
but, in the GetClientValidationRules method, the metadata.Model is null.
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(
ModelMetadata metadata,
ControllerContext context)
{
// metadata.Model is null here!
ModelMetadata answerFieldNameValue = ModelMetadataProviders.Current
.GetMetadataForProperties(
metadata.Model,
metadata.ContainerType)
.FirstOrDefault(p => p.PropertyName == "FieldName");
// and, therefore, answerFieldNameValue.Model is null too!
}
If I go back to the first line of this method and from the context.Controller.ViewData.Model I get any Answer and assign it to metadata.Model, then the answerFieldNameValue.Model will contain the proper string which is the field name of the answer.
How to make this metadata.Model have a proper value when it gets here? Or any other way to solve this problem?
Got it. The code below is my solution which was inspired by BranTheMan who found his way out of a similar problem.
public class CustomModelMetadataProvider : DataAnnotationsModelMetadataProvider
{
private object lastQuestionLabel;
protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
{
ModelMetadata modelMetadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
object model = null;
if (modelAccessor != null)
{
model = modelAccessor();
}
if (typeof(SurveyQuestionVM).IsAssignableFrom(containerType) && propertyName == "TreeViewLabel")
{
lastQuestionLabel = model;
}
if (typeof(SurveyAnswerVM).IsAssignableFrom(containerType))
{
modelMetadata.AdditionalValues.Add("QuestionLabel", lastQuestionLabel);
}
return modelMetadata;
}
}