BreezeJS in LightSwitch over Odata: fixing concurrency values and server calculated fields (PART 3/3)

Introduction

In my last post regarding BreezeJS and LightSwitch I expressed some reluctance and some fear that I might missed important stuff.

The bad news is that I was right, I missed things,  the good news is that there is a solution.

 

BreezeJS and Odata

The BreezeJS documentation states in a way that when using Odata, you are on your own when it comes to concurrency management and server calculated values.

The way how BreezeJS formulates a batched POST request, makes that the LightSwich server does not return the full data from the server.

An F12 trace can clarify. let’s first examine the working case when using LightSwitch.

Odata in LightSwitch

I’m showing here a simple update request of a Customer record containing 2 fields: FirstName and LastName. The FirstName field is patched on the server in following way.

public partial class ApplicationDataService
{
partial void Customers_Updating(Customer entity)
{
entity.FirstName = entity.FirstName + " server calculation";
}
}

The F12 dev tools prove this:As you probably know, LightSwitch handles this correctly, since the LightSwitch server side engine returns the full payload of data back to the client where it is merged into the data workspace.

The  body of the post request

–batch_8a89-e677-5051
Content-Type: multipart/mixed; boundary=changeset_e1c0-1326-576f–changeset_e1c0-1326-576f
Content-Type: application/http
Content-Transfer-Encoding: binaryMERGE Customers(1003) HTTP/1.1
Content-ID: 0
DataServiceVersion: 3.0
If-Match: W/”X’0000000000002724′”
Prefer: return-content
Accept: application/atomsvc+xml;q=0.8, application/json;odata=fullmetadata;q=0.7, application/json;q=0.5, */*;q=0.1
Content-Type: application/json;odata=verbose
MaxDataServiceVersion: 3.0{“LastName”:”bladel”}
–changeset_e1c0-1326-576f–

–batch_8a89-e677-5051–

The body of the response

–batchresponse_ebbffd58-3f48-44da-a4d6-497875620ca0
Content-Type: multipart/mixed; boundary=changesetresponse_11986cc3-8666-46e9-bf5f-6482f162469b–changesetresponse_11986cc3-8666-46e9-bf5f-6482f162469b
Content-Type: application/http
Content-Transfer-Encoding: binaryHTTP/1.1 200 OK
DataServiceVersion: 3.0;
Content-Type: application/json;odata=fullmetadata;streaming=true;charset=utf-8
Content-ID: 0
X-Content-Type-Options: nosniff
Cache-Control: no-cache
Preference-Applied: return-content
ETag: W/”X’0000000000002725′”{“odata.metadata”:”http://localhost:23617/ApplicationData.svc/$metadata#Customers/@Element”,
“odata.type”:”LightSwitchApplication.Customer”,
“odata.id”:”http://localhost:23617/ApplicationData.svc/Customers(1003)”,
“odata.etag”:”W/”X’0000000000002725′””,”odata.editLink”:”Customers(1003)”,”Id”:1003,”LastName”:”bladel”,
“FirstName”:”paul server calculation”,”CreatedBy”:”TestUser”,
“Created@odata.type”:”Edm.DateTimeOffset”,”Created”:”2014-09-25T07:25:19.1300663Z”,
“ModifiedBy”:”TestUser”,”Modified@odata.type”:”Edm.DateTimeOffset”,
“Modified”:”2014-09-25T07:26:13.8130025Z”,”RowVersion@odata.type”:”Edm.Binary”,”RowVersion”:”AAAAAAAAJyU=”}
–changesetresponse_11986cc3-8666-46e9-bf5f-6482f162469b–
–batchresponse_ebbffd58-3f48-44da-a4d6-497875620ca0–

As you can see the patched first name is there and also the fields required for concurrency management.

 

Thank you LightSwitch !

 

Odata in Breeze

Let’s now do the same with breezeJS  towards the LightSwitch back-end.

The  body of the post request

–batch_c4f2-b918-1740
Content-Type: multipart/mixed; boundary=changeset_504a-5d01-2140–changeset_504a-5d01-2140
Content-Type: application/http
Content-Transfer-Encoding: binaryMERGE http://localhost:23617/ApplicationData.svc/Customers(1004) HTTP/1.1
Content-ID: 1
DataServiceVersion: 2.0
If-Match: W/”X’0000000000002728′”
Accept: application/atomsvc+xml;q=0.8, application/json;odata=fullmetadata;q=0.7, application/json;q=0.5, */*;q=0.1
Content-Type: application/json
MaxDataServiceVersion: 3.0{“LastName”:”bladel”}
–changeset_504a-5d01-2140–

