简体   繁体   中英

Passing a Complex Javascript Object to ASP.NET Web Service

I'm using the jQuery AJAX function to pass a complex JavaScript object to a Web Method in an ASP.NET web service. I followed the advice in this article (among others):

Using Complex Types to Make Calling Services Less Complex

I am now able to pass objects from client-to-browser and from browser-to-client. However, I've encountered a problem that is beyond my skill to heal. When passing a complex object from client-to-browser, data is somehow being "lost" on the trip. It's there in the JSON string that I'm passing to the web service, but when ASP.NET AJAX translates the JSON string into a new instance of the object and passes it to the service method, there are parts missing.

The complex type is a Meal object consisting of a few properties, an array of Recipes that are used in the meal, and an array of individual Food items. Each Recipe object in the array consists of a few properties and a "dictionary" of Food items as ingredients with the Food items as keys and their respective amounts as values. The Food object simply has properties. Behold the structure of the various objects in JavaScript:

function Meal() {

    this.MealNumber;
    this.MealName;
    this.MealType;
    this.Ratio;
    this.DailyCalorieTarget;
    this.Recipes = [];
    this.Foods = []
}

function Recipe() {

    this.RecipeNumber;
    this.RecipeName;
    this.RecipeInstructions;
    this.Foods = [];
}

function Food() {

    this.FoodNumber;
    this.FoodName;
    this.CaloriesPerGram;
    this.FatPerGram;
    this.CarbsPerGram;
    this.DietaryFiberPerGram;
    this.ProteinPerGram;
    this.Category;
}

Now, check out the corresponding objects in the Web Service:

Meal:

int MealNumber;
string MealName;
string MealType;
float Ratio;
int DailyCalorieTarget;

List<Recipe> Recipes;
List<KeyValuePair<Food, float>> Foods;

Recipe:

int RecipeNumber;
string RecipeName;
string RecipeInstructions;
List<KeyValuePair<Food, float>> Foods;

Food:

int FoodNumber;
string FoodName;
float CaloriesPerGram;
float FatPerGram;
float CarbsPerGram;
float DietaryFiberPerGram;
float ProteinPerGram;
string Category;

As you can see, the properties are the same. Some of you may be asking why I'm using a List of KeyValuePairs on the server-side, as opposed to a Dictionary object. The rest of you already know that Dictionary objects won't serialize into JSON strings and that a List of KeyValuePairs will.

Now, for the problem: I have two web methods in my web service - getMealData() and postMealData(). Here's what I'm doing for debugging purposes:

  1. When I click the Get Meal Data button in the client, I am making an AJAX request, using jQuery, to the getMealData() method. The getMealData() method populates the Meal object with some Recipe and Food data, uses the JavaScriptSerializer.Serializer method to serialize the Meal and sends the JSON string back to the client where the JSON.parse method parses the string into the meal object.

  2. After that, I click the Send Meal Data button, and JSON.stringify the EXACT SAME object that I just created from the getMealData() method using the following script:

    function postMealData() {

     var DTO = { 'meal': meal } $.ajax({ type: "POST", url: "KetoCompanionService.asmx/postMealObject", data: JSON.stringify(DTO), contentType: "application/json; charset=utf-8", dataType: "json", success: function (msg) { $('#returnedData').html(msg.d); }, error: function (msg) { $('#returnedData').html(msg.responseText); } }); 

    }

In between getting the meal data and sending it back to the server, I DO NOTHING TO IT. All that's going on here is that I'm getting JSON data from the server and parsing it into a JavaScript object. I'm stringifying the EXACT SAME object and sending it right back to the server. However, what I get on the server side doesn't match what I'm sending.

The JSON string that I get back from the getMealData method on the server-side:

