【问题标题】:Enum as Required Field in ASP.NET Core WebAPI枚举作为 ASP.NET Core WebAPI 中的必填字段
【发布时间】:2019-01-15 16:21:43
【问题描述】:

当 JSON 请求没有为枚举属性提供正确的值时,是否可以返回 [Required] 属性错误消息?

例如,我有一个 POST 消息模型,其中包含一个枚举类型的 AddressType 属性:

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

AddressType 枚举接受两个值:

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

我注意到(或者实际上我的 QA 团队注意到)如果请求消息 JSON 包含空字符串或 AddressType 的 null,则错误消息不是预期的 Address type is required. 消息。相反,错误消息是一个有点不友好的解析错误。

例如,如果请求 JSON 如下所示:

{  "addressType": "" }

那么验证框架自动生成的错误是这样的:

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

如果有人没有包含有效的枚举值,是否有办法确保返回 [Required] 属性的错误消息?

【问题讨论】:

  • 我想说验证工作正常。枚举是基本数字类型之上的一组名称。它们不可为空,因此除非设置,否则它们的值是其基本类型的默认值(即 0)。空字符串不是有效的枚举名称。 null 也无效,正是因为枚举不可为空。
  • 如果你想“接受”缺失值,类型必须可以为空,即AddressType?
  • @PanagiotisKanavos 我同意你的观点,验证工作正常,这不是我想要的。我不希望接受 null 或空字符串,我只想在客户端发送其中任何一个值时呈现用户友好的消息。
  • 您也可以使用 FluentValidation 之类的验证包来处理这种特定情况或所有验证。

标签: c# enums asp.net-core-webapi


【解决方案1】:

选项1:使用自定义RequiredEnum属性并避免JsonConverter属性

不要将 JsonConverter 放在 AddressType 枚举上。此 StringToEnum 无法将 string.Empty 映射到枚举值,并抛出此错误消息。

除此之外,您可以编写一个自定义的必需枚举验证器,如下所示。

    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);
        }
   }

然后你就可以像下面这样使用它了:

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

选项 2:为 AddressType 使用自定义 JsonConverter

添加一个从 StringEnumConverter 派生的自定义 CustomStringToEnumConverter。

如果 JSON 中的值为空,此方法会抛出错误。

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);
    }
}

如下所示,使用这个 jsonConverter 代替默认的 StringEnumConverter

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

【讨论】:

  • 只有当我在 JSON 请求中完全不包含 AddressType 属性时,才会点击自定义所需的枚举属性代码。如果我将 AddressType 设置为空字符串或 null,我会得到同样不希望的“错误转换值”错误。
  • +1,但是如果在ReadJson方法中抛出异常,required属性中的消息仍然不会显示。 OP应该看看stackoverflow.com/questions/20104575/…
  • @MikołajMularczyk - 从抛出新异常中,我们可以抛出想要在 UI 上显示的消息。另一种选择是如您所说,我们可以将 NULL 或 EMPTY 字符串映射到某个 Enum 值,然后使用自定义属性抛出错误消息。但为此,我们必须向 Enum 添加一个新值,该值将映射到 undefined。
  • 你可以在异常中包含任何你想要的消息,这是真的,但除非 OP 有一些中间件来捕获和处理异常,否则消费者将得到内部服务器错误 (500)。跨度>
  • 不幸的是,即使从 AddressType 中删除 StringEnumConverter 属性,选项 1 也不起作用。我仍然收到“错误转换值”异常。选项 2 看起来更有希望,因为自定义转换器代码在“错误转换值”错误之前被触发。
【解决方案2】:

我想出了一个满足我要求的解决方案,尽管代码让我有点畏缩。

我在视图模型的AddressType 属性上保留了[Required] 属性。令人畏惧的部分是我必须将属性设为nullable

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

AttributeType 枚举本身上,我用@Manoj Choudhari 建议的自定义JsonConverter 替换了StringEnumConverter 属性:

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

这是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;
        }
    }
}

CustomStringToEnumConverter 可以处理空字符串、空值和无效字符串。如果遇到无效的枚举值,则返回null,然后在发生必填字段验证(魔术)并在 JSON 响应中显示RequiredAttribute 错误消息时捕获该值。

虽然我不喜欢将 AttributeType 类型设为可为空,但如果请求 JSON 中的 AttributeType 值错误,我的 API 使用者将看到一致的验证消息。

【讨论】:

  • 谢谢!我花了几个小时来分析我的问题,提出一个问题并找到你的答案;-)
  • 请通过将第一个枚举值设置为“0”以外的任何值来重置枚举值。这将强制系统默认值 '0' 不是有效的枚举
  • 这一行出现错误 var isValidEnumValue = Enum.TryParse(objectType.GenericTypeArguments[0], reader.Value.ToString(), true, out parsedEnumValue); TryParse 不需要 4 个参数
【解决方案3】:

我认为没有现成的数据注释来验证枚举值。

您可以从所需的属性派生:

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);
        }
}

Enum.IsDefined 方法检查给定value 是否存在于给定type 的枚举中。

用法:

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

参见this 文章。

【讨论】:

  • 只有当我在 JSON 请求中完全不包含 AddressType 属性时,才会点击自定义所需的枚举属性代码。如果我将 AddressType 设置为空字符串或 null,我会得到同样不希望的“错误转换值”错误。
  • 那么你的问题在于 JsonConverter,它无法解析空字符串。正如@Manoj Chudari 指出的那样,您必须实现一个自定义的。你应该看看这个线程stackoverflow.com/questions/20104575/…
【解决方案4】:

(在此处查看完整的文章:https://mtarleton.medium.com/enum-as-required-field-in-asp-net-core-webapi-a79b697ef270

这里面临的问题是将正文转换为 JSON 和验证模型验证的操作顺序。

首先,.NET 将正文反序列化为 JSON具有默认值。这意味着如果您为不可为空的 ENUM 传入一个空值,则会为其设置默认值(通常是 ENUM 中的第一项)。基元也会发生同样的事情。

[JsonProperty(Required = Required.Always)]

【讨论】:

  • 我尝试了您文章中的建议,但它不起作用。如果我从 JSON 中省略该属性,则不会出现错误。如果我输入了一个无效的枚举值,我会从 JSON 反序列化程序中得到一个错误,而不是来自 ValidEnumAttribute 的错误消息
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多