JsonConverter等效于HTTP GET参数

时间:2018-01-24 14:14:41

标签: c# asp.net json.net

为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方法。

1 个答案:

答案 0 :(得分:1)

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

public enum Color
{
    None,
    Green,
    Red,
}

[Route("getSomething")]
[HttpGet]
public string Get(Color color)
{
    // ...
}

如果您获得/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)

要解决此问题并从Url参数正确创建orderId,我们可以实现将字符串值转换为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);
    }
}

要将此Type Converter与OrderId类关联,只需添加TypeConverter属性:

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

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

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转换器的通用模型绑定器。