ASP.NET MVC: Custom Validation by DataAnnotation - c#

I have a Model with 4 properties which are of type string. I know you can validate the length of a single property by using the StringLength annotation. However I want to validate the length of the 4 properties combined.
What is the MVC way to do this with data annotation?
I'm asking this because I'm new to MVC and want to do it the correct way before making my own solution.

You could write a custom validation attribute:
public class CombinedMinLengthAttribute: ValidationAttribute
{
public CombinedMinLengthAttribute(int minLength, params string[] propertyNames)
{
this.PropertyNames = propertyNames;
this.MinLength = minLength;
}
public string[] PropertyNames { get; private set; }
public int MinLength { get; private set; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var properties = this.PropertyNames.Select(validationContext.ObjectType.GetProperty);
var values = properties.Select(p => p.GetValue(validationContext.ObjectInstance, null)).OfType<string>();
var totalLength = values.Sum(x => x.Length) + Convert.ToString(value).Length;
if (totalLength < this.MinLength)
{
return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
}
return null;
}
}
and then you might have a view model and decorate one of its properties with it:
public class MyViewModel
{
[CombinedMinLength(20, "Bar", "Baz", ErrorMessage = "The combined minimum length of the Foo, Bar and Baz properties should be longer than 20")]
public string Foo { get; set; }
public string Bar { get; set; }
public string Baz { get; set; }
}

Self validated model
Your model should implement an interface IValidatableObject. Put your validation code in Validate method:
public class MyModel : IValidatableObject
{
public string Title { get; set; }
public string Description { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Title == null)
yield return new ValidationResult("*", new [] { nameof(Title) });
if (Description == null)
yield return new ValidationResult("*", new [] { nameof(Description) });
}
}
Please notice: this is a server-side validation. It doesn't work on client-side. You validation will be performed only after form submission.

ExpressiveAnnotations gives you such a possibility:
[Required]
[AssertThat("Length(FieldA) + Length(FieldB) + Length(FieldC) + Length(FieldD) > 50")]
public string FieldA { get; set; }

To improve Darin's answer, it can be bit shorter:
public class UniqueFileName : ValidationAttribute
{
private readonly NewsService _newsService = new NewsService();
public override bool IsValid(object value)
{
if (value == null) { return false; }
var file = (HttpPostedFile) value;
return _newsService.IsFileNameUnique(file.FileName);
}
}
Model:
[UniqueFileName(ErrorMessage = "This file name is not unique.")]
Do note that an error message is required, otherwise the error will be empty.

Background:
Model validations are required for ensuring that the received data we receive is valid and correct so that we can do the further processing with this data. We can validate a model in an action method. The built-in validation attributes are Compare, Range, RegularExpression, Required, StringLength. However we may have scenarios wherein we required validation attributes other than the built-in ones.
Custom Validation Attributes
public class EmployeeModel
{
[Required]
[UniqueEmailAddress]
public string EmailAddress {get;set;}
public string FirstName {get;set;}
public string LastName {get;set;}
public int OrganizationId {get;set;}
}
To create a custom validation attribute, you will have to derive this class from ValidationAttribute.
public class UniqueEmailAddress : ValidationAttribute
{
private IEmployeeRepository _employeeRepository;
[Inject]
public IEmployeeRepository EmployeeRepository
{
get { return _employeeRepository; }
set
{
_employeeRepository = value;
}
}
protected override ValidationResult IsValid(object value,
ValidationContext validationContext)
{
var model = (EmployeeModel)validationContext.ObjectInstance;
if(model.Field1 == null){
return new ValidationResult("Field1 is null");
}
if(model.Field2 == null){
return new ValidationResult("Field2 is null");
}
if(model.Field3 == null){
return new ValidationResult("Field3 is null");
}
return ValidationResult.Success;
}
}
Hope this helps. Cheers !
References
Code Project - Custom Validation Attribute in ASP.NET MVC3
Haacked - ASP.NET MVC 2 Custom Validation