{"MealNumber":1,"MealName":"Cheese Pizza, Strawberries, and Cream","MealType":"Dinner","Ratio":2.25,"DailyCalorieTarget":1600,"Recipes":[{"RecipeNumber":10,"RecipeName":"Cheese Pizza","RecipeInstructions":"Just fix the damned thing...","Foods":[{"Key":{"FoodNumber":1,"FoodName":"Eggs","CaloriesPerGram":1.432,"FatPerGram":0.823,"CarbsPerGram":0.234,"DietaryFiberPerGram":0,"ProteinPerGram":0.432,"Category":"Protein"},"Value":20},{"Key":{"FoodNumber":2,"FoodName":"Nuts","CaloriesPerGram":2.432,"FatPerGram":1.823,"CarbsPerGram":1.234,"DietaryFiberPerGram":1,"ProteinPerGram":1.432,"Category":"Protein"},"Value":10}]}],"Foods":[{"Key":{"FoodNumber":3,"FoodName":"Strawberries","CaloriesPerGram":0.332,"FatPerGram":0.723,"CarbsPerGram":0.034,"DietaryFiberPerGram":0.2,"ProteinPerGram":0.232,"Category":"Carbs"},"Value":120}]}

The string that I get back from the postMealData method, after it has been translated by ASP.NET AJAX into the Meal object on the server-side looks like this:

{"MealNumber":1,"MealName":"Cheese Pizza, Strawberries, and Cream","MealType":"Dinner","Ratio":2.25,"DailyCalorieTarget":1600,"Recipes":[],"Foods":[]}

In a nutshell, the Recipe and Food arrays are now empty. What happened to that data?? Naturally, I set breakpoints in the outgoing data from the client and in the incoming web method. The object is perfectly fine when it leaves but is missing the array data when it arrives.

I'm perfectly humble enough to admit that I could make a silly mistake in doing complex stringification. However, I didn't do this stringification -- ASP.NET and JavaScript did. All I did was relay the strings back and forth between them.

Is there something about serialization of Lists that I'm missing??

What in the HE-Double Hockey Sticks could be going on here? I'm pulling my hair out!

Thanks for taking the time to read such a long post.

Jeremy

EDITED TO ADD:

Client-side getMealData() code:

function getMealData() {
    $.ajax({
        type: "POST",
        url: "KetoCompanionService.asmx/getMealObject",
        data: "{}",
        contentType: "application/json; charset=utf-8",
        dataType: "json",
        success: function (msg) {

            meal = JSON.parse(msg.d);

            // Insert the returned HTML into the <div>.
            $('#returnedData').html(msg.d);
        },
        error: function (msg) {

            $('#returnedData').html(msg.responseText);
        }
    });
}

Server-side getMealDataMethod:

