Binding abstract action parameters in WebAPI

前端 未结 1 2010
轻奢々
轻奢々 2021-02-14 14:03

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.

相关标签:
1条回答
  • 2021-02-14 14:48

    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.

    0 讨论(0)
提交回复
热议问题