简体   繁体   中英

ValidationAttribute for custom validation not working to check null or empty (server-side)

I found that the IsValid() method of a custom ValidationAttribute is never called when the attrib is not part of the calling query but is part of the method signature.

So, I wonder how can RequiredAttribute work for certain parameters of a QueryString. I have peek at the RequiredAttribute source code and it would have the same problem that I will describe,

For example, with the following action method signature:

    public ActionResult<IEnumerable<SpaceBody>> Get(
        [FromQuery]
        [SomeAttribute]string some,
        int minDistance, int maxDistance)

The IsValid method will be called with queries as:

  • Api/SpaceBody?some=xxx

But will not be called with:

  • Api/SpaceBody
  • Api/SpaceBody?some=

Until here it has kind of sense: if a parameter is not in the query, how the framework would know which filter to apply? Well, it should know by the action method signature

If I change the signature to:

    public ActionResult<IEnumerable<SpaceBody>> Get(
        [FromQuery]
        [Required, Some]string some,
        int minDistance, int maxDistance)

The IsValid method of the custom attrib will fire also for:

  • Api/SpaceBody
  • Api/SpaceBody?some=

So, what does RequiredAttribute does to make the IsValid() method of another attribute to be called?

If I peek into the github code, it has nothing that I can notice: https://github.com/microsoft/referencesource/blob/master/System.ComponentModel.DataAnnotations/DataAnnotations/RequiredAttribute.cs

On my custom attrib I do the following:

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
public class SomeAttribute : ValidationAttribute
{
    private const string SOME_REGEX = @"(?i)^\b(xx)\b$";

    private const string SOME_NOT_VALID_ERROR_MESSAGE = "Some is not valid.";
    private const string SOME_REQUIRED_ERROR_MESSAGE = "Some must have a value.";

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var some = Convert.ToString(value);

        if (string.IsNullOrWhiteSpace(some))
            return new ValidationResult(SOME_REQUIRED_ERROR_MESSAGE);

        if (!Regex.Match(some, SOME_REGEX, RegexOptions.IgnoreCase).Success)
            return new ValidationResult(SOME_NOT_VALID_ERROR_MESSAGE);

        return ValidationResult.Success;
    }
}

It contains logic to reject empty values (as RequiredAttribute does), so if I use [Required, Some] it will show twice any null or empty parameter call, which is undessired.

If I keep only [Some], the null or empty errors will be skipped, which is the worst thing that could happen.

Only solution that I see right now is to remove the null or empty logic from the custom attrib and use [Required, Some]. Ok, it could be a solution, but because with wrong motivation: "because I can't make a custom attribute to be fired in case of null or empty values". Is it by design? And in such case, what's the magic behind RequiredAttribute to work and make other attribs to be fired?

To complete the context, this is my Startup.ConfigureServices:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvcCore(o =>
        {
            //o.Filters.Add<ValidateModelAttribute>();
            o.EnableEndpointRouting = false;
        })
        .AddApiExplorer()
        .SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
        .AddJsonFormatters()
        .AddDataAnnotations()
        .AddControllersAsServices();
    }

Without the SetCompatibilityVersion, it wouldn't work at all, but our production code already has it anyway.

Note that I also have a ValidateModelAttribute that just does: By uncommenting it, I don't get any different behavior.

public class ValidateModelAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        if (!context.ModelState.IsValid)
        {
            context.Result = new BadRequestObjectResult(context.ModelState);
        }
    }
}

Sorry for long details, at the end I only want to know how can I use only [Some] to perform all "some" related validation?

Inherit from RequiredAttribute instead because that inherits from ValidationAttribute

public class SomeAttribute :RequiredAttribute
{
    private const string SOME_REGEX = @"(?i)^\b(xx)\b$";

    private const string SOME_NOT_VALID_ERROR_MESSAGE = "Some is not valid.";
    private const string SOME_REQUIRED_ERROR_MESSAGE = "Some must have a value.";

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var some = Convert.ToString(value);

        if (string.IsNullOrWhiteSpace(some))
            return new ValidationResult(SOME_REQUIRED_ERROR_MESSAGE);

        if (!Regex.Match(some, SOME_REGEX, RegexOptions.IgnoreCase).Success)
            return new ValidationResult(SOME_NOT_VALID_ERROR_MESSAGE);

        return ValidationResult.Success;
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM