11

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?

2
  • How is the request encoded? application/json? application/xml? application/x-www-form-urlencoded? Commented Jan 25, 2014 at 11:44
  • application/json, but ideally it shouldn't be dependent on the request encoding. Commented Jan 25, 2014 at 12:06

1 Answer 1

17
+100

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.

Sign up to request clarification or add additional context in comments.

3 Comments

Thank you very much Darin. I was hoping for a solution that isn't format specific, but I guess that isn't possible in Web API?
Great example. However, one drawback of this approach is that the documentation tools (for example asp .net web api help page) will only expose the abstract class' properties and the consumer of the API will not have any idea of the derived types unless he's part of the same development team. The request can't be strongly typed. I can't use Data Annotations to validate the model in this approach. But i think this mostly boils down to case by case compromising decisions
Where to put the config code in .net core application? @darin

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.