4

I have problems to receive json values from my javascript/jQuery request to my controller.

MyClass looks like the following:

function MyClass() {
    this.MyString = null;
    this.MyInt = null;
    this.Subs = null;
}

My Request looks like the following:

var testData1 = new MyClass();
testData1.MyInt = 1234;
testData1.MyString = "abcDEF";
testData1.Subs = new Array();
var testData2 = new MyClass();
testData2.MyInt = 5678;
testData2.MyString = "GHIjkl";
testData1.Subs.push(testData2);
var jsonData = JSON.stringify(testData1);
var self = this;
$.ajax({
    url: '/Home/Request',
    type: 'POST',
    dataType: 'json',
    data: jsonData,
    contentType: 'application/json; charset=utf-8',
    success: function (x) {
        self.ParseResult(x);
    }
});

Now I have a controller:

public JsonResult Request(MyClass myObj)
{
    var answer = ...
    return Json(answer, JsonRequestBehavior.DenyGet);
}

With the following class:

public class  MyClass
{
    public string MyString { get; set; }
    public int MyInt { get; set; }
    public List<MyClass> Subs { get; set; }
}

All names in jsonData are exactly the same like in my class "MyClass". But there are no values in myObj.

Where is the Problem. Is there anything I can do to get this mapping working correctly?

Thank you very much in advance,

Chris

UPDATE:

Thank you for your repley. I have used the JavascriptSerializer. But I have the problem, that myString is null:

public JsonResult Data(string myString)
{
    JavaScriptSerializer serializer = new JavaScriptSerializer();
    var data = serializer.Deserialize<MyClass>(myString);
var answer = ...
    return Json(answer, JsonRequestBehavior.DenyGet);
}

Where is the value? Should I take the value from the request-data?

@Dave Ward

You second solution is working. But I have a problem with the subs.

var testData1 = new MyClass();
testData1.MyInt = 1234;
testData1.MyString = "abcDEF";
testData1.Subs = new Array();
for (var i = 0; i < 10; i++) {
    var testData2 = new MyClass();
    testData2.MyInt = i;
    testData2.MyString = "abcDEF";
    testData1.Subs.push(testData2);
}

I get 10 Subs in my controller, but all are empty. What can I do?

@Dave Ward, @ALL

Using the traditional settings my 10 Subs are bot empty, they are not there. Subs count is 0 (not NULL). I tried to change the Subs Type from List to IEnumerable but that didn't help. Do you know anything else I can do to get Subs filled in my controller?

Ok, thank you Dave Ward, I will use the JSON method.

For somebody else having the same problem this controller code could be from help:

MemoryStream ms = new MemoryStream(Encoding.Unicode.GetBytes(myString));
var serializer = new DataContractJsonSerializer(typeof(MyClass));
MyClass se = serializer.ReadObject(ms) as MyClass;
ms.Close();
Chris
  • 4,325
  • 11
  • 51
  • 70

4 Answers4

3

Right now, you're sending the JSON string as the entire body of your POST. MVC has no way to connect the dots and understand that you wanted it to provide that entire string as a parameter named myString.

To do that, change your client-side data parameter like this:

$.ajax({
  url: '/Home/Request',
  type: 'POST',
  dataType: 'json',
  data: { myString: jsonData },
  contentType: 'application/json; charset=utf-8',
  success: function (x) {
    // If ParseResult is some sort of JSON deserializing, don't do that.
    // When the dataType is set to 'json', jQuery handles this for you 
    //  automatically before the success handler is called.
    self.ParseResult(x);
  }
});

When you provide jQuery an object as its data parameter, it automatically URLEncodes that before sending, so something like this will be POSTed to the server:

myString={json:data, here:etc}

MVC will pick that up as the myString parameter as desired then, and you can proceed with deserializing it with JavaScriptSerializer.

Also, unless you're doing something more complicated on the client-side than you're showing, the JavaScript class is unnecessary. Something like this would work:

var testData2 = {
  MyInt: 5678,
  MyString: "GHIjkl",
}

var testData1 = {
  MyInt: 1234,
  MyString: "abcDEF",
  Subs: [ testData2 ]
}

All that said, why are you using JSON for the request? If you accept a parameter of the custom type in your action, MVC's model binder is pretty good at hydrating that type from standard URLEncoded input data:

public JsonResult Data(MyClass request)
{
  // request.MyInt, request.MyString, etc should exist here.

  var answer = ...

  // It's okay to accept URLEncoded input parameters, but still return JSON.
  return Json(answer, JsonRequestBehavior.DenyGet);
}

Then, calling it doesn't require the client-side JSON serialization:

var testData2 = {
  MyInt: 5678,
  MyString: "GHIjkl",
}

var testData1 = {
  MyInt: 1234,
  MyString: "abcDEF",
  Subs: [ testData2 ]
}

$.ajax({
  url: '/Home/Request',
  type: 'POST',
  traditional: true,
  dataType: 'json',
  data: testData1
  success: function (x) {
    self.ParseResult(x);
  }
});

It's a bit simpler all around, and is faster since you've removed a layer of serialization on the client-side and deserialization on the server-side.

