How to Create a custom model validation attribute in ASP.NET Core

By FoxLearn 3/7/2025 4:29:10 AM   97
ASP.NET Core offers many built-in model validation attributes, such as [Required] and [StringLength], that can handle a majority of validation scenarios.

However, sometimes built-in attributes aren’t enough, and you might need to create your own custom validation logic. In this example, I'll demonstrate how to create a custom model validation attribute that ensures a password has a minimum length.

Subclass ValidationAttribute and Implement Validation Logic

To create a custom validation attribute, you need to subclass ValidationAttribute, override the IsValid() method, and write your validation logic. The IsValid() method takes an object parameter, so you need to check its type, and then apply the specific validation. In this example, we'll check if the password's length is above a certain threshold.

using System.ComponentModel.DataAnnotations;

public class MinPasswordLengthAttribute : ValidationAttribute
{
    private readonly int _minLength;

    public MinPasswordLengthAttribute(int minLength)
    {
        _minLength = minLength;
    }

    public override bool IsValid(object value)
    {
        if (value is string password && password.Length >= _minLength)
        {
            return true;
        }

        return false;
    }
}

In this example:

  • We define a MinPasswordLengthAttribute that accepts a minimum password length through the constructor.
  • Inside the IsValid() method, we check if the value is a string and if its length is greater than or equal to the specified minimum length. If both conditions are true, the validation passes.

When a request is received with a model containing this attribute, the framework will automatically call the IsValid() method to validate the property value. If validation fails, it will return a 400 Bad Request with an error response.

Customize the Error Message (Optional)

By default, the error message generated will be generic, like "The field <property name> is invalid." You may want to make the error message more descriptive. You can override the FormatErrorMessage() method to provide a custom message that includes the property name.

using System.ComponentModel.DataAnnotations;

public class MinPasswordLengthAttribute : ValidationAttribute
{
    private readonly int _minLength;

    public MinPasswordLengthAttribute(int minLength)
    {
        _minLength = minLength;
    }

    public override bool IsValid(object value)
    {
        if (value is string password && password.Length >= _minLength)
        {
            return true;
        }

        return false;
    }

    public override string FormatErrorMessage(string name)
    {
        return $"{name} must be at least {_minLength} characters long.";
    }
}

In this example:

  • We override the FormatErrorMessage() method to return a more specific error message.
  • Now, instead of the generic message, the response will say something like: "Password must be at least 8 characters long."

Using the Attribute

Now, you can apply this custom attribute to a model property.

For example, How to use the MinPasswordLength attribute on a password field:

public class UserRegistration
{
    public string Username { get; set; }

    [MinPasswordLength(8)]
    public string Password { get; set; }
}

The Password property now uses the MinPasswordLength attribute, which enforces that the password must be at least 8 characters long.

Test the Validation

Let's test this custom validation by sending a request with a short password using Postman.

Request:

POST /users/register

{
    "username": "john_doe",
    "password": "1234"
}

Response:

{
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
    "title": "One or more validation errors occurred.",
    "status": 400,
    "traceId": "0HMHK72C15DT8:0000000B",
    "errors": {
        "Password": [
            "Password must be at least 8 characters long."
        ]
    }
}

In this case, the password is too short (less than 8 characters), so the validation error is triggered, and the response returns a 400 status with a clear error message.

Now, let’s try with a valid password:

Request:

POST /users/register

{
    "username": "john_doe",
    "password": "StrongPassword123"
}

Response:

{
    "status": 200,
    "message": "Registration successful"
}

Since the password is now long enough, the validation passes, and the user can proceed with registration.