复杂类型枚举模型绑定
Complex Type Enum Model Binding
背景
在 .NET Core 中,如果您的模型在其层次结构中的任何位置包含 enum
并且提供的值不匹配,则默认控制器模型绑定失败(为您的操作参数生成 null
值)与 enum
中的名称完全相同。 White-space 或奇数大写打破了绑定,这对我的 API 端点的消费者来说似乎不友好。
我的解决方案
我创建了一个模型活页夹提供程序,它使用反射来确定目标绑定类型中的某处是否存在 enum
;如果此检查为真,则它 returns 一个自定义模型活页夹(通过传递 enum
类型构建),它使用 Regex/string 操作(粗略)扫描请求主体以查找 enum
值并努力将它们解析为该 enum
类型的名称,然后再传递 JsonConvert
进行反序列化。
在我看来,这个解决方案对于我想要实现的目标来说过于复杂和丑陋。
我想要的是 JsonConvert 属性(用于我的 enum
字段),它在 binding/deserialization 期间完成这项工作。 Newtonsoft 开箱即用的解决方案 (StringEnumConverter
) 不会尝试调整字符串以适应 enum
类型(我想是公平的),但我不能在这里扩展 Newtonsoft 的功能,因为它依赖于很多内部 类(无需复制和粘贴大量代码)。
管道中是否有我遗漏的部分可以更好地利用来满足此需求?
P.S. 我把它放在这里而不是代码审查(太理论化)或软件工程(太具体);不对的地方请指教
我为此使用了类型安全枚举模式,我认为它对你有用。使用 TypeSafeEnum,您可以使用 Newtonsoft 的 JsonConverter 属性控制映射到 JSON 的内容。由于您没有 post 的代码,我已经构建了一个示例。
您的应用程序的 TypeSafeEnums 使用的基 class:
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() });
背景
在 .NET Core 中,如果您的模型在其层次结构中的任何位置包含 enum
并且提供的值不匹配,则默认控制器模型绑定失败(为您的操作参数生成 null
值)与 enum
中的名称完全相同。 White-space 或奇数大写打破了绑定,这对我的 API 端点的消费者来说似乎不友好。
我的解决方案
我创建了一个模型活页夹提供程序,它使用反射来确定目标绑定类型中的某处是否存在 enum
;如果此检查为真,则它 returns 一个自定义模型活页夹(通过传递 enum
类型构建),它使用 Regex/string 操作(粗略)扫描请求主体以查找 enum
值并努力将它们解析为该 enum
类型的名称,然后再传递 JsonConvert
进行反序列化。
在我看来,这个解决方案对于我想要实现的目标来说过于复杂和丑陋。
我想要的是 JsonConvert 属性(用于我的 enum
字段),它在 binding/deserialization 期间完成这项工作。 Newtonsoft 开箱即用的解决方案 (StringEnumConverter
) 不会尝试调整字符串以适应 enum
类型(我想是公平的),但我不能在这里扩展 Newtonsoft 的功能,因为它依赖于很多内部 类(无需复制和粘贴大量代码)。
管道中是否有我遗漏的部分可以更好地利用来满足此需求?
P.S. 我把它放在这里而不是代码审查(太理论化)或软件工程(太具体);不对的地方请指教
我为此使用了类型安全枚举模式,我认为它对你有用。使用 TypeSafeEnum,您可以使用 Newtonsoft 的 JsonConverter 属性控制映射到 JSON 的内容。由于您没有 post 的代码,我已经构建了一个示例。
您的应用程序的 TypeSafeEnums 使用的基 class:
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() });