复杂类型枚举模型绑定

背景

在 .NET Core 中,如果模型在其层次结构中包含任何位置并且提供的值与 .NET Core 中的名称不完全匹配,则默认控制器模型绑定将失败(null为操作参数生成值)。空格或奇怪的大写字母会破坏绑定,这对于我的 API 端点的使用者来说似乎不友好。enumenum

我的解决方案

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

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

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

管道中是否有我遗漏的部分可以更好地利用来满足这一需求?

PS我把这个放在这里,而不是代码审查(太理论化)或软件工程(太具体);如果地方不对请指教。


哈士奇WWW
浏览 118回答 1
1回答

ibeautiful

我为此使用了类型安全枚举模式,我认为它适合您。通过 TypeSafeEnum,您可以使用 Newtonsoft 的 JsonConverter 属性控制映射到 JSON 的内容。由于您没有要发布的代码,我已经构建了一个示例。应用程序的 TypeSafeEnums 使用的基类:public abstract class TypeSafeEnumBase{&nbsp; &nbsp; protected readonly string Name;&nbsp; &nbsp; protected readonly int Value;&nbsp; &nbsp; protected TypeSafeEnumBase(int value, string name)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; this.Name = name;&nbsp; &nbsp; &nbsp; &nbsp; this.Value = value;&nbsp; &nbsp; }&nbsp; &nbsp; public override string ToString()&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; return Name;&nbsp; &nbsp; }}作为 TypeSafeEnum 实现的示例类型,它通常是一个普通的 Enum,包括 Parse 和 TryParse 方法:public sealed class BirdType : TypeSafeEnumBase{&nbsp; &nbsp; private const int BlueBirdId = 1;&nbsp; &nbsp; private const int RedBirdId = 2;&nbsp; &nbsp; private const int GreenBirdId = 3;&nbsp; &nbsp; public static readonly BirdType BlueBird =&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; new BirdType(BlueBirdId, nameof(BlueBird), "Blue Bird");&nbsp; &nbsp; public static readonly BirdType RedBird =&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; new BirdType(RedBirdId, nameof(RedBird), "Red Bird");&nbsp; &nbsp; public static readonly BirdType GreenBird =&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; new BirdType(GreenBirdId, nameof(GreenBird), "Green Bird");&nbsp; &nbsp; private BirdType(int value, string name, string displayName) :&nbsp; &nbsp; &nbsp; &nbsp; base(value, name)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; DisplayName = displayName;&nbsp; &nbsp; }&nbsp; &nbsp; public string DisplayName { get; }&nbsp; &nbsp; public static BirdType Parse(int value)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; switch (value)&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case BlueBirdId:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return BlueBird;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case RedBirdId:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return RedBird;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case GreenBirdId:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return GreenBird;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; default:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; throw new ArgumentOutOfRangeException(nameof(value), $"Unable to parse for value, '{value}'. Not found.");&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }&nbsp; &nbsp; public static BirdType Parse(string value)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; switch (value)&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case "Blue Bird":&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case nameof(BlueBird):&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return BlueBird;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case "Red Bird":&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case nameof(RedBird):&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return RedBird;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case "Green Bird":&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case nameof(GreenBird):&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return GreenBird;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; default:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; throw new ArgumentOutOfRangeException(nameof(value), $"Unable to parse for value, '{value}'. Not found.");&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }&nbsp; &nbsp; public static bool TryParse(int value, out BirdType type)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; try&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; type = Parse(value);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return true;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; catch&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; type = null;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return false;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }&nbsp; &nbsp; public static bool TryParse(string value, out BirdType type)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; try&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; type = Parse(value);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return true;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; catch&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; type = null;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return false;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }}用于处理类型安全转换的容器,因此您无需为实现的每个类型安全创建转换器,并在实现新的类型安全枚举时防止 TypeSafeEnumJsonConverter 发生更改:public class TypeSafeEnumConverter{&nbsp; &nbsp; public static object ConvertToTypeSafeEnum(string typeName, string value)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; switch (typeName)&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case "BirdType":&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return BirdType.Parse(value);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //case "SomeOtherType": // other type safe enums&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp; return // some other type safe parse call&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; default:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return null;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }}实现 Newtonsoft 的 JsonConverter,它又调用我们的 TypeSafeEnumConverterpublic class TypeSafeEnumJsonConverter : JsonConverter{&nbsp; &nbsp; public override bool CanConvert(Type objectType)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; var types = new[] { typeof(TypeSafeEnumBase) };&nbsp; &nbsp; &nbsp; &nbsp; return types.Any(t => t == objectType);&nbsp; &nbsp; }&nbsp; &nbsp; public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; string name = objectType.Name;&nbsp; &nbsp; &nbsp; &nbsp; string value = serializer.Deserialize(reader).ToString();&nbsp; &nbsp; &nbsp; &nbsp; return TypeSafeEnumConversion.ConvertToTypeSafeEnum(name, value); // call to our type safe converter&nbsp; &nbsp; }&nbsp; &nbsp; public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; if (value == null && serializer.NullValueHandling == NullValueHandling.Ignore)&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; writer.WriteValue(value?.ToString());&nbsp; &nbsp; }}使用 BirdType 并设置要使用的转换器的示例对象:public class BirdCoup{&nbsp; &nbsp; [JsonProperty("bird-a")]&nbsp; &nbsp; [JsonConverter(typeof(TypeSafeEnumJsonConverter))] // sets the converter used for this type&nbsp; &nbsp; public BirdType BirdA { get; set; }&nbsp; &nbsp; [JsonProperty("bird-b")]&nbsp; &nbsp; [JsonConverter(typeof(TypeSafeEnumJsonConverter))] // sets the converter for this type&nbsp; &nbsp; public BirdType BirdB { get; set; }}使用示例:// sample #1, converts value with spaces to BirdTypstring sampleJson_1 = "{\"bird-a\":\"Red Bird\",\"bird-b\":\"Blue Bird\"}";BirdCoup resultSample_1 =&nbsp;JsonConvert.DeserializeObject<BirdCoup>(sampleJson_1, new JsonConverter[]{new TypeSafeEnumJsonConverter()});// sample #2, converts value with no spaces in name to BirdTypestring sampleJson_2 = "{\"bird-a\":\"RedBird\",\"bird-b\":\"BlueBird\"}";BirdCoup resultSample_2 =&nbsp;JsonConvert.DeserializeObject<BirdCoup>(sampleJson_2, new JsonConverter[] { new TypeSafeEnumJsonConverter() });
打开App,查看更多内容
随时随地看视频慕课网APP