18

I want to have an API as such:

public class RelayController : ApiController
{
    // POST api/values
    public void Post([FromBody]IDataRelayPackage package)
    {
        MessageQueue queue = new MessageQueue(".\\private$\\DataRelay");
        queue.Send(package);
        queue.Close();
    }
}

I'm getting a null value for 'package' so I'm wondering what might be going wrong. My only thoughts are that the default JSON serializer can't handle this, but I'm unclear how to fix it.

tereško
  • 58,060
  • 25
  • 98
  • 150
David Cornelson
  • 391
  • 2
  • 3
  • 16
  • In the interest of time, I've decided to alter my WebAPI POST to accept a string containing XML. I plan to circle back on this later. – David Cornelson Jan 02 '13 at 16:16

4 Answers4

18

You can do this fairly easily with a custom model binder. Here is what worked for me. (Using Web API 2 and JSON.Net 6)

public class JsonPolyModelBinder : IModelBinder
{
    readonly JsonSerializerSettings settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto };

    public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
    {
        var content = actionContext.Request.Content;
        string json = content.ReadAsStringAsync().Result;
        var obj = JsonConvert.DeserializeObject(json, bindingContext.ModelType, settings);
        bindingContext.Model = obj;
        return true;
    }
}

The Web API controller looks like this. (Note: should also work for regular MVC actions -- I've done something like this for them before as well.)

public class TestController : ApiController
{
    // POST api/test
    public void Post([ModelBinder(typeof(JsonPolyModelBinder))]ICommand command)
    {
        ...
    }
}

I should also note that when you serialize the JSON, you should serialize it with the same setting, and serialize it as an interface to make the Auto kick in and include the type hint. Something like this.

    JsonSerializerSettings settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto };
    string json = JsonConvert.SerializeObject(command, typeof(ICommand), settings);
Kasey Speakman
  • 4,511
  • 2
  • 32
  • 41
  • This is awesome and opens up overloaded JSON service factory interfaces. Thanks! – David Cornelson Mar 13 '14 at 17:06
  • 1
    Additionally, JSON.Net can deserialize immutable messages (no public setters) so long as your property names match your constructor parameters (case insensitive). – Kasey Speakman Mar 13 '14 at 17:25
  • 1
    Eventually, I decided it was just simpler to have my Post action with no parameters and deserialize the post body as part of the action. That way I can handle exceptions or whatever else I want to do. – Kasey Speakman Aug 05 '14 at 18:27
5

You are trying to deserialise to an interface. The serialiser won't know what type to instantiate unless it is told.

Take a look at TypeNameHandling option Posting a collection of subclasses

Or look at creating a custom JsonConverter. Take a look at this question How to implement custom JsonConverter in JSON.NET to deserialize a List of base class objects?

Community
  • 1
  • 1
Mark Jones
  • 12,156
  • 2
  • 50
  • 62
  • `You are trying to deserialise to an interface`. D'oh, but well explained. I've had the same issue, this one line made it crystal clear. Thanks. – MyDaftQuestions Apr 25 '18 at 18:27
3

Json.NET (the default Json serializer for ASP.NET Web API) can handle this situation. All you need to do is change the serializer settings, and set TypeNameHandling to All (or Objects). This will add a "$type"-json property to your json, containing the type name of your instance. On the other side it'll try to deserialize to this type again.

We used this for ourselves, taking an abstract base class as type.

0

The serializer needs a type that it can construct, one with an empty (Default) constructor. Since an Interface can't be constructed, serialization fails and you get a null value.

Two options:

  1. Use a POCO (Plain Old Clr Object) with a default constructor
  2. Implement your own binder using IActionValueBinder
Josh
  • 44,706
  • 7
  • 102
  • 124