5

I have a simple Controller with a POST method.
My model has a property of type enum.
When I send valid values ,everything works as expected

{  "MyProperty": "Option2"}

or

{  "MyProperty": 2}

If I send an invalid string

{  "MyProperty": "Option15"}

it correctly gets the default value (Option1) but if I send an invalid int ,it keep the invalid value

{  "MyProperty": 15}

enter image description here

Can I avoid that and get the default value or throw an error?

Thanks

public class ValuesController : ApiController
{
    [HttpPost]
    public void Post(MyModel value) {}
}

public class MyModel
{
    [JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
    public MyEnum MyProperty { get; set; }
}

public enum MyEnum
{
    Option1 = 0,
    Option2,
    Option3
}

Update
I know I can cast any int to an enum,that's not the problem.
@AakashM's suggestion solves half my problem,I didn't know about AllowIntegerValues

Now I correctly get an error when Posting an invalid int

{  "MyProperty": 15} 

The only problematic case now is when I post a string which is a number (which is strange because when I send an invalid non numeric string it correctly fails)

{  "MyProperty": "15"}
George Vovos
  • 7,563
  • 2
  • 22
  • 45
  • A quick look suggests no, not without unsetting `AllowIntegerValues` entirely. The standard behaviour of C# enums is unaltered by the converter - just as you could write `MyEnum hmm = (MyEnum)15;`, so the converter allows `15` here. – AakashM Feb 18 '16 at 11:18
  • 6
    Not related to MVC, see http://stackoverflow.com/questions/6413804/why-does-casting-int-to-invalid-enum-value-not-throw-exception – CodeCaster Feb 18 '16 at 11:18
  • @AakashM Thanks for the tip,it's half a solution.See my updated answer.If we don't find a better solution please post it as an asnwer and I'll accept it – George Vovos Feb 18 '16 at 12:21

2 Answers2

5

I solved my problem by extending StringEnumConverter and using @ AakashM's suggestion

public class OnlyStringEnumConverter : StringEnumConverter
{
    public OnlyStringEnumConverter()
    {
        AllowIntegerValues = false;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (!AllowIntegerValues && reader.TokenType == JsonToken.String)
        {
            string s = reader.Value.ToString().Trim();
            if (!String.IsNullOrEmpty(s))
            {
                if (char.IsDigit(s[0]) || s[0] == '-' || s[0] == '+')
                {
                    string message = String.Format(CultureInfo.InvariantCulture, "Value '{0}' is not allowed for enum '{1}'.", s, objectType.FullName);
                    string formattedMessage = FormatMessage(reader as IJsonLineInfo, reader.Path, message);
                    throw new JsonSerializationException(formattedMessage);
                }
            }
        }
        return base.ReadJson(reader, objectType, existingValue, serializer);
    }

    // Copy of internal method in NewtonSoft.Json.JsonPosition, to get the same formatting as a standard JsonSerializationException
    private static string FormatMessage(IJsonLineInfo lineInfo, string path, string message)
    {
        if (!message.EndsWith(Environment.NewLine, StringComparison.Ordinal))
        {
            message = message.Trim();
            if (!message.EndsWith("."))
            {
                message += ".";
            }
            message += " ";
        }
        message += String.Format(CultureInfo.InvariantCulture, "Path '{0}'", path);
        if (lineInfo != null && lineInfo.HasLineInfo())
        {
            message += String.Format(CultureInfo.InvariantCulture, ", line {0}, position {1}", lineInfo.LineNumber, lineInfo.LinePosition);
        }
        message += ".";
        return message;
    }

}
Joe
  • 122,218
  • 32
  • 205
  • 338
George Vovos
  • 7,563
  • 2
  • 22
  • 45
  • +1 - just what I needed. I've taken the liberty of making changes to support other integral types (e.g. long, ulong), and to throw a JsonSerializationException rather than ArgumentException with a message whose format is the same as those generated by standard converters. It's a pity the `JsonSerializationException.Create(reader, message)` is internal ... – Joe Feb 23 '17 at 12:39
  • @joe I created a pull request for this but JameNewton told me this is fixed now.I haven't confirmed that myself but I'll try to later. – George Vovos Feb 23 '17 at 12:57
1
MyEnum _myProperty;
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
public MyEnum MyProperty 
{ 
    get
    {
        return _myProperty;
    }
    set
    {
        if (Enum.IsDefined(typeof(MyEnum), value))
            _myProperty = value;
        else
            _myProperty = 0;
    }
}
TVOHM
  • 2,740
  • 1
  • 19
  • 29
  • Thanks @TVOHM but since I have many models with many properties I'm looking for a generic solution like @ AakashM's. This is a valid answer though ,+1 – George Vovos Feb 18 '16 at 12:26