–batch_c4f2-b918-1740–

The body of the response

–batchresponse_36c1d1a4-6c64-4e6a-ac80-2d819eed9494
Content-Type: multipart/mixed; boundary=changesetresponse_ba2058a3-eb0b-4c6f-b79b-4026eb0b1167–changesetresponse_ba2058a3-eb0b-4c6f-b79b-4026eb0b1167
Content-Type: application/http
Content-Transfer-Encoding: binaryHTTP/1.1 204 No Content
Content-ID: 1
X-Content-Type-Options: nosniff
Cache-Control: no-cache
DataServiceVersion: 1.0;
ETag: W/”X’0000000000002729′”–changesetresponse_ba2058a3-eb0b-4c6f-b79b-4026eb0b1167–
–batchresponse_36c1d1a4-6c64-4e6a-ac80-2d819eed9494–

As you can see (I hope?), there is no longer response data ! So, the angular client (using BreezeJS) can not update the data with updates done on the server and the concurrency management will simply fail !.

What are the missing pieces?

Well, since we provided above the full traces, it’s quite easy to find out that LightSwitch is giving an additional line in the request body:

Prefer: return-contentrn

Also, the DataServiceVersion in the body is set to 3 instead of 2.

 

The Fix

Lucikly we have already an entityManagerFactory, which you might recall from the previous article in this BreezeJS series. Let’s include the new functionality:

(function () {
    var entityManagerFactory = function (breeze) {
        breeze.NamingConvention.camelCase.setAsDefault();
        breeze.config.initializeAdapterInstance('dataService', 'OData', true);

        var oldClient = OData.defaultHttpClient;

        var myClient = {
            request: function (request, success, error) {
                if (request.requestUri.indexOf("$metadata", request.requestUri.length - "$metadata".length) !== -1) {
                    request.headers.Accept = "application/xml";
                }
                else {
                    if (request.method === "POST") {
                        var body = request.body;
                        request.body = replaceAll(body, "rnDataServiceVersion: 2.0rn", "rnDataServiceVersion: 3.0rnPrefer: return-contentrn");
                    }
                }
                return oldClient.request(request, success, error);
            }
        };

        OData.defaultHttpClient = myClient;

        var serviceName = "/ApplicationData.svc/";

        var factory = {
            Manager: function () {
                return new breeze.EntityManager(serviceName);
            },
            serviceName: serviceName
        };

        return factory;
    };

    angular.module('app')
    .factory('lightSwitchEntityManagerFactory', ['breeze', entityManagerFactory]);

    function escapeRegExp(string) {
        return string.replace(/([.*+?^=!:${}()|[]/])/g, "$1");
    };

    function replaceAll(string, find, replace) {
        return string.replace(new RegExp(escapeRegExp(find), 'g'), replace);
    }

}());

Basically, we are using a regular expression for doing a replace all in the body of the request, but only when it’s a POST request:

if (request.method === "POST") {
                        var body = request.body;
                        request.body = replaceAll(body, "rnDataServiceVersion: 2.0rn", "rnDataServiceVersion: 3.0rnPrefer: return-contentrn");
                    }

 

conclusion

Hopefully, that was the last fix for getting Odata working from an angularJS application using BreezeJS towards a LightSwitch back-end.

On the other hand, BreezeJS proves to be really a great library !