2

Is there a way I can get an MVC controller to bind incoming dynamic JSON to a JToken object?

If I use an API Controller I can do this:

public class TestController : ApiController
{
    public void Post(JToken json)
    {
    }
}

and the posted json gets converted into a JToken object. However if I use an MVC controller it results in a server error.

public class TestController : Controller
{
    [HttpPost]
    public ActionResult TestAction(JToken json)
    {
        return new HttpStatusCodeResult(HttpStatusCode.OK);
    }
}

I realise there other ways to obtain the incoming data but I would prefer to receive it as a JToken in the MVC Controller.

I have tried to use a custom ValueProviderFactory from here but I still get a server error returned from my AJAX call:

$.ajax({
    url: '/Test/TestAction',    //or /api/Test
    type: 'POST',
    contentType: 'application/json',
    data: JSON.stringify({foo:"bar",wibble:"wobble"})
}).done(function (res) {
    alert('ok');
}).fail(function (xhr, status, error) {
    alert('error')
});

UPDATE:

Note - As stated above I have replaced the default JsonValueProviderFactory with one based on Json.NET.

On further investigation it appears that the problem occurs in the DefaultModelBinder.CreateModel method. When the DefaultModelBinder tries to create a JToken instance it fails because JToken is an abstract class. Even if I change the TestAction parameter to a JObject it stills fails, presumably because there are JToken properties further down the object heirarchy.

Jon Susiak
  • 4,948
  • 1
  • 30
  • 38
  • is your JToken class having foo and wibble properties? – Karthik M R Apr 27 '16 at 13:15
  • @Karthik It is a [Json.NET JToken Class](http://www.newtonsoft.com/json/help/html/t_newtonsoft_json_linq_jtoken.htm) – Jon Susiak Apr 27 '16 at 13:24
  • ok. does that class includes foo and wibble properties? – Karthik M R Apr 27 '16 at 13:27
  • 2
    ASP.Net MVC uses `JavaScriptSerializer` not Json.NET. To switch, see [Setting the Default JSON Serializer in ASP.NET MVC](https://stackoverflow.com/questions/14591750) and [How to use Json.NET for JSON modelbinding in an MVC5 project?](https://stackoverflow.com/questions/23995210). – dbc Apr 27 '16 at 15:15
  • @dbc Thanks, Jason Butera's answer in your second link provided the solution, I also needed to create a custom ModelBinder based on Json.NET. Do you want to provide an answer and I'll accept it or shall I show the solution? – Jon Susiak Apr 28 '16 at 10:47
  • @JonSusiak - Glad to help. I'd recommend making your own answer. Since you needed to extend the answer I found, it isn't an exact duplicate. – dbc Apr 28 '16 at 14:40

1 Answers1

2

In this particular case, changing the default serializer for incoming JSON by writing a custom ValueProviderFactory did not work. This appears to be because JToken is an abstract class and the default ModelBinder can't create a model instance where abstract classes are involved.

The solution was to create a custom ModelBinder for the Action:

public class JsonNetModelBinder : DefaultModelBinder
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        if (!IsJSONRequest(controllerContext))
        {
            return base.BindModel(controllerContext, bindingContext);
        }

        var request = controllerContext.HttpContext.Request;
        request.InputStream.Seek(0, SeekOrigin.Begin);
        var jsonStringData = new StreamReader(request.InputStream).ReadToEnd();

        return JsonConvert.DeserializeObject(jsonStringData, bindingContext.ModelType);
    }
    private static bool IsJSONRequest(ControllerContext controllerContext)
    {
        var contentType = controllerContext.HttpContext.Request.ContentType;
        return contentType.Contains("application/json");
    }
}

And use the custom ModelBinder on the Action as follows:

public class TestController : Controller
{
    [HttpPost]
    public ActionResult TestAction([ModelBinder(typeof(JsonNetModelBinder))] JToken json)
    {
        return new HttpStatusCodeResult(HttpStatusCode.OK);
    }
}
Jon Susiak
  • 4,948
  • 1
  • 30
  • 38