1

I have a complex type with many properties, including one which is an enum. When PUTing data (application/x-www-form-urlencoded) to a web API method which has this type as a parameter, it appears to allow ANY string value to be passed for the value for the enum property. If the value passed is one of the members of the enum, it correctly assigns the value, but if an invalid value is passed, it simply assigns the first member of the enumeration.

Simple example to describe the problem - given the model classes:

public enum EdibleFarmAnimal {
   Sheep = 0,
   Cow = 1,
   Chicken = 2
}

public class ExampleModel {
    public EdibleFarmAnimal EatThis { get; set; }
    public string AnotherIrrelevantProperty { get; set; }
}

... and the web API method:

[HttpPut]
[ActionName("Put")]
public void Put(long id, ExampleModel model) {
    // Do something with the model
}

If I PUT EatThis=Cow&AnotherIrrelevantProperty=cheese to the relevant URL, it works as expected, and model.EatThis is equal to EdibleFarmAnimal.Cow, however if I PUT EatThis=Horse&AnotherIrrelevantProperty=cheese then model.EatThis is set to EdibleFarmAnimal.Sheep, whereas I would like (and expect) an error of some sort to be thrown, as the input is not valid for the type it is being deserialised into.

Mark Hughes
  • 7,264
  • 1
  • 33
  • 37

2 Answers2

2

Enum in C# has a default value of 0, so when it can't parse your input, it defaults to 0 which stands for Sheep in your enum (msdn article on this).

What you can do is create an Invalid enum element and assign it to 0:

public enum EdibleFarmAnimal {
   Invalid = 0,
   Sheep = 1,
   Cow = 2,
   Chicken = 3
}

And then check if your input was valid or not.

Vsevolod Goloviznin
  • 12,074
  • 1
  • 49
  • 50
  • Thanks that's a good point about why it's picking up that value - do you know if there is any way of forcing the deserialiser to reject input when it can't parse it though, rather than assigning the default value? (short of implementing my own complex type parser which seems massively over the top for such a basic validation requirement). – Mark Hughes Dec 16 '14 at 16:03
  • As this is a default behavior then you don't have much options: either change your enum, manually check in your action that the field contains appropriate value or implement your custom model binder – Vsevolod Goloviznin Dec 16 '14 at 16:04
  • Thank you for leading me in the right direction with this (I'd give you a plus-one but I don't have enough reputation yet!) - it turns out checking ModelState.IsValid tells you when this sort of thing has happened - see my answer - so allows you to handle it cleanly. – Mark Hughes Dec 16 '14 at 17:46
1

Thanks to Vsevolod's answer, I was able to understand why it was assigning the value it was, however the solution to detecting the scenario is to check the ModelState.IsValid property in the Web API method - this is set to false if an enumeration value or date time couldn't be parsed, or any specific validation attributes you've added to your model do not pass for the given input. As described in this article:

Web API does not automatically return an error to the client when validation fails. It is up to the controller action to check the model state and respond appropriately.

Therefore, to correct the original example so it now throws an appropriate error:

[HttpPut]
[ActionName("Put")]
public void Put(long id, ExampleModel model) {
    if (!ModelState.IsValid) throw new HttpResponseException(HttpStatusCode.BadRequest);
    // Do something with the model
}

This answer describes a nice way of applying such validation through the use of a custom attribute, as an alternative to checking on every API call.

Community
  • 1
  • 1
Mark Hughes
  • 7,264
  • 1
  • 33
  • 37