5

I have a call to a WebAPI with the following code:

var client = new HttpClient
{
    BaseAddress = new Uri("http://localhost:8490/")
};
var jObject = new JObject();
jObject.Add("paramA", paramA);
jObject.Add("paramB", paramB);
JArray jArr = JArray.FromObject(paramsGenericArr);
jObject.Add("paramC", jArr);
var content = new StringContent(jObject.ToString(), Encoding.UTF8, "application/json");
var result = await client.PostAsync("api/path/tofunc", content).ConfigureAwait(false);
result.EnsureSuccessStatusCode();

The ParamsGeneric class is an abstract type with 2 derived classes:

[DataContract]
public class ParamsTypeA : ParamsGeneric
{
    [DataMember]
    public long itemC {get; set;}
    public ParamsTypeA() :
                      base()
    {}
}

[DataContract]
public class ParamsTypeB : ParamsGeneric
{
    [DataMember]
    public long itemD {get; set;}
    public ParamsTypeB() :
                      base()
    {}
}

[DataContract]
[KnownType(typeof(ParamsTypeA))]
[KnownType(typeof(ParamsTypeB))]
public abstract class ParamsGeneric
{
    [DataMember]
    public long itemA { get; set; }
    [DataMember]
    public long itemB {get; set;}
    public ParamsGeneric() 
    {}
}

I suspect that I have a problem with the deserialization in the WebAPI:

public class ClientData
{
    public string paramA { get; set; }
    public string paramB { get; set; }
    public ParamsGeneric[] paramC { get; set; }
}

[HttpPost]
[Route("api/path/tofunc")]
public async Task<bool> DoStuffAsync(ClientData clientData)
{
    ....
}

I have a problem with the paramsGenericArr/paramC (which is of type ParamsGeneric[], and holds items of type ParamsTypeA & ParamsTypeB)

The WebAPI receives a blank array (ParamsGeneric[0]), along with the other parameters.

Help will be appriciated.

UPDATE

Even if I try to pass a single ParamsGeneric object instead of an array, I receive null instead of the object.

SOLUTION

var serializer = new JsonSerializer();
serializer.TypeNameHandling = TypeNameHandling.Auto;
JArray jArr = JArray.FromObject(paramsGenericArr, serializer);

Did the trick.

shlatchz
  • 1,612
  • 1
  • 18
  • 40
  • 1
    Side-note, did you know that you can use anonymous objects with Json.Net to create json? `JsonConvert.SerializeObject(new { paramA, paramB, paramC = new[] { ... } })` – Lasse V. Karlsen Apr 18 '16 at 13:00
  • @LasseV.Karlsen Good to know. – shlatchz Apr 18 '16 at 13:05
  • 1
    Check out [this post](https://stackoverflow.com/a/12641541/968003) for a customized JSON converter to handle inherited classes. The post also explains why it's not recommended to use `TypeNameHandling.Auto or All`. – Alex Klaus Sep 21 '17 at 04:47

3 Answers3

3

While inheritance in messages / json is definitely possible, IMHO; it's just too much of a hassle :)

Anyway, you can actually let Newtonsoft.Json handle the inheritance for you, by setting TypeNameHandling

// Allow inheritance in json payload
JsonSerializerSettings serializerSettings = GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings;
serializerSettings.TypeNameHandling = TypeNameHandling.All;

.. or just

serializerSettings.TypeNameHandling = TypeNameHandling.Auto;

.. depending on your needs.

This is the easy fix, which will work fine if it's an internal API or if you can guarantee you will always be in control of the clients. If you have external clients, I would go the 'override default model binder'-approach such as what is posted here "Deserialising Json to derived types in Asp.Net Web API" - or very strongly consider avoiding inheritance in the model of the API.

Community
  • 1
  • 1
cwap
  • 11,087
  • 8
  • 47
  • 61
0

Try to pass data to your API as below

Dictionary<string, string> param = new Dictionary<string, string>();
param.Add("paramA", paramA);
param.Add("paramB", paramB);

HttpClient client = new HttpClient();
HttpFormUrlEncodedContent contents = new HttpFormUrlEncodedContent(param);
var result = await client.PostAsync(new Uri("http://localhost:8490/api/path/tofunc")
                                                                         , contents);
var reply = await result.Content.ReadAsStringAsync();
if (reply.IsSuccessStatusCode)
{ 
}

Hope this will help you.

Krunal Mevada
  • 1,637
  • 1
  • 17
  • 28
-1
[HttpPost]
[Route("api/path/tofunc")]
public async Task<bool> DoStuffAsync([FromBody]ClientData clientData)
{
....
}

Please keep [FromBody] in the web api method, so that model binder will map your body data to parameter i.e clientData.

  • It didn't change anything. As I wrote, I do get a value for clientData. Only the paramC in clientData is ParamsGeneric[0] (or null, if I change it to a single object and not an array). – shlatchz Apr 18 '16 at 12:02
  • Why do you needed to write abstract class (public abstract class ParamsGeneric)? can you make it concrete class and try. – Siva Kumar Siddam Apr 18 '16 at 12:05
  • Because I want to pass two types of values (ParamsTypeA/ParamsTypeB). That's one of the pros of inheritance. – shlatchz Apr 18 '16 at 12:09
  • for this kind of things, you must need to override the default model binder, and this link will help you , http://stackoverflow.com/questions/17277578/web-api-model-binding-and-polymorphic-inheritence – Siva Kumar Siddam Apr 18 '16 at 12:13