2
votes

I'm trying to write a javascript OData consumer that will take several of my entities at once for a POST (so I can submit an entity and associated children at once) to my WebApi server. However I get an annoying error:

ExceptionMessage: "The message header 'POST /odata/MyEntity HTTP/1.1' is invalid. The header value must be of the format '<header name>: <header value>'."
ExceptionType: "Microsoft.Data.OData.ODataException"
Message: "An error has occurred."
StackTrace: "   at Microsoft.Data.OData.ODataBatchReaderStream.ValidateHeaderLine(String headerLine, String& headerName, String& headerValue)
↵   at Microsoft.Data.OData.ODataBatchReaderStream.ReadHeaders()
↵   at Microsoft.Data.OData.ODataBatchReaderStream.ProcessPartHeader()
↵   at Microsoft.Data.OData.ODataBatchReader.SkipToNextPartAndReadHeaders()
↵   at Microsoft.Data.OData.ODataBatchReader.ReadImplementation()
↵   at Microsoft.Data.OData.ODataBatchReader.ReadSynchronously()
↵   at Microsoft.Data.OData.ODataBatchReader.InterceptException[T](Func`1 action)
↵   at Microsoft.Data.OData.ODataBatchReader.Read()
↵   at System.Web.Http.OData.Batch.DefaultODataBatchHandler.<ParseBatchRequestsAsync>d__e.MoveNext()
↵--- End of stack trace from previous location where exception was thrown ---
↵   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
↵   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
↵   at System.Web.Http.OData.Batch.DefaultODataBatchHandler.<ProcessBatchAsync>d__0.MoveNext()
↵--- End of stack trace from previous location where exception was thrown ---
↵   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
↵   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
↵   at System.Web.Http.Batch.HttpBatchHandler.<SendAsync>d__0.MoveNext()"

My function to build this is as follows:

var oReq = new XMLHttpRequest();
oReq.onload = function () {
    if (oReq.status == 404) {
        defer.reject(oReq.statusText);
    } else {
        var response = JSON.parse(oReq.response);
        if (response['odata.error']) {
            defer.reject(oReq['odata.error']);
        } else if (response.Message) {
            defer.reject(response.Message);
        } else {
            defer.resolve(response);
        }
    }
};
oReq.onerror = function () {
    defer.reject(oReq.statusText);
};
oReq.open("POST", "/odata/$batch", true);
var batch = "batch_" + newGuid();
oReq.setRequestHeader("Content-type", 'multipart/mixed; boundary="' + batch + '"');

var body = "--" + batch + "\r\n";
ko.utils.arrayForEach(entities, function (entity) {
    body = body + [
        'Content-Type: application/http; msgtype=request',
        '',
        'POST ' + url + ' HTTP/1.1',
        'Content-Type: application/json; charset=utf-8',
        '',
        ko.toJSON(entity),
        "--" + batch
    ].join('\r\n')
});

oReq.send(body + "--");

Have I not formatted the request properly? I've been trying to emulate this:

http://blogs.msdn.com/b/webdev/archive/2013/11/01/introducing-batch-support-in-web-api-and-web-api-odata.aspx

Thanks.

2
Do you have a sample http request? It could be a server issue, Or it could be caused by invalid request. - Maya

2 Answers

4
votes

I've sort of got this to work. I read through someone elses code (https://github.com/volpav/batchjs) and made my own implementation (since I didn't want to use JQuery).

export interface ODataChangeRequest {
    requestUri: string;
    method: string;
    data: string;
    resolve: (response?: Object) => void;
    reject: (response?: Object) => void;
}


export function packBatch(requests: ODataChangeRequest[], boundary: string): string {
    var body = [];

    body.push('--' + boundary);
    var changeSet = 'changeset_' + newGuid();
    body.push('Content-type: multipart/mixed; boundary=' + changeSet, '');

    ko.utils.arrayForEach(requests, (d) => {
        var t = d.method.toUpperCase();

        body.push('--' + changeSet);
        body.push('Content-Type: application/http', 'Content-Transfer-Encoding: binary', '');

        body.push(t + ' ' + d.requestUri + ' ' + httpVersion);
        body.push('Host: ' + location.host);

        if (d.data) {
            body.push('Content-Type: application/json; charset=utf-8');
            body.push('', d.data, '');
        }
    });

    body.push('--' + changeSet + '--', '');

    body.push('--' + boundary + '--', '');

    return body.join('\r\n');
}

export function unpackBatch(requests: ODataChangeRequest[], xhr: XMLHttpRequest): boolean {
    var lines = xhr.responseText.split('\r\n'),
        i = 0,
        data = [],
        feedingData = false,
        statusCode = null,
        allSuccessful = true;

    ko.utils.arrayForEach(lines, (l) => {
        if (!l.length) {
            return;
        }

        var currentRequest = requests[i];
        if (!currentRequest) {
            return;
        }

        var httpIndex = l.indexOf(httpVersion);
        if (httpIndex != -1) {
            // Hadn't gotten data for previous request. Close
            if (statusCode) {
                if (statusCode == '200') {
                    currentRequest.resolve();
                } else {
                    currentRequest.reject();
                }
            }

            var startIndex = httpIndex + httpVersion.length + 1;
            statusCode = l.substring(startIndex, startIndex + 3);
            if (statusCode != '200') {
                allSuccessful = false;
            }
        }

        if (statusCode) {
            if (l == "{") {
                feedingData = true;
            }
            if (feedingData) {
                data.push(l);
                if (l == "}") {
                    var dataObj = data.length ? JSON.parse(data.join(" ")) : null;
                    if (statusCode == '200') {
                        currentRequest.resolve(dataObj);
                    } else {
                        currentRequest.reject(dataObj);
                    }

                    i++;
                    data = [];
                    feedingData = false;
                    statusCode = null;
                }
            }
        }
    });

    return allSuccessful;
}
0
votes

I think you can create an odata clint first with 'Add Reference'. And use Fiddler to inspect the payloads of the requests that the client sends. And, you can do the same thing in your AJAX.