0

I want to have a generic submission data contract that I can use to send lots of different requests to an API endpoint, using polymorphism to define the contracts for each specific request.

I have an object hierarchy that looks like the following

    ProductOrderSubmission
         -> IOrder Order
         -> string OrderId
         -> string User

    IOrder      
         -> IProduct[] Items

    EmailOrderSubmission : IOrder    
         -> IProduct[] Items    
         -> string ClientName    
         -> string FromEmail    
         -> IProduct ReferenceItem

    PreOrderSubmission : IOrder    
         -> IProduct[] Items    
         -> int AccountNumber    
         -> DateTime FulfillmentDate

    IProduct
         -> decimal Price
         -> int Quantity

    ListedProduct : IProduct
         -> decimal Price
         -> int Quantity
         -> int CatalogId
         -> int IdType

    PromoProduct : IProduct
         -> decimal Price
         -> int Quantity
         -> DateTime StartDate
         -> DateTime EndDate

With an API that has two methods, EmailOrder and PreOrder

Their structure looks like this (note the "route" is what I use in the model binder to determine which top level order type this is, whether its an email order or a preorder for example, you can see this in the model binder code)

[HttpPost, Route("email"), System.Web.Http.Description.ResponseType(typeof(OrderSubmissionResult))]
public async Task<IHttpActionResult> EmailOrderAsync([ModelBinder(typeof(ProductOrderSubmissionModelBinder))] ProductOrderSubmission orderSubmission)
{
    \\ action code here
}

[HttpPost, Route("preorder"), System.Web.Http.Description.ResponseType(typeof(OrderSubmissionResult))]
public async Task<IHttpActionResult> PreOrderAsync([ModelBinder(typeof(ProductOrderSubmissionModelBinder))] ProductOrderSubmission orderSubmission)
{
    \\ action code here
}

So the idea is that there is a generic ProductOrderSubmission object that contains the payload for all different sorts of requests. Based on the actual types and data at runtime, different code paths will execute.

The problem I have got is being able to deserialize the payload with polymorphism. I want to be able to call deserialize on a ProductOrderSubmission and have it configured so the deserializer knows how to determine whether the IOrder is an EmailOrderSubmission or a PreOrderSubmission and deserialise into the correct type, and likewise for each Item, I want it to be able to figure out to deserialise into ListedProduct or PromoProduct.

I understand that I need a custom deserializer for this, but I'm not able to figure out how exactly to structure this. I'll paste the code for as far as I have got below

The Model Binder

public class ProductOrderSubmissionModelBinder : IModelBinder
    {
        public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
        {
            if (bindingContext.ModelType != typeof(ProductOrderSubmission))
                return false;

            var content = actionContext.Request.Content.ReadAsStringAsync().Result;
            var action = actionContext.Request.RequestUri.Segments.Last();

            try
            {
                var submission = JsonConvert.DeserializeObject<ProductOrderSubmission>
                (
                    content, 
                    new EmailOrderSubmissionConverter(action)
                );

                bindingContext.Model = submission;

                return true;
            }
            catch (ValidationException ex)
            {
                bindingContext.ModelState.AddModelError(bindingContext.ModelName, ex.Message);

                return false;
            }
        }
    }

The EmailOrderSubmissionConverter

 public class EmailOrderSubmissionConverter : JsonConverter
    {
        private readonly string _action;
        public EmailOrderSubmissionConverter(string action)
        {
            _action = action;
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            var emailSubmission =
                JsonConvert.DeserializeObject<EmailOrderSubmission>(
                    reader.ReadAsString(),
                    new ListedProductConverter(),
                    new PromoProductConverter()
                );

            return emailSubmission;
        }

        public override bool CanConvert(Type objectType)
        {
            return _action == "email";
        }
    }

I haven't gotten as far as implementing the ListedProductConverter and PromoProductConverter as I'm starting to feel my approach is not the correct one as I'm not sure how to proceed

NZJames
  • 4,963
  • 15
  • 50
  • 100
  • I guess you are looking for https://stackoverflow.com/a/49214090/9260725. Imo it's not a dupe as other answer are "make a custom converter" and that's what you did. But so close. – xdtTransform Jul 24 '19 at 11:05
  • And https://www.newtonsoft.com/json/help/html/SerializeTypeNameHandling.htm – xdtTransform Jul 24 '19 at 11:11

0 Answers0