A bit late to answer, but for who is searching.
You can easily do this by using an extra property with the data annotation:
public string foo { get; set; }
public string bar { get; set; }
[MinLength(20, ErrorMessage = "too short")]
public string foobar
{
get
{
return foo + bar;
}
}
That's all that is too it really. If you really want to display in a specific place the validation error as well, you can add this in your view:
#Html.ValidationMessage("foobar", "your combined text is too short")
doing this in the view can come in handy if you want to do localization.
Hope this helps!

Related

IsValid method is not working in custom required validation attribute in MVC and i am not using ModelState.IsValid

I have a model like
public class Employee
{
[Required]
[RegularExpression]
public string Name { get; set; }
}
When I type spaces in the textbox then required field validation is not firing (though I keep the AllowEmptyString property of Required validation).
I have written a custom validation and planned to override IsValid() as below:
[AttributeUsage(AttributeTargets.Property)]
public class CustomRequiredValidatiorAttribute : ValidationAttribute, IClientValidatable
{
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metaData, ControllerContext context)
{
if (metaData == null)
{
throw new ArgumentNullException(nameof(metaData));
}
var rule = new ModelClientValidationRule
{
ErrorMessage = "Error msg",
};
rule.ValidationType = "required";
rule.ValidationParameters["propertynames"] = metaData.PropertyName;
yield return rule;
}
public override bool IsValid(object value)
{
// some logic
}
}
and model looks like
public class Employee
{
[CustomRequiredValidatiorAttribute]
[RegularExpression]
public string Name { get; set; }
}
In my code, this validation should fire without page refresh/postback
But IsValid() is not being called, could someone help me how to fix this issue?
Since I am not posting the page to the server IsValid() won't fire

How to use a constructor from a Action model?

I am learning Dependency Injection.
How can I make my "UserCreate" model to use the custom constructor I set when it is being used as a parameter on controller action? I want to pass the UserContext to my UserCreate model.
My action:
[HttpPost]
public JsonResult Post(UserCreate model)
{
var user = _repository.GetByUserName(model.Email);
if (user != null)
{
this.ModelState.AddModelError(nameof(model.Email), "Email already registered!");
}
else
{
if (ModelState.IsValid)
{
var userModel = _mapper.Map<User>(model);
_repository.Add(userModel);
_repository.SaveChanges();
return Json(new { success = "true" });
}
}
return Json(new { success = "false", errors = this.ModelErrors(this.ModelState) });
}
My Model
public class UserCreate : BaseModel
{
private readonly IUserRepo repo;
public UserCreate(UserContext context) : base(context){
repo = new UserRepository(context);
}
public UserCreate():base() { }
[Required]
[MaxLength(100)]
public string Email { get; set; }
[Required]
[MaxLength(30)]
public string Password { get; set; }
[Required]
[MaxLength(30)]
public string FirstName { get; set; }
[Required]
[MaxLength(30)]
public string MiddleName { get; set; }
[Required]
[MaxLength(30)]
public string LastName { get; set; }
[Required]
public int Age { get; set; }
[Required]
public DateTime Birthday { get; set; }
[Required]
[MaxLength(250)]
public string Adddress { get; set; }
public DateTime Created { get { return DateTime.Now; } }
}
I've set it on startup:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<UserContext>(opt => opt.UseSqlServer
(Configuration.GetConnectionString("Dev")));
services.AddControllers();
services.AddScoped<IUserRepo, UserRepository>();
services.AddScoped<ICardRepo, CardRepository>();
services.AddScoped<IUserContext, UserContext>();
services.AddScoped<TransactCreate, TransactCreate>();
services.AddSingleton<UserCreate>(x =>
new UserCreate(x.GetRequiredService<UserContext>()));
I have set the Addsingleton on startup however when i test my API, public UserCreate():base() { } constructor is called instead of the constructor with UserContext parameter. I am using netcore 3.1
the reason why I want do this is I to move my validations to model and i need to use UserContext from there.
Thanks!
I understand what you are asking, but please understand that your approach to this problem is very flawed. Your view model should absolutely know nothing about your repository.
In MVC, the Controller is responsible for handling HTTP requests (as well as model validation), and delegating actions to the rest of the application. The Model (UserCreate), should be a simple poco that only exists to transfer data from the client back to your controller. The controller should then delegate responsibility to the repository for handling the data.
Your controller should, instead, accept the repository via DI, and then send the UserCreate model through, after validating it. And your model, UserCreate, should 100% have a parameterless constructor, as the ModelBinder is going to build it up from the request.
however what I want to achieve is if I have multiple properties that i
need to validate from the database, i dont want to write them all in
my controller action. Can you recommend the right way to handle custom
validations?
According to your code and the previous discuss, I suppose you want to valid whether the entered value is exist in the database, if the value exist, display the error message, such as "Email already registered". If that is the case, it is better to use the [Remote] attribute:
Code as below:
[Remote(action: "VerifyEmail", controller: "Users")]
public string Email { get; set; }
and
[AcceptVerbs("GET", "POST")]
public IActionResult VerifyEmail(string email)
{
if (!_userService.VerifyEmail(email))
{
return Json($"Email {email} is already in use.");
}
return Json(true);
}
Besides, if you want to create custom validation, you can check this thread, then, in the Custom validation IsValid method, you could get the current dbcontext and check whether the entered data is valid or not. Code as below:
code in the model:
[Required(ErrorMessage ="Country is Required")]
public string Country { get; set; }
[RequiredIfHasState("Country", ErrorMessage ="State is Required")]
public string State { get; set; }
code in the custom valiation:
public class RequiredIfHasStateAttribute : ValidationAttribute
{
private readonly string _comparisonProperty;
public RequiredIfHasStateAttribute(string comparisonProperty)
{
_comparisonProperty = comparisonProperty;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
ErrorMessage = ErrorMessageString;
//get entered state value
var stateValue = (string)value;
var property = validationContext.ObjectType.GetProperty(_comparisonProperty);
if (property == null)
throw new ArgumentException("Property with this name not found");
//get the country value
var countryValue = (string)property.GetValue(validationContext.ObjectInstance);
//get the current dbcontext
var _context = (MvcMovieContext)validationContext.GetService(typeof(MvcMovieContext));
//query the database and check whether the country has state.
if (_context.Countries.Where(c => c.CountryCode == countryValue).Select(c => c).FirstOrDefault().HasState)
{
if(stateValue == null)
{
//if country has state and the state is null. return error message
return new ValidationResult(ErrorMessage);
}
else
{
//if country has state and the state is not found.
if(!_context.Countries.Where(c => c.CountryCode == countryValue).Any(c => c.States.Any(e => e.StateName == stateValue)))
{
return new ValidationResult("State not found");
}
}
}
return ValidationResult.Success;
}
}

