ASP.NET MVC: Custom Validation by DataAnnotation

170,530

Solution 1

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; }
}

Solution 2

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.

Solution 3

ExpressiveAnnotations gives you such a possibility:

[Required]
[AssertThat("Length(FieldA) + Length(FieldB) + Length(FieldC) + Length(FieldD) > 50")]
public string FieldA { get; set; }

Solution 4

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.

Solution 5

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

Share:
170,530

Related videos on Youtube

Danny van der Kraan
Author by

Danny van der Kraan

Blog: https://dannyvanderkraan.wordpress.com/ LinkedIn: https://nl.linkedin.com/in/dannyvanderkraan Tags: C#.NET, ASP.NET Core 1.0, Azure, NServiceBus, WPF MVVM, LINQ, Agile Scrum, TDD, DDD, UML ~ Let's help eachother become better developers.

Updated on April 26, 2020

Comments

  • Danny van der Kraan
    Danny van der Kraan about 4 years

    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.

    • levelnis
      levelnis about 11 years
      Have you looked at Fluent Validation? It handles complex scenarios much better than Data Annotations
    • Niks
      Niks about 11 years
      Take a look at highly recommended solutions.... dotnetcurry.com/ShowArticle.aspx?ID=776
    • Danny van der Kraan
      Danny van der Kraan about 11 years
      Thanks for answering. I'll check out Fluent Validation, never heard of it. And Niks, Darin basically wrote out what the article at the link you posted explained. So, thank you... Awesome stuff!
  • Danny van der Kraan
    Danny van der Kraan about 11 years
    Thanks for answering, I accepted your answer. Feel a bit embarrassed actually. You wrote out the entire solution! Hehe. Only had to change the IsValid function to check for max length. So is this the accepted MVC solution for these types of problems?
  • Danny van der Kraan
    Danny van der Kraan about 11 years
    Thanks for answering Andrei. While your solution would work too I choose Darin's because it's more reusable.
  • Darin Dimitrov
    Darin Dimitrov about 11 years
    @DannyvanderKraan, yes, this is the accepted way. Of course this sucks so badly that I never use it and use FluentValidation.NET instead to perform validation.
  • Danny van der Kraan
    Danny van der Kraan about 11 years
    Lol. Okok, I don't know what that is. Why is it better?
  • Darin Dimitrov
    Darin Dimitrov about 11 years
    Here: fluentvalidation.codeplex.com. You could have just written a simple validator for the view model that might have looked like this (a single line of code): this.RuleFor(x => x.Foo).Must((x, foo) => x.Foo.Length + x.Bar.Length + x.Baz.Length < 20).WithMessage("The combined minimum length of the Foo, Bar and Baz properties should be longer than 20");. Now look at the code in my answer that you need to write with the data annotations and tell me which one you prefer. The declarative validation model is very poor compared to an imperative model.
  • Danny van der Kraan
    Danny van der Kraan about 11 years
    Agreed. Thank you. I'll look into Fluent Validation.
  • Data
    Data over 10 years
    yield return new ValidationResult("The title is mandatory.", "Title"); would add the property name, useful in grouping validation errors for display if necessary.
  • Jose
    Jose over 10 years
    This is a bit late, but does anyone know if there is a different setting that you have to "turn on" in order to allow custom data annotations? I know about adding a namespace for unobstrusive js on the web.config file, but anywhere else?
  • Grimm The Opiner
    Grimm The Opiner over 10 years
    I've been looking for this all morning! I've mplemented it, and unfortunately when IsValid is called the validationContext is null. Any idea what I did wrong? :-(
  • Pedro
    Pedro almost 10 years
    Note that this validation method is only called after all validation attributes have passed validation.
  • IDIR Samir
    IDIR Samir over 8 years
    This is brilliant! my prayers got answered :)
  • Mou
    Mou over 8 years
    does this kind of validation work at client side CombinedMinLength() required js will be injected for client side validation ?
  • Darin Dimitrov
    Darin Dimitrov over 8 years
    @Mou, for this to work you will need to write a corresponding client side extension to the standard validators because this is custom server side logic.
  • Steve S
    Steve S almost 8 years
    This worked well for me since my validation was very specific. Adding a custom attribute would have been overkill for me since the validation was not going to be re-used.
  • Muhammad Ashikuzzaman
    Muhammad Ashikuzzaman over 6 years
    @DarinDimitrov thanks for this answer, But now if I hit a controller actoin having MyViewModel model; with ajax am seeing it is working in server side. But if I want to show the error message in client side when click on submit button what I have to do? please inform me.
  • Amol Jadhav
    Amol Jadhav almost 5 years
    This is what I am looking for. Thank you!
  • Brad
    Brad about 4 years
    Just found this answer and it's just saved loads of time. ExpressiveAnnotations are brilliant!