[WebMethod]
[ScriptMethod(UseHttpGet=false, ResponseFormat = ResponseFormat.Json)]
public string getMealObject() {

    JavaScriptSerializer js = new JavaScriptSerializer();

    _meal = new Meal();
    _meal.MealNumber = 1;
    _meal.MealName = "Cheese Pizza, Strawberries, and Cream";
    _meal.MealType = "Dinner";
    _meal.Ratio = 2.25f;
    _meal.DailyCalorieTarget = 1600;

    Recipe _recipe1 = new Recipe();
    _recipe1.RecipeNumber = 10;
    _recipe1.RecipeName = "Cheese Pizza";
    _recipe1.RecipeInstructions = "Just fix the damned thing...";

     Food _recipe1Food1 = new Food();
    _recipe1Food1.FoodNumber = 1;
    _recipe1Food1.FoodName = "Eggs";
    _recipe1Food1.CaloriesPerGram = 1.432f;
    _recipe1Food1.FatPerGram = 0.823f;
    _recipe1Food1.CarbsPerGram = 0.234f;
    _recipe1Food1.DietaryFiberPerGram = 0.0f;
    _recipe1Food1.ProteinPerGram = 0.432f;
    _recipe1Food1.Category = "Protein";

    KeyValuePair<Food, float> _kvp1 = new KeyValuePair<Food, float>(_recipe1Food1, 20.0f);
    _recipe1.Foods.Add(_kvp1);

    Food _recipe1Food2 = new Food();
    _recipe1Food2.FoodNumber = 2;
    _recipe1Food2.FoodName = "Nuts";
    _recipe1Food2.CaloriesPerGram = 2.432f;
    _recipe1Food2.FatPerGram = 1.823f;
    _recipe1Food2.CarbsPerGram = 1.234f;
    _recipe1Food2.DietaryFiberPerGram = 1.0f;
    _recipe1Food2.ProteinPerGram = 1.432f;
    _recipe1Food2.Category = "Protein";

    KeyValuePair<Food, float> _kvp2 = new KeyValuePair<Food, float>(_recipe1Food2, 10.0f);
    _recipe1.Foods.Add(_kvp2);

    _meal.Recipes.Add(_recipe1);

    Food _mealFood1 = new Food();
    _mealFood1.FoodNumber = 3;
    _mealFood1.FoodName = "Strawberries";
    _mealFood1.CaloriesPerGram = 0.332f;
    _mealFood1.FatPerGram = 0.723f;
    _mealFood1.CarbsPerGram = 0.034f;
    _mealFood1.DietaryFiberPerGram = 0.2f;
    _mealFood1.ProteinPerGram = 0.232f;
    _mealFood1.Category = "Carbs";

    KeyValuePair<Food, float> _kvp3 = new KeyValuePair<Food, float>(_mealFood1, 120.0f);
    _meal.Foods.Add(_kvp3);

    string returnString = js.Serialize(_meal);

    return returnString;
}

UPDATE:

In my getMealData() JavaScript function, rather than parsing the returned JSON data from the server and storing it in meal object, I simply stored the raw string.

Then, in my postMealData() JavaScript function, rather than stringifying the meal object that would've been created, I did my own stringifying using the raw text:

var DTO = "{ \"meal\" : " + meal + "}";

Then, I sent that DTO string as my data: property in the AJAX request.

I got the same result. It's almost as if ASP.NET used a different algorithm to serialize the meal object than they did to deserialize it. Even the raw JSON string that ASP.NET sent me doesn't work when I send it back.

Solution - Part 1

I discovered why my Recipe and Foods arrays were empty - every public property that I created for each Meal, Recipe, and Food class in ASP.NET had both a getter and a setter -- except for the List. I had neglected to place a setter in that property, which made it read-only. As such, the web method didn't have access to those properties to set them.

However, I'm not out of the woods, yet. Even though I've found the missing data, my Foods arrays are coming up { Key : null, Value : 0 } at the server-side, even though I'm sending food data key-value pairs over.

Solution - Part 2

I have fixed the issue.

Apparently, ASP.NET AJAX has a problem with deserializing JSON when it comes to Lists of KeyValuePair objects.

So, instead of using the type, List<KeyValuePair<Food, float>> , to store my individual food items, I created my own class I call FoodWithAmount with the properties FoodItem and Amount and instead of using this: List<KeyValuePair<Food, float>> , I'm using this: List<FoodWithAmount> . The server side translated the JSON string into the meal object, perfectly, once I implemented this change.

public class FoodWithAmount
{
    Food _foodItem;
    float _amount;

    public Food FoodItem
    {
        get { return _foodItem; }
        set { _foodItem = value; }
    }

    public float Amount
    {
        get { return _amount; }
        set { _amount = value; }
    }

    //Parameterless constructor is necessary for serialization
    public FoodWithAmount()
    {
    }

    public FoodWithAmount(Food foodItem, float amount)
    {
        _foodItem = foodItem;
        _amount = amount;
    }
}

Out of all the obstacles I have encountered while learning JavaScript, ASP.NET, jQuery, HTML, and CSS over the past few weeks, this problem has consumed more of my time and thought than any other.

I hope that my suffering makes someone else's journey a bit easier.

Happy coding!!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM