问题
When writing C# web API controllers for HTTP POST functions, I can use attributes from Newtonsoft JSON on the properties of the parameter object. In particular, I can use a JsonConverter attribute on properties of an enum
type to convert a string representation received from the client into one of the enum
values (and back, for response objects):
public class MyArgumentsDTO
{
[JsonConverter(typeof(SomeEnumConverter))]
public SomeEnum MyValue { get; set; }
}
// in the controller:
[Route("doSomething")]
[HttpPost]
public Boolean DoSomething(MyArgumentsDTO options);
However, what should I do for HTTP GET methods that expect a parameter of such an enum
type?
[Route("getSomething")]
[HttpGet]
public Boolean GetSomething(SomeEnum myValue);
Is there an attribute that I can decorate the respective parameter with to indicate a (string to enum) converter?
(To be clear, I am using enums as an example because I regularly use this technique (with HTTP POST) with enums. Presumeably, any solution that works for enums would just as well work for any other (possibly complex) data types.)
Of course, I could just declare the parameter as string
and do the conversion myself, in the method body. However, that seems unclean, and I agree with a statement given in a related answer:
Defining all your enum parameters as strings and then parsing them everywhere means you have to do this on every single action and you will need to come up with a consistent approach such that all parsing errors conform.
Unfortunately, I do not understand the solution proposed in that answer, given that it does not even touch upon using the TypeEnum
mentioned in that question.
Using HTTP POST instead of HTTP GET for methods when I need an enum parameter seems somehow wrong, as well. I don't think the HTTP method should be chosen based upon such internal technicalities.
回答1:
Enums are correctly deserialized by ASP.NET model binder. Try defininig some simple enum, e.g.
public enum Color
{
None,
Green,
Red,
}
[Route("getSomething")]
[HttpGet]
public string Get(Color color)
{
// ...
}
If you GET /api/values/color=Green
, color will be correctly set to Color.Green
. If you need some custom values conversion (like #FF0000
to Color.Red
) approach with custom Type Converter (see below) will work for you.
ASP.NET also provides possibility to deserialize more complex data types from the url. The easiest way is to implement custom type converter. Here is a sample from the application I have developed some time ago. It worked with the orders that have unique identifiers in format <department>:<order number>
, i.e. NY:123
or LA:456
. The model is
public class OrderId
{
public string DepartmentId { get; }
public int OrderNumber { get; }
public OrderId(string departmentId, int orderNumber)
{
DepartmentId = departmentId;
OrderNumber = orderNumber;
}
}
And it was required to pass such order ids via HTTP GET method:
[HttpGet]
public OrderDetails GetOrderDetails(OrderId orderId)
To solve this and make orderId
correctly created from an Url parameter, we could implement custom Type Converter that converts string value to instance of 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);
}
}
To associate this Type Converter with OrderId class just add the TypeConverter
attribute:
[TypeConverter(typeof(OrderIdTypeConverter))]
public class OrderId
Now if we get Url /api/Orders/?orderId=NYC:123
, action GetOrderDetails
will be called with correctly filled instance of OrderId
.
ASP.NET provides another extensibility points for binding the model from the URL. They are custom implementations of IModelBinder
and IValueProvider
interfaces. Check this article for more details.
If you can't set type converter for the type that you don't control, approach with custom model binder should work for you. Here is a sample of IModelBinder
implementation for customizing conversion of enum values:
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)
{
// ...
}
Wit a bit of additional work you could implement some sort of generic model binder that could aggregate your existing Json converters.
来源:https://stackoverflow.com/questions/48424564/jsonconverter-equivalent-for-http-get-parameter