Validation strategy

I'm trying to build a series of attribute classes to make it easier for our development team to validate objects. The objects are POCO classes like this.
public class User
{
public string Name { get; set; }
public string Company { get; set; }
}
I want to decorate this model with a custom attribute.
public class User
{
[MustHaveValue]
public string Name { get; set; }
public string Company { get; set; }
}
Then I would create my own class implementing ValidationAttribute, the base class in .NET Framework, which belongs to System.ComponentModel.DataAnnotations.
public class MustHaveValueAttribute : ValidationAttribute
{
.
.
public override IsValid(object value)
{
// validation logic.
}
}
And then I can validate the User model whenever I want by making the set of instances like ValidationContext, List<ValidationResult>.
But in an enterprise environment, problems just can't be solved by a specific class. My validation scenario requires more complex and more flexible ways. Imagine that one of the required validation scenarios would something like this.
public class User
{
public string Name { get; set; }
public string Company { get; set; }
// Check if an item exists in this list.
[MustHaveMoreThanOneItem]
public IList<Client> Clients { get; set; }
}
Then I would need to make another attribute class
public class MustHaveMoreThanOneItemAttribute : ValidationAttribute
{
.
.
public override IsValid(object value)
{
// Let's assume this value is List<Client> for now.
// I know the exact type, so I'm going to cast it to List<Client> without further considerations
List<Client> clients = value as List<Client>;
if(clients.Count > 0) {
return true;
} else {
return false;
}
}
}
But the problem is that there are a lot of other models that have a nested list items. Try to imagine the time when I want to reuse the MustHaveMoreThanOneItem in one of the other models like...
public class Department
{
public string Name { get; set; }
[MustHaveMoreThanOneItem]
public IList<Employee> { get; set; }
}
You already know that it's not going to work because it was strongly typed only for List<Client>. So I decided to use Generic there to solve this problem.
But to my disappointment, the _Attribute interface doesn't support Generic. There's no additional implementation like _Attribute<T> : Attribute and therefore, no ValidationAttribute<T> alas!! I just cannot use Generic here !!
public class Department
{
public string Name { get; set; }
// No way to use this syntax.
[MustHaveMoreThanOneItem<Employee>]
public IList<Employee> { get; set; }
}
So I made a conclusion that Attribute must have been designed for a fixed set of validations like email format, card format, null check, and etc IMAO.
But I still want to use an attribute and give a lot of flexibilities in it to prevent the duplicated, verbose validation codes like this.
if(model.Clients.Count > 0) ...
if(model.Name != null) ...
if(model.Clients.GroupBy(x => x.Country == Country.USA).Count >= 1) ...
if(model.Clients.Where(x => x.CompanyName == Company.Google).ToList().Count > 1 ) ...
.
.
.
I want to pose two questions here.
If Attirbute supports Generic, this problem will be solved?
Is there any way to implement Generic Attribute? in order to use
[MustHaveMoreThanOneItem<Employee>] annotation on a class member?
You can generically check any object that implements IEnumerable like this:
public class MustHaveMoreThanOneItemAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
// omitted null checking
var enumerable = value as IEnumerable;
var enumerator = enumerable.GetEnumerator();
if (!enumerator.MoveNext())
{
return false;
}
if (!enumerator.MoveNext())
{
return false;
}
return true;
}
}
C# by definition does not support generic type attributes, although this has been requested actively for a long time:
https://github.com/dotnet/roslyn/issues/953
https://github.com/dotnet/csharplang/issues/124
However, you can still inject a type into a validation attribute via constructor. You then can use reflection or whatever you need to define your custom validation criteria.
public class MustHaveMoreThanOneItemAttribute : ValidationAttribute
{
public Type EnumerableType { get; }
public MustHaveMoreThanOneItemAttribute(Type t)
=> this.EnumerableType = typeof(ICollection<>).MakeGenericType(t);
public override bool IsValid(object value)
{
var count = this.EnumerableType.GetProperty("Count").GetValue(value) as int?;
return (count ?? 0) > 1;
}
}
Now this allows you to use something similar to your goal:
public class Department
{
public string Name { get; set; }
[MustHaveMoreThanOneItem(typeof(Employee))]
public IList<Employee> { get; set; }
}

