【问题标题】:Complex Type Enum Model Binding复杂类型枚举模型绑定
【发布时间】:2019-08-27 03:27:45
【问题描述】:

背景

在 .NET Core 中,如果您的模型在其层次结构中的任何位置包含 enum 并且提供的值与enum。空格或奇数大写会破坏绑定,这对我的 API 端点的使用者来说似乎不友好。

我的解决方案

我创建了一个模型绑定器提供程序,它使用反射来确定目标绑定类型中的某处是否存在enum;如果此检查为真,它将返回一个自定义模型绑定器(通过传递 enum 类型构建),该绑定器使用正则表达式/字符串操作(粗略)扫描请求正文中的 enum 值并努力解决它们到 enum 类型中的名称,然后通过 JsonConvert 进行反序列化。

在我看来,这个解决方案对于我想要实现的目标来说过于复杂和丑陋。

我想要的是类似于 JsonConvert 属性(对于我的 enum 字段),它在绑定/反序列化期间进行这项工作。 Newtonsoft 的开箱即用解决方案 (StringEnumConverter) 不会尝试调整字符串以适应 enum 类型(公平,我想),但我不能在这里扩展 Newtonsoft 的功能,因为它依赖于很多内部类(无需复制和粘贴大量代码)。

在我遗漏的某个地方是否有一个可以更好地满足这一需求的部分?

P.S.我把它放在这里而不是代码审查(太理论化)或软件工程(太具体);如果位置不对,请指教。

【问题讨论】:

    标签: c# asp.net-core enums model-binding


    【解决方案1】:

    我为此使用了类型安全枚举模式,我认为它对你有用。使用 TypeSafeEnum,您可以使用 Newtonsoft 的 JsonConverter 属性控制映射到 JSON 的内容。由于您没有要发布的代码,我已经建立了一个示例。

    应用程序的 TypeSafeEnums 使用的基类:

    public abstract class TypeSafeEnumBase
    {
        protected readonly string Name;
        protected readonly int Value;
    
        protected TypeSafeEnumBase(int value, string name)
        {
            this.Name = name;
            this.Value = value;
        }
    
        public override string ToString()
        {
            return Name;
        }
    }
    

    作为 TypeSafeEnum 实现的示例类型,它通常是一个普通的 Enum,包括 Parse 和 TryParse 方法:

    public sealed class BirdType : TypeSafeEnumBase
    {
        private const int BlueBirdId = 1;
        private const int RedBirdId = 2;
        private const int GreenBirdId = 3;
        public static readonly BirdType BlueBird = 
            new BirdType(BlueBirdId, nameof(BlueBird), "Blue Bird");
        public static readonly BirdType RedBird = 
            new BirdType(RedBirdId, nameof(RedBird), "Red Bird");
        public static readonly BirdType GreenBird = 
            new BirdType(GreenBirdId, nameof(GreenBird), "Green Bird");
    
        private BirdType(int value, string name, string displayName) :
            base(value, name)
        {
            DisplayName = displayName;
        }
    
        public string DisplayName { get; }
    
        public static BirdType Parse(int value)
        {
            switch (value)
            {
                case BlueBirdId:
                    return BlueBird;
                case RedBirdId:
                    return RedBird;
                case GreenBirdId:
                    return GreenBird;
                default:
                    throw new ArgumentOutOfRangeException(nameof(value), $"Unable to parse for value, '{value}'. Not found.");
            }
        }
    
        public static BirdType Parse(string value)
        {
            switch (value)
            {
                case "Blue Bird":
                case nameof(BlueBird):
                    return BlueBird;
                case "Red Bird":
                case nameof(RedBird):
                    return RedBird;
                case "Green Bird":
                case nameof(GreenBird):
                    return GreenBird;
                default:
                    throw new ArgumentOutOfRangeException(nameof(value), $"Unable to parse for value, '{value}'. Not found.");
            }
        }
    
        public static bool TryParse(int value, out BirdType type)
        {
            try
            {
                type = Parse(value);
                return true;
            }
            catch
            {
                type = null;
                return false;
            }
        }
    
        public static bool TryParse(string value, out BirdType type)
        {
            try
            {
                type = Parse(value);
                return true;
            }
            catch
            {
                type = null;
                return false;
            }
        }
    }
    

    用于处理类型安全转换的容器,因此您无需为实现的每个类型安全创建转换器,并防止在实现新的类型安全枚举时更改 TypeSafeEnumJsonConverter:

    public class TypeSafeEnumConverter
    {
        public static object ConvertToTypeSafeEnum(string typeName, string value)
        {
            switch (typeName)
            {
                case "BirdType":
                    return BirdType.Parse(value);
                //case "SomeOtherType": // other type safe enums
                //    return // some other type safe parse call
                default:
                    return null;
            }
        }
    }
    

    实现 Newtonsoft 的 JsonConverter,后者又调用我们的 TypeSafeEnumConverter

    public class TypeSafeEnumJsonConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            var types = new[] { typeof(TypeSafeEnumBase) };
            return types.Any(t => t == objectType);
        }
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            string name = objectType.Name;
            string value = serializer.Deserialize(reader).ToString();
            return TypeSafeEnumConversion.ConvertToTypeSafeEnum(name, value); // call to our type safe converter
        }
    
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            if (value == null && serializer.NullValueHandling == NullValueHandling.Ignore)
            {
                return;
            }
            writer.WriteValue(value?.ToString());
        }
    }
    

    使用我们的 BirdType 并设置要使用的转换器的示例对象:

    public class BirdCoup
    {
        [JsonProperty("bird-a")]
        [JsonConverter(typeof(TypeSafeEnumJsonConverter))] // sets the converter used for this type
        public BirdType BirdA { get; set; }
    
        [JsonProperty("bird-b")]
        [JsonConverter(typeof(TypeSafeEnumJsonConverter))] // sets the converter for this type
        public BirdType BirdB { get; set; }
    }
    

    使用示例:

    // sample #1, converts value with spaces to BirdTyp
    string sampleJson_1 = "{\"bird-a\":\"Red Bird\",\"bird-b\":\"Blue Bird\"}";
    BirdCoup resultSample_1 = 
    JsonConvert.DeserializeObject<BirdCoup>(sampleJson_1, new JsonConverter[]{new TypeSafeEnumJsonConverter()});
    
    // sample #2, converts value with no spaces in name to BirdType
    string sampleJson_2 = "{\"bird-a\":\"RedBird\",\"bird-b\":\"BlueBird\"}";
    BirdCoup resultSample_2 = 
    JsonConvert.DeserializeObject<BirdCoup>(sampleJson_2, new JsonConverter[] { new TypeSafeEnumJsonConverter() });
    

    【讨论】:

    • 太好了,这正好符合我的需要。我知道这只是一个例子,但是从 1 而不是 0 开始值的实现原因是什么?这可能会阻止可能很好的默认实例化;当我实施它时我会发现。还要欣赏如何正确扩展 JsonConverter 的示例。
    • 谢谢。没有理由,你是对的。在我的真实代码中,我从零开始。
    • 我注意到在这篇文章中我没有完全实现对 int 值的解析。我可能会用该更改更新帖子。但从字符串解析时不需要。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-18
    相关资源
    最近更新 更多