28

In MVC when we post a model to an action we do the following in order to validate the model against the data annotation of that model:

if (ModelState.IsValid)

If we mark a property as [Required], the ModelState.IsValid will validate that property if contains a value or not.

My question: How can I manually build and run custom validator?

P.S. I am talking about backend validator only.

user2818430
  • 5,853
  • 21
  • 82
  • 148

2 Answers2

38

In .NET Core, you can simply create a class that inherits from ValidationAttribute. You can see the full details in the ASP.NET Core MVC Docs.

Here's the example taken straight from the docs:

public class ClassicMovieAttribute : ValidationAttribute
{
    private int _year;

    public ClassicMovieAttribute(int Year)
    {
        _year = Year;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        Movie movie = (Movie)validationContext.ObjectInstance;

        if (movie.Genre == Genre.Classic && movie.ReleaseDate.Year > _year)
        {
            return new ValidationResult(GetErrorMessage());
        }

        return ValidationResult.Success;
    }
}

I've adapted the example to exclude client-side validation, as requested in your question.

In order to use this new attribute (again, taken from the docs), you need to add it to the relevant field:

[ClassicMovie(1960)]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }

Here's another, simpler example for ensuring that a value is true:

public class EnforceTrueAttribute : ValidationAttribute
{
    public EnforceTrueAttribute()
        : base("The {0} field must be true.") { }

    public override bool IsValid(object value) =>
        value is bool valueAsBool && valueAsBool;
}

This is applied in the same way:

[EnforceTrue]
public bool ThisShouldBeTrue { get; set; }

Edit: Front-End Code as requested:

<div asp-validation-summary="All" class="text-danger"></div>

The options are All, ModelOnly or None.

Jeremy Thompson
  • 61,933
  • 36
  • 195
  • 321
Kirk Larkin
  • 84,915
  • 16
  • 214
  • 203
  • 1
    Could you add the front end code as well? It would help me and other readers I suspect. – w0051977 Apr 08 '19 at 18:24
  • The full example with the client-side implementation is available in the docs, [here](https://learn.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-2.2#custom-client-side-validation). – Kirk Larkin Apr 09 '19 at 09:14
  • I got a question about, because I hace done exactly like you, but, on the client side never validate my customs things – sgrysoft Jul 14 '19 at 13:17
  • @sgrysoft This answer doesn't cover client-side validation. I linked above to the docs that explains how to do that. If you're having problems, ask a new question and feel free to post a link to it here. – Kirk Larkin Jul 14 '19 at 21:29
12

To create a custom validation attribute in .Net Core, you need to inherit from IModelValidator and implement Validate method.

Custom validator

public class ValidUrlAttribute : Attribute, IModelValidator
{
    public string ErrorMessage { get; set; }

    public IEnumerable<ModelValidationResult> Validate(ModelValidationContext context)
    {
        var url = context.Model as string;
        if (url != null && Uri.IsWellFormedUriString(url, UriKind.Absolute))
        {
            return Enumerable.Empty<ModelValidationResult>();
        }

        return new List<ModelValidationResult>
        {
            new ModelValidationResult(context.ModelMetadata.PropertyName, ErrorMessage)
        };
    }
}

The model

public class Product
{
    public int ProductId { get; set; }

    [Required]
    public string ProductName { get; set; }

    [Required]
    [ValidUrl]
    public string ProductThumbnailUrl { get; set; }
}

Will this approach give opportunity to work with "ModelState.IsValid" property in controller action method?

Yes! The ModelState object will correctly reflect the errors.

Can this approach be applied to the model class? Or it can be used with model class properties only?

I don't know if that could be applied onto class level. I know you can get the information about the class from ModelValidationContext though:

  • context.Model: returns the property value that is to be validated
  • context.Container: returns the object that contains the property
  • context.ActionContext: provides context data and describes the action method that processes the request
  • context.ModelMetadata: describes the model class that is being validated in detail

Notes:

This validation attribute doesn't work with Client Validation, as requested in OP.

Jeremy Thompson
  • 61,933
  • 36
  • 195
  • 321
David Liang
  • 20,385
  • 6
  • 44
  • 70
  • Is it possible to assign values to property values during this validation? Is it a good idea? – petko Mar 11 '20 at 09:57
  • 2
    I've already deleted this sample code so I don't have access to a running code and prove you can assign values to properties. I don't think that's a good idea anyway because of single responsibility principle? – David Liang Mar 11 '20 at 18:40