Enum as Required Field in ASP.NET Core WebAPI

為{幸葍}努か 提交于 2019-12-10 17:33:42

问题


Is it possible to return the [Required] attribute error message when a JSON request doesn't provide a proper value for an enum property?

For example, I have a model for a POST message that contains an AddressType property that is an enumeration type:

public class AddressPostViewModel
{
    [JsonProperty("addressType")]
    [Required(ErrorMessage = "Address type is required.")]
    public AddressType AddressType { get; set; }
}

The AddressType enum accepts two values:

[JsonConverter(typeof(StringEnumConverter))]
public enum AddressType
{
    [EnumMember(Value = "Dropship")]
    Dropship,
    [EnumMember(Value = "Shipping")]
    Shipping
}

I've noticed (or actually my QA team noticed) that if the request message JSON contains either an empty string or null for the AddressType, the error message isn't the expected Address type is required. message. Instead, the error message is a somewhat unfriendly parsing error.

For example, if the request JSON looks like this:

{  "addressType": "" }

Then the error that is auto-generated by the validation framework looks like this:

{
    "message": "Validation Failed",
    "errors": [
        {
            "property": "addressType",
            "message": "Error converting value \"\" to type 'MyNamespace.AddressType'. Path 'addressType', line 4, position 19."
        }
    ]
}

Is there a way to ensure that error message of the [Required] attribute is returned if someone doesn't include a valid value for an enum?


回答1:


Option 1: To use Custom RequiredEnum attribute and avoid JsonConverter attribute

Do not put JsonConverter on the AddressType enum. This StringToEnum is failing to map the string.Empty to enum value and it is throwing this error message.

Instead of that, you can write a custom required enum validator as shown below.

    using System;
    using System.ComponentModel.DataAnnotations;

    public class RequiredEnumFieldAttribute: RequiredAttribute
    {
        public override bool IsValid(object value)
        {
            if (value == null)
            {
                 return false;
            }

            var type = value.GetType();
            return type.IsEnum && Enum.IsDefined(type, value);
        }
   }

Then you can use it like shown below:

public class AddressPostViewModel
{
    [JsonProperty("addressType")]
    [RequiredEnumField(ErrorMessage = "Address type is required.")]
    public AddressType AddressType { get; set; }
}

Option 2: Use custom JsonConverter for AddressType

Add one custom CustomStringToEnumConverter which is derived from StringEnumConverter.

This method would throw an error if the value in JSON is empty.

public class CustomStringToEnumConverter : StringEnumConverter
{
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (string.IsNullOrEmpty(reader.Value.ToString()))
            throw new Exception("Address not provided");

        return base.ReadJson(reader, objectType, existingValue, serializer);
    }
}

Use this jsonConverter instead of default StringEnumConverter as shown below

[JsonConverter(typeof(CustomStringToEnumConverter))]
public enum AddressType
{
}



回答2:


I don't think there is an out of the box data annotation validating enum values.

You can derive from the required attribute though:

using System;
using System.ComponentModel.DataAnnotations;

    public class RequiredEnumAttribute : RequiredAttribute
    {
        public override bool IsValid(object value)
        {
            if (value == null) return false;
            var type = value.GetType();
            return type.IsEnum && Enum.IsDefined(type, value);
        }
}

The Enum.IsDefined method is checking if a given value exists in the enum of given type.

Usage:

[RequiredEnum(ErrorMessage = "Your error message.")]
public YourEnum EnumProperty { get; set; }

See this article.




回答3:


I've come-up with a solution that meets my requirements, although the code makes me cringe a little.

I kept the [Required] attribute on the AddressType property in the view model. The cringe-worthy part is that I had to make the property nullable:

public class AddressPostViewModel
{
    [JsonProperty("addressType")]
    [Required(ErrorMessage = "Address type is required.")]
    public AddressType? AddressType { get; set; }
}

On the AttributeType enum itself, I replaced the StringEnumConverter attribute with a custom JsonConverter as suggested by @Manoj Choudhari:

[JsonConverter(typeof(CustomStringToEnumConverter))]
public enum AddressType
{
    [EnumMember(Value = "Dropship")]
    Dropship,
    [EnumMember(Value = "Shipping")]
    Shipping
}

This is the CustomStringToEnumConverter:

public class CustomStringToEnumConverter : StringEnumConverter
{
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (string.IsNullOrEmpty(reader.Value?.ToString()))
        {
            return null;
        }

        object parsedEnumValue;

        var isValidEnumValue = Enum.TryParse(objectType.GenericTypeArguments[0], reader.Value.ToString(), true, out parsedEnumValue);

        if (isValidEnumValue)
        {
            return parsedEnumValue;
        }
        else
        {
            return null;
        }
    }
}

The CustomerStringToEnumConverter can handle empty strings, nulls, and invalid strings. If it encounters an invalid enum value, it returns null which is then caught when the required field validation (magic) occurs and the RequiredAttribute error message is displayed in the JSON response.

While I don't like making the AttributeType type nullable, the consumer of my API will see a consistent validation message if the AttributeType value is wrong in the request JSON.



来源:https://stackoverflow.com/questions/54202864/enum-as-required-field-in-asp-net-core-webapi

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!