简体   繁体   English

复杂类型枚举模型绑定

[英]Complex Type Enum Model Binding

Background 背景

In .NET Core, default controller model binding fails (produces null value for your action argument) if your model contains an enum anywhere in its hierarchy and the supplied values don't match EXACTLY with the names in the enum . 在.NET Core中,如果模型的层次结构中的任何地方都包含enum ,并且提供的值与该enum的名称不完全匹配,则默认控制器模型绑定将失败(为您的action参数生成null值)。 White-space or odd capitalization breaks the binding, and that seems unfriendly for the consumer of my API endpoint. 空格或奇数大写会破坏绑定,这对我的API端点的使用者而言似乎不友好。

My solution 我的解决方案

I created a model binder provider, which uses reflection to determine if somewhere in the target binding type there exists an enum ; 我创建了一个模型绑定器提供程序,该提供程序使用反射来确定目标绑定类型中是否存在enum if this check is true, it returns a custom model binder (constructed by passing along the enum type) which uses Regex/string manipulation (gross) to scan the request body for the enum values and make an effort to resolve them to a name in that enum type, before passing JsonConvert for deserializing. 如果此检查为true,则返回一个自定义模型绑定程序(通过传递enum类型构造),该绑定程序使用正则表达式/字符串操作(粗略)扫描请求正文以获取enum值,并努力将其解析为名称。该enum类型,然后再传递JsonConvert进行反序列化。

This solution is, in my opinion, far too complex and ugly for what I'm trying to achieve. 我认为,这种解决方案对于我要实现的目标而言过于复杂和丑陋。

What I'd like is something like a JsonConvert attribute (for my enum fields) that makes this effort during binding/deserialization. 我想要的是类似JsonConvert属性(用于我的enum字段)的东西,该属性会在绑定/反序列化期间进行此工作。 Newtonsoft's out of the box solution ( StringEnumConverter ) doesn't try to adjust the string to fit the enum type (fair, I suppose), but I can't extend Newtonsoft's functionality here because it relies on a lot of internal classes (without copying and pasting a ton's of their code). Newtonsoft开箱即用的解决方案( StringEnumConverter )不会尝试调整字符串以适合enum类型(我想是公平的),但是我不能在这里扩展Newtonsoft的功能,因为它依赖于很多内部类(无需复制)并粘贴大量的代码)。

Is there a piece in the pipeline somewhere I'm missing that could be better leveraged to fit this need? 我想念的地方是否有管道可以更好地满足这一需求?

PS I placed this here rather than code review (it's too theoretical) or software engineering (too specific); PS我把它放在这里而不是代码审查(太理论化)或软件工程(太具体); please advise if it's not the right place. 请告知该地点是否不合适。

I've used a Type Safe Enum pattern for this, I think it will work for you. 我为此使用了类型安全的枚举模式,我认为它将对您有用。 With the TypeSafeEnum you can control what is mapped to the JSON using Newtonsoft's JsonConverter attribute. 使用TypeSafeEnum,您可以使用Newtonsoft的JsonConverter属性控制映射到JSON的内容。 Since you have no code to post I've built up a sample. 由于您没有要发布的代码,因此我建立了一个示例。

Base class used by your application's TypeSafeEnums: 应用程序的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;
    }
}

Sample type implemented as a TypeSafeEnum, it would have normally been a plain Enum, including Parse and TryParse methods: 实现为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;
        }
    }
}

Container to handle type safe conversion, so you don't need to create a converter for every type safe implemented, and to prevent changes in the TypeSafeEnumJsonConverter when new type safe enums are implemented: 用于处理类型安全转换的容器,因此您无需为实现的每个类型安全创建一个转换器,并在实现新类型安全枚举时防止在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;
        }
    }
}

Implements Newtonsoft's JsonConverter which in turn calls our TypeSafeEnumConverter 实现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());
    }
}

Sample object that uses our BirdType and sets the converter to use: 使用我们的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; }
}

Usage example: 用法示例:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM