0

I'm running into a problem that I suspect is caused by an improper implementation of JSON.Net's TypeNameHandling on my part.

My Web API method expects a parameter of a base type BaseObj. Clients are expected to pass in an object of a derived type (DerivedObj for example). In order for .NET to map derived type to the base type, the global JsonFormatter is set to use TypeNameHandling.Auto in WebApiConfig.cs. Here is a contrived example of what I'm doing.

// The base and derived types
public class BaseObj { public virtual string Text { get; set; } }
public class DerivedObj { public override string Text { get; set; } }

// The API controller
public class TestApiController : ApiController
{
    public string TestMethod([FromBody]BaseObj obj) {
        return ((DerivedObj)obj).Text;   // ERROR: "Unable to cast object of type..."
    }
}

// TypeNameHandling is configured in WebApiConfig.cs
public static class WebApiConfig {
    public static void Register(HttpConfiguration config) {
        config.Formatters.JsonFormatter.SerializerSettings.TypeNameHandling = Newtonsoft.Json.TypeNameHandling.Auto;
        ...
    }
}

My client uses JQuery to call the API method using the derived type (note the $type field).

$.ajax({
    url: "http://localhost/TestApi/TestMethod",
    data: JSON.stringify({ $type: "MyAssembly.MyNamespace.DerivedObj, MyAssembly", Text: "hi!" }),
    dataType: "json",
    contentType: 'application/json',
    method: "POST",
    processData: false
})
    .success(function (response) {
        console.log(response);   // response should be "hi!"...
    })
    .fail(function (response) {
        console.error(response);   // ...instead, TestMethod throws the cast exception
    });

The problem is that the obj parameter received by the API method is truly of type BaseObj, not DerivedObj as the client specified in $type, thus an "Unable to cast object of type..." exception is thrown on the line return ((DerivedObj)obj).Text;

I suspect that TypeNameHandling is not working for these 2 reasons:

  • This example works fine if I change the API method to expect the derived type rather than the base type: public string TestMethod([FromBody]DerivedObj obj)
  • If I change the $type field in this example to a bogus type, I do not get any error stating that the bogus type was not found. I would expect that to happen, though I don't know for a fact how that situation is handled by JSON.Net/Web API. (Does it allow the request even if the type is bad?)

I did verify that the TypeNameHandling setting has been set correctly by watching GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.TypeNameHandling in the debugger.

Is there more to be done to enable TypeNameHandling? I would prefer not to implement a custom JsonConverter if I don't need to.

Keith
  • 20,636
  • 11
  • 84
  • 125
  • I did end up creating a custom JsonConverter per [this answer](http://stackoverflow.com/a/12641541/65775) and it works great, but I'd really like to know why the builtin TypeNameHandling isn't working. – Keith Oct 23 '15 at 16:03
  • Can you make an interface that both classes can implement and make parameter of type interface? – codeMonkey Oct 23 '15 at 18:29
  • 1
    @codeMonkey I tried that but then the parameter was received as null. Also tried an abstract base class and again the parameter was null. – Keith Oct 23 '15 at 18:37
  • I revisited this issue and now type name handling works fine. I made so many revisions in the meantime that it's difficult to tell what the fix was. But I'm really glad I implemented a custom JsonConverter instead because they I don't have to expose my internal namespace and assembly names to the public. – Keith Nov 23 '15 at 20:47
  • Hey, Keith, you really made it work using TypeNameHandling? I ran into the same problem and struggled for a day now. I tried to avoid custom JsonConverter because I don't like extra code and my WebAPIs are internal. I wonder if you could expose some more insides how you got it work. – newman Jul 18 '16 at 21:31
  • @miliu I was never able to figure out what my problem with TypeNameHandling was. I no longer even have the code with the working TypeNameHandling implementation (never checked it in). Sorry! – Keith Jul 19 '16 at 13:48

0 Answers0