How to add additional validation to Model field?

I have a second thing:
<td>
#Html.LabelFor(m => m.Number, titleHtmlAttrs)
</td>
<td>
<span class="element-value2">
#Html.EditorFor(m => m.Number)
#Html.ValidationTooltipFor(m => m.Number)
</span>
</td>
And this is how this field looks like in model:
[Display(Name = "Special Number")]
[StringLength(20)]
public string Number { get; set; }
Which means that if I wanted to change this field, i can have any value from empty to 20.
It's ok, but now I need an additional validation.
In model I have some fields:
public DateTime? TimeOf { get; set; }
public bool HasType { get; set; }
New validation should work ONLY if TimeOf is not null and HasType is true. New validation should prevent empty values in Number. Basically, change (from empty to 20) to (from 1 to 20).
How could I correctly accomplish this?
P.S Sorry about my bad English.
For complex validation logic, look at implementing IValidatableObject in your ViewModel and then you can place your conditional validation logic inside the Validate method. (Caveat, this is obviously server side)
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (this.HasType)
{
// Do other conditional validation
if (validationFails)
{
yield return new ValidationResult("descriptive error goes here");
}
}
// Other validation here.
UPDATE It seems I have misunderstood the question. As the other answer has already pointed out, you could implement the IValidatableObject for achieving this. Something like:
public class YourModelName : IValidatableObject
{
[StringLength(20)]
public string Number{ get; set; }
[Required]
public DateTime? TimeOf { get; set; }
public bool HasType { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var results = new List<ValidationResult>();
if (TimeOf != null && HasType)
Validator.TryValidateProperty(this.Number,
new ValidationContext(this, null, null) { MemberName = "Number" },
results);
if (TimeOf == null)
results.Add(new ValidationResult("Date Time must have a value"));
if (!HasType)
results.Add(new ValidationResult("Must be true"));
return results;
}
}
OLD ANSWER:
You could write your custom validator for more complex validation conditions. Something like:
public class SomeCustomValidator : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
string number = value as string;
if (value == null) throw new InvalidOperationException("Can only be used on string properties");
if (!value.IsEmpty && value.Length <= 20)
{
return ValidationResult.Success;
}
return new ValidationResult("Name must be a non-empty string smaller than 20 chars"));
}
}
And for HasType, another custom one:
public class IsTrueAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
if (value == null) return false;
if (value.GetType() != typeof(bool)) throw new InvalidOperationException("can only be used on boolean properties.");
return (bool) value == true;
}
}
And on TimeOf you could use the required attribute to make sure it has a value:
[Required(ErrorMessage="Must have value")]
public DateTime? TimeOf {get;set;}
And use the custom attributes on the other two:
[SomeCustomValidator(ErrorMessage="Error msg...")]
public string Number {get;set;}
[IsTrueAttribute(ErrorMessage="Must be true")]
public bool HasType {get;set;}