Dave Ward
  • 59,815
  • 13
  • 117
  • 134
  • Thank you for your answer. Your second solution is working very well. The only problem I have is with the "Subs". I have updated my orignal post to format my post a bit better. – Chris Dec 14 '10 at 23:29
  • I believe you'll also need to use jQuery's "traditional" serialization. I updated the $.ajax() code in my answer. – Dave Ward Dec 14 '10 at 23:35
  • With the "traditional" serialization the Subs-count @ the controller is 0. – Chris Dec 14 '10 at 23:54
  • Unfortunately, the nested object may be too deep to serialize with "traditional" urlencoding (at least under ASP.NET MVC; the "non-traditional" jQuery serialization is an implementation of fixing that deep nesting problem, but ASP.NET MVC doesn't support it). Go with the JSON. – Dave Ward Dec 15 '10 at 00:41
  • Thank you for all your help. I am using the JSON method with stringify and it working with your posted changes. – Chris Dec 15 '10 at 01:15
  • NOTE: as Dave has mentioned re: nested objects, in asp.net mvc2 specifically this is to do with JsonValueProviderFactory, this is build into asp.net mvc3, so if you were trying what dave suggested in asp.net mvc3 it would just work! – Haroon Apr 15 '11 at 06:57
  • See my question too. I have the link to solution file in the question . Please help me http://stackoverflow.com/questions/19221594/always-getting-null-values-in-controller-for-ajax-post – Subin Jacob Oct 08 '13 at 11:39
1

In my experience I have to pass in the json as a string and then deserialize it. I use Newtonsoft to do this.

public ActionResult DoAjax(string jsonRequest)
{
    JsonSerializer serializer = new JsonSerializer();

    StringReader sr = new StringReader(jsonRequest);
    Newtonsoft.Json.JsonTextReader reader = new JsonTextReader(sr);

    MyClass obj = (MyClass)serializer.Deserialize(reader, typeof(MyClass));

    //...do other stuff
}
Josh
  • 16,286
  • 25
  • 113
  • 158
  • 1
    I agree with this approach. I would add to this that you can also use the built-in JavaScriptSerializer in .NET to do the deserialization. See this link for more info http://msdn.microsoft.com/en-us/library/system.web.script.serialization.javascriptserializer.aspx – Hector Correa Dec 14 '10 at 22:00
  • @Hector - very cool. I wasn't aware there was a built in serializer for json. +1 for teaching me something new. :) – Josh Dec 14 '10 at 22:05
  • 1
    I've never had to do this, but there are some tricks around MVC's json deserialization. Basically if you're mixing ints, decimals, and strings things can get messy. A int as a string ("1") will bind to a string or int, but a decimal as a string will only deserialze to a string. I couldn't get "1.1" to bind to a decimal property. I have no idea how the MVC team misses these kinds of use cases. There's also oddities around nested structs, but I can't remember the specific use case I ran into. – Ryan Dec 14 '10 at 22:07
  • Thank you for your anwer. No matter which solution I choose (Joshs or (Hectors), myString is null. – Chris Dec 14 '10 at 22:17
  • 1
    BTW - if your going to use the built-in serializer, use `DataContractJsonSerializer`. It's more up to date and is what WCF uses. Onto the question - can you keep things simple first, remove the `Subs` property - and make sure you can pass a JSON object with just a `string` and `int` first. – RPM1984 Dec 14 '10 at 22:41
  • DataContractJsonSerializer is still actually *less* flexible than JavaScriptSerializer. Being able to pass date and enum strings into JSS is a big timesaver over DCJS sometimes, and they perform almost identically in real-world use. – Dave Ward Dec 14 '10 at 23:04
1

on a similar tack to josh's, you can use a json actionfilter a la:

// requires newtonsoft.json.dll 
public class JsonFilter : ActionFilterAttribute
{
    public string Param { get; set; }
    public Type JsonDataType { get; set; }
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (filterContext.HttpContext
            .Request.ContentType.Contains("application/json"))
        {
            string inputContent;
            using (var sr = new StreamReader(filterContext.HttpContext
                                .Request.InputStream))
            {
                inputContent = sr.ReadToEnd();
            }

            var result = JsonConvert.DeserializeObject(inputContent, JsonDataType);
            filterContext.ActionParameters[Param] = result;
        }
        else
            try
            {
                // we should do a loop looking for json here ONLY if there's a callback
                // i.e. we're calling jsonP
                if (filterContext.HttpContext.Request.QueryString["callback"] != null)
                {
                    string inputContent = 
                        Enumerable.Where(filterContext.HttpContext
                                        .Request.QueryString.Keys.Cast<string>()
                                        .Select(qs => filterContext.HttpContext
                                        .Request.QueryString[qs]), query => !string.IsNullOrEmpty(query))
                                        .FirstOrDefault(query => query.IndexOf("{") == 0);

                    var result = JsonConvert.DeserializeObject(inputContent, JsonDataType);
                    filterContext.ActionParameters[Param] = result;
                }
            }
            catch (Exception e)
            {
                // do nothing
                filterContext.ActionParameters[Param] = null;
            }
    }
}

usage (in this case typeof(IList)) but can be ANY class ie typeof(myClass):

[JsonFilter(Param = "jsonData", JsonDataType = typeof(IList<FundPropertyWeekSplit>))]
public virtual ActionResult AddFundPropertyWeekSplit(IList<FundPropertyWeekSplit> jsonData)
{
    // code to deal with the newly created strongly typed object
}

i use this (rather too much!!) all over the shop...

jim tollan
  • 22,305
  • 4
  • 49
  • 63
0

You can take a look Omar Al Zabir's article about json, xml request and response handling he provided a really good solution for solving this common situations.

Create REST API using ASP.NET MVC that speaks both Json and plain Xml

fyasar
  • 3,996
  • 2
  • 42
  • 55