Linking abstract action parameters in WebAPI

I am in a situation where I need to associate an incoming HTTP POST request with the data in the body, to a specific type depending on the 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 } 

At first I thought about using a custom mediator, but it seems like it's impossible to access the request body at this point:

For complex types, the Web API attempts to read the value from the body message using a media type formatter.

I can’t understand how media type formers solve this problem, but I probably missed something. How would you solve this problem?

+8
c # asp.net-web-api model-binding
source share
1 answer

Depending on the type of request content, you will need to decide which particular class you want to create. Take an example with application/json . For this type of content out of the box, the Web API uses the JSON.NET framework to deserialize the payload of the request body into a specific object.

Therefore, you will need to connect to this structure in order to achieve the desired functionality. A good extension point in this framework is to create a custom JsonConverter . Suppose 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 me write my own 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 WebApiConfig :

 config.Formatters.JsonFormatter.SerializerSettings.Converters.Add( new PolymorphicProductConverter() ); 

And that is pretty much the case. 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 reply:

 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 can do the same and connect to the appropriate serializer.

+12
source share

All Articles