How to access viewmodel's property value in custom validation attribute to alter messages?

The viewmodel has many string properties like Sample as below. My requirement is to show different validation messages depending on a bool flag in my viewmodel. That flag is IsProposer property as mentioned below:
[SampleAttribute(true, "bla prop", "foo add driver")]
public string Sample { get; set; }
public bool IsProposer { get; set; }
I thought to create a validation attribute so that I can just place it on all my string properties (required validation). And then depending on the value of that boolean flag, I will pass the msg accordingly. My custom validation attribute is as follows:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = false)]
public class SampleAttribute : RequiredAttribute
{
protected string ProposerErrorMessage { get; set; }
protected string AdditionalDriverErrorMessage { get; set; }
protected bool IsProposer { get; set; }
public SampleAttribute(bool isProposer, string propmsg, string adddrivermsg)
{
ProposerErrorMessage = propmsg;
IsProposer = isProposer;
AdditionalDriverErrorMessage = adddrivermsg;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (IsValid(value))
{
return ValidationResult.Success;
}
else
{
return new ValidationResult(IsProposer ? ProposerErrorMessage : AdditionalDriverErrorMessage);
}
}
}
Now the issue is, as you can see I am just passing true as first parameter for the attribute. Here, I need to pass the Isproposer property's value from the viewmodel instance so that I can then act accordingly. How can I access it?
I solved my problem by creating a attribute like this:
/// <summary>
/// This validation attribute is an extension to RequiredAttribute that can be used to choose either of the two
/// validation messages depending on a property in the context of same model.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = false)]
public class RequiredExtensionAttribute : RequiredAttribute
{
private string _errorMessageIfTruthy;
private string _errorMessageIfFalsy;
private string _dependentProperty;
public RequiredExtensionAttribute(string dependentproperty, string errorMessageIfTruthy, string errorMessageIfFalsy)
{
_errorMessageIfTruthy = errorMessageIfTruthy;
_dependentProperty = dependentproperty;
_errorMessageIfFalsy = errorMessageIfFalsy;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var propertyTestedInfo = validationContext.ObjectType.GetProperty(this._dependentProperty);
if (propertyTestedInfo == null)
{
return new ValidationResult(string.Format("unknown property {0}", this._dependentProperty));
}
var propertyTestedValue = propertyTestedInfo.GetValue(validationContext.ObjectInstance, null);
if (IsValid(value))
{
return ValidationResult.Success;
}
else
{
return new ValidationResult((bool)propertyTestedValue ? _errorMessageIfTruthy : _errorMessageIfFalsy);
}
}
}
This can now be used in models like:
[RequiredExtensionAttribute("IsProposerViewModel", "Please select your employment status.", "Please select this driver's employment status")]
public string EmploymentStatus { get; set; }
public bool IsProposerViewModel { get; set; }
-where the first parameter for attribute is the IsProposerViewModel, the dependent value.

Categories

Resources