【问题标题】:JsonConverter equivalent for HTTP GET parameterHTTP GET 参数的 JsonConverter 等效项
【发布时间】:2018-01-25 00:46:28
【问题描述】:

在为 HTTP POST 函数编写 C# Web API 控制器时,我可以在参数对象的属性上使用来自 Newtonsoft JSON 的属性。特别是,我可以在 enum 类型的属性上使用 JsonConverter attribute 将从客户端接收到的字符串表示形式转换为 enum 值之一(对于响应对象,也可以返回):

public class MyArgumentsDTO
{
    [JsonConverter(typeof(SomeEnumConverter))]
    public SomeEnum MyValue { get; set; }
}

// in the controller:
[Route("doSomething")]
[HttpPost]
public Boolean DoSomething(MyArgumentsDTO options);

但是,对于需要 enum 类型参数的 HTTP GET 方法,我应该怎么做?

[Route("getSomething")]
[HttpGet]
public Boolean GetSomething(SomeEnum myValue);

是否有一个属性可以用来装饰各个参数以指示(字符串到枚举)转换器?

(需要说明的是,我使用枚举作为示例,因为我经常将这种技术(使用 HTTP POST)与枚举一起使用。据推测,任何适用于枚举的解决方案同样适用于任何其他(可能是复杂的)数据类型。)


当然,我可以在方法体中将参数声明为string 并自己进行转换。但是,这似乎不干净,我同意related answer 中给出的声明:

将所有枚举参数定义为字符串,然后在任何地方解析它们意味着您必须对每一个操作执行此操作,并且您需要提出一种一致的方法,以使所有解析错误都符合。

不幸的是,我不理解该答案中提出的解决方案,因为它甚至没有涉及使用该问题中提到的TypeEnum


当我需要枚举参数时,对方法使用 HTTP POST 而不是 HTTP GET 似乎也是错误的。我认为不应该根据这种内部技术来选择 HTTP 方法。

【问题讨论】:

    标签: c# asp.net json.net


    【解决方案1】:

    枚举被 ASP.NET 模型绑定器正确反序列化。尝试定义一些简单的枚举,例如

    public enum Color
    {
        None,
        Green,
        Red,
    }
    
    [Route("getSomething")]
    [HttpGet]
    public string Get(Color color)
    {
        // ...
    }
    

    如果您 GET /api/values/color=Green,颜色将正确设置为 Color.Green。如果您需要一些自定义值转换(如 #FF0000Color.Red),使用自定义类型转换器(见下文)的方法将适合您。

    ASP.NET 还提供了从 url 反序列化更复杂数据类型的可能性。最简单的方法是实现自定义类型转换器。这是我前段时间开发的应用程序的示例。它适用于具有<department>:<order number> 格式的唯一标识符的订单,即NY:123LA:456。型号是

    public class OrderId
    {
        public string DepartmentId { get; }
    
        public int OrderNumber { get; }
    
        public OrderId(string departmentId, int orderNumber)
        {
            DepartmentId = departmentId;
            OrderNumber = orderNumber;
        }
    }
    

    并且需要通过 HTTP GET 方法传递此类订单 ID:

    [HttpGet]
    public OrderDetails GetOrderDetails(OrderId orderId)
    

    为了解决这个问题并让 orderId 从 Url 参数正确创建,我们可以实现自定义类型转换器,将字符串值转换为 OrderId 的实例:

    public class OrderIdTypeConverter : TypeConverter
    {
        private static readonly Regex OrderIdRegex = new Regex("^(.+):(\\d+)$", RegexOptions.Compiled);
    
        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
        {
            return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
        }
    
        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
        {
            var str = value as string;
            if (str != null)
            {
                int orderId;
                var match = OrderIdRegex.Match(str);
                if (match.Success && Int32.TryParse(match.Groups[2].Value, out orderId))
                {
                    return new OrderId(match.Groups[1].Value, orderId);
                }
            }
    
            return base.ConvertFrom(context, culture, value);
        }
    }
    

    要将此类型转换器与 OrderId 类相关联,只需添加 TypeConverter 属性:

    [TypeConverter(typeof(OrderIdTypeConverter))]
    public class OrderId
    

    现在,如果我们获得 Url /api/Orders/?orderId=NYC:123,则将使用正确填充的 OrderId 实例调用操作 GetOrderDetails

    ASP.NET 为从 URL 绑定模型提供了另一个扩展点。它们是IModelBinderIValueProvider 接口的自定义实现。查看此article 了解更多详情。

    如果您无法为您无法控制的类型设置类型转换器,则使用自定义模型绑定器的方法应该适合您。以下是用于自定义枚举值转换的IModelBinder 实现示例:

    public class CustomEnumModelBinder : IModelBinder
    {
        public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
        {
            if (bindingContext.ModelType != typeof(Color))
            {
                return false;
            }
    
            ValueProviderResult val = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
            if (val == null)
            {
                return false;
            }
    
            string rawValue = val.RawValue as string;
            if (rawValue == null)
            {
                bindingContext.ModelState.AddModelError(bindingContext.ModelName, "Incorrect input value type");
                return false;
            }
    
            //  Your logic for converting string to enum.
            if (rawValue == "FF0000")
            {
                bindingContext.Model = Color.Red;
                return true;
            }
    
            bindingContext.ModelState.AddModelError(bindingContext.ModelName, $"Cannot convert {rawValue} to Color");
            return false;
        }
    }
    
    [Route("getSomething")]
    [HttpGet]
    public string Get([ModelBinder(typeof(CustomEnumModelBinder))] Color color)
    {
        // ...
    }
    

    通过一些额外的工作,您可以实现某种可以聚合现有 Json 转换器的通用模型绑定器。

    【讨论】:

    • 这看起来很有希望。需要注意的一件事是,我不想使用枚举的自动(反)序列化,而是想为自定义字符串提供自定义映射。话虽如此,在要转换的类型上使用TypeConverter 属性对我来说是有问题的,因为我们应该在另一个程序集引用的一个程序集中保留“非活动”定义,例如接口和枚举类型,我可以在其中定义“活跃”的东西,比如转换器类。 IOW,从我的枚举类型声明中无法访问类型转换器。我将阅读链接的文章以查看...
    • ...它是否包含针对该问题的解决方案。
    • 有一个技巧可以为您无法控制的类型分配TypeConverterTypeDescriptor.AddAttributes(typeof(Color), new TypeConverterAttribute(typeof(ColorEnumConverter)))
    • 我已经用自定义枚举转换的模型活页夹样本更新了我的答案。
    • 优秀。它与模型绑定器解决方案一起使用 - 从那里开始,我应该很容易创建一个通用模型绑定器,它采用类型转换器或提供简单的 StringToX 方法或快速将此模式适应其他类型的东西。跨度>
    猜你喜欢
    • 2016-04-03
    • 1970-01-01
    • 1970-01-01
    • 2012-12-26
    • 2015-03-26
    • 1970-01-01
    • 2013-07-19
    • 1970-01-01
    • 2020-12-07
    相关资源
    最近更新 更多