问题
I'm in a situation where I need to bind an incoming HTTP POST request with data in the body, to a concrete type depending on a ProductType
denominator in the data. Here is my Web API 2 action method:
[HttpPost, Route]
public HttpResponseMessage New(ProductBase product)
{
// Access concrete product class...
if (product is ConcreteProduct)
// Do something
else if (product is OtherConcreteProduct)
// Do something else
}
I was first thinking of using a custom model binder, but it seems like it isn't possible to access the request body at that point:
For complex types, Web API tries to read the value from the message body, using a media-type formatter.
I can't really see how media-type formatters solves this problem, but I'm probably missing something. How would you solve this problem?
回答1:
Depending on the request content type you will have to decide which concrete class to instantiate. Let's take an example with application/json
. For this content type out-of-the-box the Web API is using the JSON.NET framework to deserialize the request body payload into a concrete object.
So you will have to hook into this framework in order to achieve the desired functionality. A good extension point in this framework is writing a custom JsonConverter
. Let's suppose that you have the following classes:
public abstract class ProductBase
{
public string ProductType { get; set; }
}
public class ConcreteProduct1 : ProductBase
{
public string Foo { get; set; }
}
public class ConcreteProduct2 : ProductBase
{
public string Bar { get; set; }
}
and the following action:
public HttpResponseMessage Post(ProductBase product)
{
return Request.CreateResponse(HttpStatusCode.OK, product);
}
Let's write a custom converter to handle this type:
public class PolymorphicProductConverter: JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(ProductBase);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var obj = JObject.Load(reader);
ProductBase product;
var pt = obj["productType"];
if (pt == null)
{
throw new ArgumentException("Missing productType", "productType");
}
string productType = pt.Value<string>();
if (productType == "concrete1")
{
product = new ConcreteProduct1();
}
else if (productType == "concrete2")
{
product = new ConcreteProduct2();
}
else
{
throw new NotSupportedException("Unknown product type: " + productType);
}
serializer.Populate(obj.CreateReader(), product);
return product;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
and the last step is to register this custom converter in the WebApiConfig
:
config.Formatters.JsonFormatter.SerializerSettings.Converters.Add(
new PolymorphicProductConverter()
);
And that's pretty much it. Now you can send the following request:
POST /api/products HTTP/1.1
Content-Type: application/json
Host: localhost:8816
Content-Length: 39
{"productType":"concrete2","bar":"baz"}
and the server will properly deserialize this message and respond with:
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
Date: Sat, 25 Jan 2014 12:39:21 GMT
Content-Length: 39
{"Bar":"baz","ProductType":"concrete2"}
If you need to handle other formats such as application/xml
you might do the same and plug into the corresponding serializer.
来源:https://stackoverflow.com/questions/21306305/binding-abstract-action-parameters-in-webapi