64

I'm calling a method of my WebAPI sending a JSON that I would like to match (or bind) with a model.

In the controller I have a method like:

public Result Post([ModelBinder(typeof(CustomModelBinder))]MyClass model);

'MyClass', which is given as a parameter is an abstract class. I would like that at, depending of the type of json passed, the correct inherited class is instantiated.

To achieve it, I'm trying to implement a custom binder. The problem is that (I don't know if it's very basic but I can't find anything) I don't know how to retrieve the raw JSON (or better, some kind of serialization) that comes in the request.

I see:

  • actionContext.Request.Content

But all methods are exposed as async. I don't know who this fits with passing the generate model to the controller method...

Jason Aller
  • 3,541
  • 28
  • 38
  • 38
Jacob
  • 1,886
  • 2
  • 25
  • 40

4 Answers4

95

You don't need a custom model binder. Nor do you need to muck about with the request pipeline.

Take a look at this other SO: How to implement custom JsonConverter in JSON.NET to deserialize a List of base class objects?.

I used this as the basis for my own solution to the same problem.

Starting off with the JsonCreationConverter<T> referenced in that SO (slightly modified to fix issues with serialization of types in responses):

public abstract class JsonCreationConverter<T> : JsonConverter
{
    /// <summary>
    /// this is very important, otherwise serialization breaks!
    /// </summary>
    public override bool CanWrite
    {
        get
        {
            return false;
        }
    }
    /// <summary> 
    /// Create an instance of objectType, based properties in the JSON object 
    /// </summary> 
    /// <param name="objectType">type of object expected</param> 
    /// <param name="jObject">contents of JSON object that will be 
    /// deserialized</param> 
    /// <returns></returns> 
    protected abstract T Create(Type objectType, JObject jObject);

    public override bool CanConvert(Type objectType)
    {
        return typeof(T).IsAssignableFrom(objectType);
    }

    public override object ReadJson(JsonReader reader, Type objectType,
      object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        // Load JObject from stream 
        JObject jObject = JObject.Load(reader);

        // Create target object based on JObject 
        T target = Create(objectType, jObject);

        // Populate the object properties 
        serializer.Populate(jObject.CreateReader(), target);

        return target;
    }

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

And now you can annotate your type with the JsonConverterAttribute, pointing Json.Net to a custom converter:

[JsonConverter(typeof(MyCustomConverter))]
public abstract class BaseClass{
  private class MyCustomConverter : JsonCreationConverter<BaseClass>
  {
     protected override BaseClass Create(Type objectType, 
       Newtonsoft.Json.Linq.JObject jObject)
     {
       //TODO: read the raw JSON object through jObject to identify the type
       //e.g. here I'm reading a 'typename' property:

       if("DerivedType".Equals(jObject.Value<string>("typename")))
       {
         return new DerivedClass();
       }
       return new DefaultClass();

       //now the base class' code will populate the returned object.
     }
  }
}

public class DerivedClass : BaseClass {
  public string DerivedProperty { get; set; }
}

public class DefaultClass : BaseClass {
  public string DefaultProperty { get; set; }
}

Now you can use the base type as a parameter:

public Result Post(BaseClass arg) {

}

And if we were to post:

{ typename: 'DerivedType', DerivedProperty: 'hello' }

Then arg would be an instance of the DerivedClass, but if we posted:

{ DefaultProperty: 'world' }

Then you'd get an instance of the DefaultClass.

EDIT - Why I prefer this method to TypeNameHandling.Auto/All

I do believe that using the TypeNameHandling.Auto/All espoused by JotaBe is not always the ideal solution. It might well be in this case - but personally I won't do it unless:

