Jason Clark
Jason Clark

Reputation: 59

Bad Request Errors when trying to Post large JSON data

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..

Upvotes: 2

Views: 3386

Answers (2)

Jason Clark
Jason Clark

Reputation: 59

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);

Upvotes: 0

Travis J
Travis J

Reputation: 82277

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);
}

Upvotes: 2

Related Questions