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.
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.