12

I'd like to validate an input on a Web API REST command. I'd like it to work something like State below being decorated with an attribute that limits the valid values for the parameter.

public class Item {
    ...

    // I want State to only be one of "New", "Used", or "Unknown"
    [Required]
    [ValidValues({"New", "Used", "Unknown"})]
    public string State { get; set; }

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

    ...
}

Is there a way to do this without going against the grain of Web API. Ideally the approach would be similar to Ruby on Rails' custom validation.

Paul Oliver
  • 7,531
  • 5
  • 31
  • 34

4 Answers4

29

Create a custom validation attribute derived from ValidationAttribute and override the IsValid member function.

public class ValidValuesAttribute: ValidationAttribute
{
  string[] _args;

  public ValidValuesAttribute(params string[] args)
  {
    _args = args;
  }

  protected override ValidationResult IsValid(object value, ValidationContext validationContext)
  {
    if (_args.Contains((string)value))
      return ValidationResult.Success;
    return new ValidationResult("Invalid value.");
  }
}

Then you can do

[ValidValues("New", "Used", "Unknown")]

The above code has not been compiled or tested.

Matt Houser
  • 33,983
  • 6
  • 70
  • 88
  • 2
    Pretty good for not having compiled or testing. This worked with minimal tweaking: The constructor didn't like the `params` before the `args` parameter. Also, the attribute looks like this `[ValidValues(new[] {"New", "Used", "Unknown"})]` – Paul Oliver Jun 21 '13 at 21:21
  • 2
    I compiled the code above. I had to make the constructor public. Aside from that, it worked as expected. For me, the `params` worked. – Matt Houser Jun 22 '13 at 16:04
  • What if I don't want to pass it a hardcoded list? For example, a reference to an existing list. Any ideas for that which still use dependency injection versus something like a Singleton or ServiceLocator? I suppose that isn't possible because Attributes can impact compile time. – crush Jan 21 '16 at 17:13
  • Is this approach reflected in the Swagger documentation? - in a similar way that an enum would be? (here's your available options) – Rob Jun 22 '20 at 17:21
  • 1
    @RobMcCabe I don't know the answer to that, but if I was to guess, I'd say no, not without more work to make it so. – Matt Houser Jun 22 '20 at 18:01
1

How is the value passed to the API? Is it a query param or is it in the body? I'd generally just do a check at the request handler level. If you put accepted values in a List or array you can just used the Contains extension method.

  if (validStates.Contains(input))
  {
     return MethodThatProcessesRequest(requiredData);
  }
  else
  {
     return ErrorHandlingMethod(requiredData);
  }

This type of validation should be done servers side. Feel free to restrict the input on the UI but if you're making a REST API it should validate all input regardless of what your client is doing.

evanmcdonnal
  • 46,131
  • 16
  • 104
  • 115
1

Implement IValidatableObject for the Item which would require you to implement Validate method, then in the Validate write your condition to check whether it is valid, something like:

public IEnumerable<ValidationResult> Validate(ValidationContext context) {
    if (!States.contains(this.State)){
        yield return new ValidationResult("Invalid state.", new[] { "State" });
    }
}
Kazi Manzur Rashid
  • 1,544
  • 13
  • 14
1

you can also do this using a regular expression as below:

[Required]
[RegularExpression("New|Used|Unknown", ErrorMessage = "Invalid State")]
 public string State{ get; set; }

More details can by found here

Aris
  • 4,643
  • 1
  • 41
  • 38