2

First of all, I'm a new developer, so I apologize in advance if I'm missing something obvious.

I'm developing a web application to work offline with large amounts of data in an IndexedDB. When the user goes to the webapp, the client grabs the entire database from the server and stores it for use in the indexeddb. That works fine, but when I'm trying to use a post method to send the data (again multiple records) back to WCF, I get method not allowed or bad request when trying to send an ajax body parameter, and when I do use uri parameters, it hits the server, but not all the data is sent. I thought perhaps invalid characters may be a factor so I used the encodeURIComponent method in javascript to convert invalid characters to be valid in a uri parameter. I've also tried compressing the data with a javascript compression api called LZString. I've tried using XMLHttpRequest(which I don't fully understand). This webapp has to work offline so I can't make a server call except for initially getting data when the client first opens and for syncing data back to the server, which is why I have to send large amounts of data at a time.

I'm also using an IndexedDB wrapper called Dexie.js.

Samples of my code is below. Some code is commented, but is left to show what I've tried.

This is what I have on the server..

    [OperationContract]
    [WebInvoke(Method = "POST",
        RequestFormat = WebMessageFormat.Json,
        ResponseFormat = WebMessageFormat.Json,
        UriTemplate = "REST_SendCompletedServiceOrders",
        BodyStyle = WebMessageBodyStyle.Wrapped)]
    [FaultContract(typeof (Exception))]
    bool REST_SendCompletedServiceOrders(string compressedWebData);

This is the click event on the client used to sync back..

$('#syncCompletedData').on('click', function() {

    db.ServiceOrder

        .toArray(function(so) {
            var completedServiceOrders = [];
            for (var i = 0; i < so.length; i++) {
                if (so[i].IsCompleted) {
                    completedServiceOrders.push(so[i]);
                };
            }
            var customerId = sessionStorage.getItem("customerId");
            var companyId = sessionStorage.getItem("companyId");
            var computerId = sessionStorage.getItem("computerId");
            var webData = JSON.stringify({ webCustomerId: customerId, webCompanyId: companyId, webComputerId: computerId, webServiceOrder: completedServiceOrders });
            alert(webData);

            alert("before compression is " + webData.length);

            var URIEncodedWebData = encodeURIComponent(webData);
            var JSONWebData = JSON.stringify(URIEncodedWebData);

        var compressedWebData = LZString.compressToUTF16(JSONWebData);

            alert("after compression is " + compressedWebData.length);
            debugger;

            try {
                $.ajax({
                    type: "POST",
                    url: "MFSRemoteDataService/REST_SendCompletedServiceOrders",
                    contentType: "application/json; charset=utf-8",
                    dataType: "json",
                    data: { compressedWebData: compressedWebData },
                    success: function(data) { alert(JSON.stringify(data)); },
                    failure: function(errMsg) {
                        alert(errMsg);
                    }
                });
            } catch (e) {
                alert(e);
            }

        });
});

Before compression data length is 7707. After compression data length is 1831.

Thanks in advance for any help, feedback, criticism, etc..

Jason Clark
  • 59
  • 1
  • 7

2 Answers2

2

In the shown snippet, you are composing the ajax data for use in a get, which usually means you prepare a uri. However, since he is both using post and ajax, the information will be sent in the post request body and as such does not need to be encoded.

The encoding is bloating the stringified json. You can stop at webdata and post that all by itself, remove the dataType parameter in the ajax options, switch to using traditional:true in the ajax options, and it should all properly model bind.

It is hard to tell what your server side view model looks like, but if the accepting parameter is named compressedWebData (names must be exact, same goes with structure), then it would probably work like this

//code as shown in OP
//replace var webData = with the following
var compressedWebData = { webCustomerId: customerId, webCompanyId: companyId, webComputerId: computerId, webServiceOrder: completedServiceOrders };

try {
     $.ajax({
         type: "POST",
         url: "MFSRemoteDataService/REST_SendCompletedServiceOrders",
         contentType: "application/json",
         data: JSON.stringify(compressedWebData),
         traditional:true,
         success: function(data) { alert(JSON.stringify(data)); },
         failure: function(errMsg) {
             alert(errMsg);
         }
    });
} catch (e) {
   alert(e);
}
Travis J
  • 81,153
  • 41
  • 202
  • 273
  • Thanks for your answer. I was originally trying to use the body parameter, but nothing seemed to be happening on the server (no break points being hit) and I was getting a 400 error back. Since I was getting to the server with uri parameters, I thought that was the route to take, which is why I was using the uri encoder because the data I was sending was being truncated on the server. I need to figure out why I'm getting the 400 error because I really do need to send the data through the body. – Jason Clark Mar 25 '15 at 20:15
  • @JasonClark - If you set a breakpoint on one of your filters, is it being hit on the ajax request? – Travis J Mar 25 '15 at 20:17
  • @JasonClark - Are there invalid characters in your completedServiceOrders ? Can you post a sort of watered down version of the json? – Travis J Mar 25 '15 at 20:19
  • This is an example of the JSON, which does have # in it, which is an invalid uri character {"webCustomerId":"10020","webCompanyId":"1","webComputerId":"2","webServiceOrder":[{"AccountID":3828,"AccountName":"Fred's Store #3706","City":"AnyTown", – Jason Clark Mar 25 '15 at 20:29
  • Also, after changing to use the body parameter, i'm now hitting the server, but the parameter is null on the server side – Jason Clark Mar 25 '15 at 20:31
  • Do I still need a parameter in the uri template if i'm passing a body parameter? I don't understand why this is null when it has data when it leaves the client – Jason Clark Mar 25 '15 at 20:37
  • @JasonClark - The names must match exactly, and I notice that I believe the names do not match in my example with regards to your exact situation. The mismatch could potentially be what is causing the issue, please see my edit for an updated naming convention. – Travis J Mar 25 '15 at 21:01
  • I'm sorry I should have mentioned that. I already changed the parameter name in the server to match the body parameter name. bool REST_SendCompletedServiceOrders(string webData); .. It's still null – Jason Clark Mar 25 '15 at 21:23
  • @JasonClark - In the json you pasted above, I found a hidden character after webServiceOrder. Perhaps you should manually retype that section to see if the hidden character affected the serialization. http://i.imgur.com/wlqDrEL.png – Travis J Mar 25 '15 at 21:38
  • The reason for the hidden character is because I pasted it directly to the comments. This is the full JSON string I'm currently trying to post.. [here](http://textuploader.com/xjfn) – Jason Clark Mar 26 '15 at 13:42
  • @JasonClark - The JSON you provide shows an error. A good way to check these is to clean them up at jsbeautifier.org and then check them at jslint.com . The error in your json is here: `"FinalUsage"...9,`. If there are errors in the posted json, the binding will be null. Is it possible your original usage was correct and that the actual json being used was somehow malformed? Or was this just a typo from watering down the structure? – Travis J Mar 26 '15 at 14:50
  • The JSON copied incorrectly. I used a prompt method to allow me to copy it, and that obviously didn't work. This one should be correct. I used the Chrome console to get it this time. [JSON string](http://textuploader.com/xjzm) – Jason Clark Mar 26 '15 at 15:22
  • I also tested the JSON string at [JSONLint.com](http://jsonlint.com/) and it returned Valid JSON. – Jason Clark Mar 26 '15 at 16:07
  • @JasonClark - That does look valid. And all of the front end work seems to be okay with that json, including the length - which is only 11kb even uncompressed or encoded. I wonder if the UriTemplate needs to be able to match the complex structure, or if simply having it be the name of the route is enough. – Travis J Mar 26 '15 at 17:28
0

I figured out my problem. I've been trying to pass a string to the contract method, and I kept getting bad request errors. Instead, I wrapped the Json string and sent it to an object instead of a string that I created on the server.

I wrapped the JSON and sent it in the body of the ajax request..

var rawWebData = {
            WebCustomerID: customerId,
            WebCompanyID: companyId,
            WebComputerID: computerId,
            WebServiceOrders: completedServiceOrders
        };
        var rawData = { webData: rawWebData };
        var webData = JSON.stringify(rawData);
            try {
                $.ajax({
                    type: "POST",
                    url: "MFSRemoteDataService/REST_SendCompletedServiceOrders",
                    contentType: "application/json; charset=utf-8",
                    dataType: "json",
                    traditional: true,
                    data: webData,
                    success: function (data) {
                        alert(JSON.stringify(data));
                    },
                    failure: function (errMsg) {
                        alert(errMsg);
                    }
                });
            } catch (e) {
                alert(e);
            }

        });

Then I created a class to collect the data...

[DataContract]
public class WebServiceOrder
{
    [DataMember]
    public Int32 WebCustomerID { get; set; }

    [DataMember]
    public Int32 WebCompanyID { get; set; }

    [DataMember]
    public Int32 WebComputerID { get; set; }

    [DataMember]
    public virtual List<ServiceOrder> WebServiceOrders { get; set; }

}

Then I changed the contract method to accept the object I created instead of a string. WCF deserialized the JSON string.

        [OperationContract]
    [WebInvoke(Method = "POST",
        RequestFormat = WebMessageFormat.Json,
        ResponseFormat = WebMessageFormat.Json,
        UriTemplate = "REST_SendCompletedServiceOrders",
        BodyStyle = WebMessageBodyStyle.WrappedRequest)]
    [FaultContract(typeof (Exception))]
    bool REST_SendCompletedServiceOrders(WebServiceOrder webData);
Jason Clark
  • 59
  • 1
  • 7