  • My API is only ever going to be used by me or my team
  • I don't care about having a dual XML-compatible endpoint

When Json.Net TypeNameHandling.Auto or All are used, your web server will start sending out type names in the format MyNamespace.MyType, MyAssemblyName.

I have said in comments that I think this is a security concern. Mention was made of this in some documentation I read from Microsoft. It's not mentioned any more, it seems, however I still feel it's a valid concern. I don't ever want to expose namespace-qualified type names and assembly names to the outside world. It's increasing my attack surface. So, yes, I can not have Object properties/parameters my API types, but who's to say the rest of my site is completely hole-free? Who's to say a future endpoint doesn't expose the ability to exploit type names? Why take that chance just because it's easier?

Also - if you are writing a 'proper' API, i.e. specifically for consumption by third-parties and not just for yourself, and you're using Web API, then you're most likely looking to leverage the JSON/XML content-type handling (as a minimum). See how far you get trying to write documentation that's easy to consume, which refers to all your API types differently for XML and JSON formats.

By overriding how JSON.Net understands the type names, you can bring the two into line, making the choice between XML/JSON for your caller purely based on taste, rather than because the type names are easier to remember in one or the other.

chris31389
  • 8,414
  • 7
  • 55
  • 66
Andras Zoltan
  • 41,961
  • 13
  • 104
  • 160
  • This is a complex solution comapred to native JSON.NET type handling. Please, see my answer. – JotaBe Jun 02 '14 at 16:23
  • I must completely disagree. Please, se my updated answer. I'd like to see the documentes security issues to add a reference to them. I've googled them, but haven't found anything at all. – JotaBe Jun 03 '14 at 09:22
  • 5
    To show that I'm not just being an a*hole - I have +1'd your answer because it has valid - up to a point - and may well be the most simple approach to this problem. I just don't think it should be seen as the silver bullet in all circumstances. – Andras Zoltan Jun 03 '14 at 10:06
  • 7
    I've updated my answer to reflect when your solution is the best option. I think it's been a very constructive discussion. You also have my +1, because your solution is a perfect start point to make all kinds of customizations on the JSON deserialization process. And now, both answers have improved. – JotaBe Jun 03 '14 at 10:43
  • 9
    Just to add a note. In the past I've gone with the $type solution but for a project I'm working on right now using AngularJS there were issues with it stripping anything with $ in the json. Further, using TypeScript classes, I could not figure out how I would go about ensuring $type was included in the JSON serialization. I'm sure there are solutions to these problems but this method allowed me to easily overcome these problems. – Mike Rowley Jun 18 '14 at 06:28
  • I like this solution! However unfortunately it looks like `serializer.Populate` does not work for Dictionary properties. Have you ever encoutered such a problem? – OschtärEi Aug 11 '15 at 14:57
  • 5
    My biggest gripe with $type is that I don't want to break API compatibility when refactoring server side code, such as renaming the type or moving it to another namespace or assembly. For example DTOs stored in client storage and used at a later time will suddenly no longer work. If instead you use an enum value or string, you are in full control of backwards compatibility in the API. – angularsen Jan 10 '16 at 09:39
  • 1
    I realize this is an old post, but this may help someone else - this code worked for my needs, but I had to fix a small issue in that the code as provided threw an exception about converting JObject to JToken. To fix this, I used this code in my Create method: `jObject[key].ToObject()`. Then I was able to do the necessary logic to select the type I desired. – Chris Thompson Feb 06 '17 at 15:05
  • Hello. Im trying to use this solution however I still get an error related to abstract class creation: `InvalidOperationException: Could not create an instance of type MyBaseClass. Model bound complex types must not be abstract or value types and must have a parameterless constructor at Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ComplexTypeModelBinder.CreateModel(ModelBindingContext bindingContext)` Am I missing something? – luisgepeto Apr 05 '18 at 17:26
  • Nevermind, I was missing the [FromBody] attribute for my endpoint: – luisgepeto Apr 05 '18 at 17:41
  • This was a great answer. I have written a sample based on this https://github.com/agrabhi/Samples/tree/master/WebApiInheritance – Abhishek Agrawal Jun 24 '18 at 03:34
49

You don't need to implement it by yourself. JSON.NET has native support for it.

You have to specify the desired TypeNameHandling option for the JSON formatter, like this (in global.asax application start event):

JsonSerializerSettings serializerSettings = GlobalConfiguration.Configuration
   .Formatters.JsonFormatter.SerializerSettings;
serializerSettings.TypeNameHandling = TypeNameHandling.Auto;

If you specify Auto, like in the above sample, the parameter will be deserialized to the type specified in the $type property of the object. If the $type property is missing, it will be deserialized to the parameter's type. So you only have to specify the type when you're passing a parameter of a derived type. (This is the most flexible option).

For example, if you pass this parameter to a Web API action:

var param = {
    $type: 'MyNamespace.MyType, MyAssemblyName', // .NET fully qualified name
    ... // object properties
};

The parameter will be deserialized to an object of MyNamespace.MyType class.

This also works fro sub-properties, i.e., you can have an object like this, which specifies that an inner property is of a given type

var param = { 
   myTypedProperty: {
      $type: `...`
      ...
};

Here you can see a sample on JSON.NET documentation of TypeNameHandling.Auto.

This works at least since JSON.NET 4 release.

NOTE

You don't need to decorate anything with attirbutes, or do any other customization. It will work without any changes in your Web API code.

IMPORTANT NOTE

The $type must be the first property of the JSON serialized object. If not, it will be ignored.

COMPARISON TO CUSTOM JsonConverter/JsonConverterAttribute

I'm comparing the native solution to this answer.

To implement the JsonConverter/JsonConverterAttribute:

  • you need to implement a custom JsonConverter, and a custom JsonConverterAttribute
  • you need to use attributes to mark the parameters
  • you need to know beforehand the possible types expected for the parameter
  • you need to implement, or change the implementation of your JsonConverter whenever your types or properties change
  • there is a code smell of magic strings, to indicate the expected property names
  • you are not implementing something generic that can be used with any type
  • you're reinventing the wheel

In the author of the answer there's a comment regarding security. Unless you do something wrong (like accepting a too generic type for your parameter, like Object) there is no risk of getting an instance of the wrong type: JSON.NET native solution only instantiates an object of the parameter's type, or a type derived from it (if not, you get null).

And these are the advantages of JSON.NET native solution:

  • you don't need to implement anything (you only have to configure the TypeNameHandling once in your app)
  • you don't need to use attributes in your action parameters
  • you don't need to know the possible parameter types beforehand: you simply need to know the base type, and specify it in the parameter (it could be an abstract type, to make polymorphism more obvious)
  • the solution works for most cases (1) without changing anything
  • this solution is widely tested, and optimized
  • you don't need magic strings
  • the implementation is generic, and will accept any derived type

(1): if you want to receive parameter values that don't inherit from the same base type, this will not work, but I see no point on doing so

So I can't find any disadvantages, and find many advantages on JSON.NET solution.

WHY USING CUSTOM JsonConverter/JsonConverterAttribute

This is a good working solution that allows customization, that can be modified or extended to adapt it to your particular case.

If you want to do something that the native solution cannot do, like customizing the type names, or inferring the type of the parameter based on available property names, then do use this solution adapted to your own case. The other one cannot be customized, and will not work for your needs.

Community
  • 1
  • 1
JotaBe
  • 38,030
  • 8
  • 98
  • 117
  • 1
    Yes you are right - but there is a reason why this is not suitable in my situations (including mine). There are *deep* security issues with using the built-in type name handling functionality of Json.Net as it effectively allows a malicious caller to bind any of your types - or any .Net types. The documentation is specific about this. My solution provides an abstraction away from that where you can control exactly the types which are available to be bound. – Andras Zoltan Jun 03 '14 at 07:54
  • I'd like to read that documentation about security. I suspected that, if the type is not of the parameter type, or one derived from it, the model wouldn't instantiate it. So I did a copy of the parameter class, passed it as parameter, and, as expected, I got a null (the object wasn't instantiated). Of course, if the parameter was of type `Object`, anything could be instantiated, but that's not a use case. Besides your implementation is not generic, not as well tested, probably lest performant. And, with JSON.NET you can implement security at another level easyly. – JotaBe Jun 03 '14 at 08:51
  • 1
    JotaBe - be very careful trotting out phrases 'not generic', 'not as well tested' & 'probably less performant' without first considering that this is a suitably cut-down answer so as not to provide a wall of code but which is easily tested & extended. Second, the solution I base this on is a wider solution implemented in a very busy Web API environment which performs excellently. Yes - the `$type` stuff is there, and it works. Dismissing security concerns as 'not a use-case' is naive. You should have come back with `SerializationBinder` as the answer to my point, too. – Andras Zoltan Jun 03 '14 at 09:22
  • 1
    The concerns about security were mentioned in Web API documentation early on - and, yes, it would appear that this has dropped off Google. Fair dos; but I'm not a liar, I did read those concerns. Even without someone else to back it up - I personally do not want to encode `MyNamespace.MyType, MyAssembly` type names in my JSON - I don't want to expose *any* of my real type names to the outside world; and third parties that use my API via JSON also do not want to have to use .Net type names when a simple `TypeName` would suffice. Each to their own, though. – Andras Zoltan Jun 03 '14 at 09:31
  • 1
    The only security concern I see is using a generic parameter type, like object or dynamic, which is the only case that allows to instance any arbitrary object. I just wanted to know if I'm missing anything else. Hey!, I'm not trying to look down on your solution, or thingking you're lying, not at all: I'm trying to express a fair opinion. I really think JSON.NET solution is more generic, works out of the box (only needs a parameter base class), is extensively field tested (with nearly 6M downloads in Nuget) and that's more testing than any well thought unit test suite that can be implemented – JotaBe Jun 03 '14 at 09:47
  • Before the suggestion comes in from SO to 'move this to chat'... Using `TypeNameHandling` is valid to a point-many people can happily switch it on & be done with it. In my case-my API must be consumed by *many* third parties in either XML or JSON. To ease documentation and integration, it's much simpler to have the same type name `MyType` regardless of whether you're calling from JSON or XML. With JSON.Net's `TypeNameHandling` you can't do that without writing lots of code. Equally I don't want, and neither do my callers, to see .Net type names in a lightweight wire format. – Andras Zoltan Jun 03 '14 at 10:03
  • 3
    Well, then you should be fair, and say that `TypeNameHandling` works fine, and it's a good solution **unless there's a concern that it cannot handle**. In that case you offer a tested solution that shows how to make a custom implementation of deserialization that can be useful for many other people that needs special customization (for example, in your case you don't want to use .NET fully qualified names). I agree with that!! It's a good solution for those cases (and not obvious at all). But the native solution is still good for most people. – JotaBe Jun 03 '14 at 10:30
  • 1
    I'm not worried about security, but I do worry about breaking API backwards compatibility due to renaming a DTO type or moving it to another namespace or assembly. This is why I definitely prefer a type property with an enum or string value that I am in full control of. – angularsen Jan 10 '16 at 09:42
  • for some reason I misclicked downvote 21 hours ago - i cannot undo until the answer is edited. This is a great answer! - sorry :) – VisualBean Jan 11 '18 at 07:36
4

You can call async methods normally, your execution will be simply suspended until the method returns and you can return the model in standard manner. Just make a call like this:

string jsonContent = await actionContext.Request.Content.ReadAsStringAsync();

It will give you raw JSON.

tpeczek
  • 23,867
  • 3
  • 74
  • 77
  • 6
    Don't use ".Result" unless you are happy with the possibility of a deadlock http://nitoprograms.blogspot.ch/2012/07/dont-block-on-async-code.html – Filip W Sep 28 '12 at 13:45
3

If you want to use the TypeNameHandling.Auto but are concerned with security or dont like api consumers needing that level of behind the scenes knowledge you can handle the $type deserialize your self.

public class InheritanceSerializationBinder : DefaultSerializationBinder
{
    public override Type BindToType(string assemblyName, string typeName)
    {
        switch (typeName)
        {
            case "parent[]": return typeof(Class1[]);
            case "parent": return typeof(Class1);
            case "child[]": return typeof(Class2[]);
            case "child": return typeof(Class2);
            default: return base.BindToType(assemblyName, typeName);
        }
    }
}

Then hook this up in global.asax.Application__Start

var config = GlobalConfiguration.Configuration;
        config.Formatters.JsonFormatter.SerializerSettings = new JsonSerializerSettings { Binder = new InheritanceSerializationBinder() };

finally i have used a wrapper class and [JsonProperty(TypeNameHandling = TypeNameHandling.Auto)] on a properpty containing the object with different types as i have not been able to get it to work by configuring the actual class.

This approach allows consumers to include the needed information in their request while allowing the documentation of the allowable values to be platform independent, easy to change, and easy to understand. All without having to write your own converster.

Credit to : https://mallibone.com/post/serialize-object-inheritance-with-json.net for showing me the custom deserializer of that field property.