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