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.

6
  • is your JToken class having foo and wibble properties? Commented Apr 27, 2016 at 13:15
  • @Karthik It is a Json.NET JToken Class Commented Apr 27, 2016 at 13:24
  • ok. does that class includes foo and wibble properties? Commented Apr 27, 2016 at 13:27
  • 2
    ASP.Net MVC uses JavaScriptSerializer not Json.NET. To switch, see Setting the Default JSON Serializer in ASP.NET MVC and How to use Json.NET for JSON modelbinding in an MVC5 project?. Commented Apr 27, 2016 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? Commented Apr 28, 2016 at 10:47

1 Answer 1

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);
    }
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.