Post batch request with Breezejs - odata

I've been trying to POST an entity using Breezejs and WebAPI OData Controllers.
Here are the configurations:
config.Routes.MapODataRoute(
routeName: "odata",
routePrefix: "odata",
model: model,
batchHandler: new DefaultODataBatchHandler(GlobalConfiguration.DefaultServer));
Where the model is very straight forward:
public class ServiceMetadata
{
public int ServiceMetadataId { get; set; }
public string ServiceName { get; set; }
public string Description { get; set; }
public ObjectState? State { get; set; }
public DateTime? LastUpdated { get; set; }
}
And it is mapped through the default:
ODataModelBuilder modelBuilder = new ODataConventionModelBuilder();
The Client is also very simple taken using AngularJs and partially from the Todo example: http://www.breezejs.com/samples/todo-angular
breeze.config.initializeAdapterInstance("modelLibrary", "backingStore", true);
var serviceName = 'http://localhost:8081/odata/';
breeze.config.initializeAdapterInstances({ dataService: "OData" });
var manager = new breeze.EntityManager(serviceName);
manager.enableSaveQueuing(true);
The actual Posting is done using the default createEntity() method:
function createServiceMetadata(initialValues) {
return manager.createEntity('ServiceMetadata', initialValues);
}
And the whole thing looks like:
serviceMetadatas.createServiceMetadata({
ServiceName: $scope.newServiceName,
Description: $scope.newServiceDescription
});
serviceMetadatas.saveChanges();
However, the request is not being transferred to the correct controller (ServiceMetadatasController which inherits from EntitySetController), or any other controller for that matter.
The HTTP request looks like this:
POST http://localhost:8081/odata/$batch HTTP/1.1
Host: localhost:8081
User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64; rv:23.0) Gecko/20100101 Firefox/23.0
Accept: multipart/mixed
Accept-Language: he-IL,he;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
DataServiceVersion: 2.0
Content-Type: multipart/mixed; charset=UTF-8;boundary=batch_4f09-d7cf-dd99
MaxDataServiceVersion: 2.0
Referer: http://localhost:9000/
Content-Length: 580
Origin: http://localhost:9000
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
--batch_4f09-d7cf-dd99
Content-Type: multipart/mixed; boundary=changeset_ca0c-06b7-ddbe
--changeset_ca0c-06b7-ddbe
Content-Type: application/http
Content-Transfer-Encoding: binary
POST ServiceMetadatas HTTP/1.1
Content-ID: 1
DataServiceVersion: 2.0
Accept: application/atomsvc+xml;q=0.8, application/json;odata=verbose;q=0.5, */*;q=0.1
Content-Type: application/json;odata=verbose
MaxDataServiceVersion: 2.0
{"ServiceMetadataId":-1,"ServiceName":"sdf sdf","Description":"sd fgs df","LastUpdated":null}
--changeset_ca0c-06b7-ddbe--
--batch_4f09-d7cf-dd99--
And the response:
HTTP/1.1 202 Accepted
Cache-Control: no-cache
Pragma: no-cache
Content-Type: multipart/mixed; boundary=batchresponse_966d4460-e00e-4900-b1c9-85b17081cfac
Expires: -1
Server: Microsoft-IIS/8.0
Access-Control-Allow-Origin: http://localhost:9000
Access-Control-Allow-Credentials: true
DataServiceVersion: 2.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNcVG9tZXJcRG9jdW1lbnRzXFZpc3VhbCBTdHVkaW8gMjAxMlxQcm9qZWN0c1xFYXN5Qml6eVxFYXN5Qml6eS5XZWJBUElcb2RhdGFcJGJhdGNo?=
X-Powered-By: ASP.NET
Date: Sun, 15 Sep 2013 14:32:39 GMT
Content-Length: 443
--batchresponse_966d4460-e00e-4900-b1c9-85b17081cfac
Content-Type: multipart/mixed; boundary=changesetresponse_44da5dcf-877d-4041-a82b-c51d06a4e9a4
--changesetresponse_44da5dcf-877d-4041-a82b-c51d06a4e9a4
Content-Type: application/http
Content-Transfer-Encoding: binary
HTTP/1.1 406 Not Acceptable
Content-ID: 1
--changesetresponse_44da5dcf-877d-4041-a82b-c51d06a4e9a4--
--batchresponse_966d4460-e00e-4900-b1c9-85b17081cfac--
Any idea what the hack is going on?
B.T.W GET requests works great.
P.S.
After looking at couple of demos, I though the using BreezeJS will be straight forward considering WebApi and OData.
I must say it is Far from being easy to configure this JS library. I hope it will turn out to be hard-to-setup but easy-to-use.
Thanks.
#UPDATE See Javier's great answer!!
In after digging allllot on the breeze code, I came to realize that the problem is laying deep in the createChangeRequests() of breezejs, right here:
request.requestUri = entity.entityType.defaultResourceName;
Where for some reason the defaultResouceName, completely ignores the path to this entity.
Long story short, the following is a hack to resolve:
manager.metadataStore.getEntityType(ENTITY_TYPE).setProperties({defaultResourceName: THE_MISSING_PART_FROM_THE_URL + ENTITY_TYPE});
manager.createEntity(ENTITY_TYPE, values);
Not very nice, but still works!

New answer is .. use webApiOData data service instead of "OData":
breeze.config.initializeAdapterInstances({
dataService : 'webApiOData'
});

The problem is in the url of the inner request. The url needs to be relative to the host. Let's say your service is hosted in host/service (in our case, service will be the equivalent as the odata prefix), so normally you send requests like host/service/Customers or /service/Customers.
When you issue a batch request, the urls in the inner requests might be absolute or relative to the host. The problem is that in your request, the url is ServiceMetadatas which is relative to the service root, not the host.
Web API is interpreting the relative url as host/ServiceMetadatas instead of as host/service/ServiceMetadatas and that's what causes the error.
Based on your repro project, the following request works fine:
POST http://localhost:6974/odata/$batch HTTP/1.1
Host: localhost:6974
User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64; rv:23.0) Gecko/20100101 Firefox/23.0
Accept: multipart/mixed
Accept-Language: he-IL,he;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
DataServiceVersion: 2.0
Content-Type: multipart/mixed; charset=UTF-8;boundary=batch_4f09-d7cf-dd99
MaxDataServiceVersion: 2.0
Referer: http://localhost:9000/
Content-Length: 565
Origin: http://localhost:9000
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
--batch_4f09-d7cf-dd99
Content-Type: multipart/mixed; boundary=changeset_ca0c-06b7-ddbe
--changeset_ca0c-06b7-ddbe
Content-Type: application/http
Content-Transfer-Encoding: binary
POST odata/ServiceMetadatas HTTP/1.1
Content-ID: 1
DataServiceVersion: 2.0
Accept: application/atomsvc+xml;q=0.8, application/json;odata=verbose;q=0.5, */*;q=0.1
Content-Type: application/json;odata=verbose
MaxDataServiceVersion: 2.0
{"ServiceMetadataId":-1,"ServiceName":"sdf sdf","Description":"sd fgs df"}
--changeset_ca0c-06b7-ddbe--
--batch_4f09-d7cf-dd99--
The associated response is the following one:
HTTP/1.1 202 Accepted
Cache-Control: no-cache
Pragma: no-cache
Content-Type: multipart/mixed; boundary=batchresponse_6779b5e5-6e40-4363-9a98-5a33d062da28
Expires: -1
Server: Microsoft-IIS/8.0
DataServiceVersion: 2.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNcamFjYWx2YXJcRG93bmxvYWRzXE9EYXRhQmF0Y2gtbWFzdGVyXENsZWFuV2ViQXBpUHJvamVjdFxvZGF0YVwkYmF0Y2g=?=
X-Powered-By: ASP.NET
Date: Tue, 17 Sep 2013 16:48:50 GMT
Content-Length: 872
--batchresponse_6779b5e5-6e40-4363-9a98-5a33d062da28
Content-Type: multipart/mixed; boundary=changesetresponse_b63ca946-ce66-43e6-a78f-d44a5b8f2d5c
--changesetresponse_b63ca946-ce66-43e6-a78f-d44a5b8f2d5c
Content-Type: application/http
Content-Transfer-Encoding: binary
HTTP/1.1 201 Created
Location: http://localhost:6974/odata/ServiceMetadatas(-1)
Content-ID: 1
Content-Type: application/json; odata=verbose; charset=utf-8
DataServiceVersion: 2.0
{
"d":{
"__metadata":{
"id":"http://localhost:6974/odata/ServiceMetadatas(-1)","uri":"http://localhost:6974/odata/ServiceMetadatas(-1)","type":"CleanWebApiProject.Models.ServiceMetadata"
},"ServiceMetadataId":-1,"ServiceName":"sdf sdf","Description":"sd fgs df"
}
}
--changesetresponse_b63ca946-ce66-43e6-a78f-d44a5b8f2d5c--
--batchresponse_6779b5e5-6e40-4363-9a98-5a33d062da28--
The only change that I made in the controller is the following (and not related to batch):
public class ServiceMetadatasController : EntitySetController<ServiceMetadata, int>
{
protected override ServiceMetadata CreateEntity(ServiceMetadata entity)
{
return entity;
}
protected override int GetKey(ServiceMetadata entity)
{
return entity.ServiceMetadataId;
}
public override IQueryable<ServiceMetadata> Get()
{
return new List<ServiceMetadata>
{
new ServiceMetadata() {ServiceName = "Service1", Description = "Desc1"},
new ServiceMetadata() {ServiceName = "Service2", Description = "Desc1"}
}.AsQueryable();
}
}
I hope this solves your problem, also let me know if you are generating the url for the inner request manually or if it's breezejs doing it for you, so that I can follow up and make sure it gets fixed.

Not sure if this is your issue but Microsoft's ODataModelBuilder does not implement a complete OData model. In particular, it doesn't generate foreign key constraints. MS is aware of this and is planning to update it in a later release. Until then you are better off using WCF data services to create your OData endpoints.

Related

Angular HTTP Post using CORS works behind the scene, but doesn't "succeed"

I'm working on an AngularJS project, and I need to perform a cross-domain (CORS) POST.
It took me a (long) while to get it working, and well, it now sort of work:
The pre flight (OPTIONS) request is sent correctly, and my server
responds to it correctly as well.
The actual POST request is sent
correctly as well, my server receives it all right, and returns a 201
(created) response as it should.
So what is the problem?
Well the Angular $http object doesn't then go to the "success" callback, but to the "error" callback instead... with a status code of 0 and no specific error.
So from my page, I have no way to know if my request actually worked. The only way I know that it does work is by controlling (debugging) the server and using Fiddler.
Did anybody run into this problem before? It's a bit frustrating to have a working solution but not being able to tell that it actually worked :)
Here is my $http request:
this.simulate = function (url, content) {
var deferred = $q.defer();
var data = { "Data": content, "Timestamp": new Date() };
$http.defaults.useXDomain = true;
$http.post(url, data)
.then(function(response) {
deferred.resolve({
isSuccess: true,
httpCode: response.status,
errorMessage: "",
url: url,
data:data
});
},function(response) { // This is this error callback that is being called, despite the fact that my request is working fine...
deferred.resolve({
isSuccess: false,
httpCode: response.status,
errorMessage: response.data.Message + " " + response.data.ExceptionMessage,
url: url,
data: data
});
});
return deferred.promise;
};
In fiddler, this is what I get:
The pre-flight request:
OPTIONS http://myServer:82/xclient/event/volupdate HTTP/1.1
Host: myServer:82
Connection: keep-alive
Access-Control-Request-Method: POST
Origin: http://myClient:1855
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36
Access-Control-Request-Headers: accept, origin, x-requested-with, authorization, ssotoken, content-type
Accept: */*
DNT: 1
Referer: http://myClient:1855/XClient/testharness/eventing
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-GB,en-US;q=0.8,en;q=0.6,fr-FR;q=0.4
Which gives me that pre-flight response:
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Expires: -1
Server: Microsoft-IIS/7.5
Access-Control-Allow-Origin: http://myClient:1855
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: accept, origin, x-requested-with, authorization, ssotoken, content-type
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Wed, 07 Aug 2013 10:13:05 GMT
Content-Length: 0
And then this is followed by the actual POST request:
POST http://myServer:82/xclient/event/volupdate HTTP/1.1
Host: myServer:82
Connection: keep-alive
Content-Length: 56
Origin: http://myClient:1855
Authorization: SSOB2L1ax<BLAHBLAHBLAH>EP49w=|0|jaussan|System X|20130825131712|1748001|
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36
Content-Type: application/json;charset=UTF-8
Accept: application/json, text/plain, */*
X-Requested-With: XMLHttpRequest
DNT: 1
Referer: http://myClient:1855/XClient/testharness/eventing
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-GB,en-US;q=0.8,en;q=0.6,fr-FR;q=0.4
{"Data":"SomeData","Timestamp":"2013-08-07T10:13:06.533Z"}
And then followed by a normal 201 created response to the POST:
HTTP/1.1 201 Created
Cache-Control: no-cache
Pragma: no-cache
Expires: -1
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Wed, 07 Aug 2013 10:13:05 GMT
Content-Length: 0
As you can see everything seems normal, but yet $http seems to think it's not.
Here are a few screenshots from Firebug:
$http goes to the wrong callback:
And this is the response I'm getting from $http... not really my 201!:
It appears as if your server is handling the OPTIONS (preflight) request correctly, but not the actual POST request. Your server needs to at least include an Access-Control-Allow-Origin header in the response to your POST request. In your case, that would be: Access-Control-Allow-Origin: http://myClient:1855.
For future reference, there is an excellent article about CORS that everyone who has to support a cross-origin environment should absolutely bookmark: MDN on HTTP access control.

Upload to D2L LOR Failing with Permission Error

In trying to upload a scorm package via the REST API the upload is not working.
PUT /d2l/api/lr/(D2LVERSION: version)/objects/
http://docs.valence.desire2learn.com/res/lor.html
We are always refused based on 403 permission.
Are there any settings I should look at? Also looking for a trace of this working.
As long as the account you are authenticated with can perform the function in the web UI it is expected that the API will also function.
If you are getting keys back from the login process but this call is failing you could try a basic call from the sample such as "whoami". If that works your keys are correct.
If the keys are correct and some GET calls are working, you may need to pass a different method to the signing call in the sdk. (The method is part of the signature).
Here is an example of a working trace. 403 errors often have a body that will provide additional information. Note the security parameters are: x_a,x_b,x_c,x_d,x_t parameters on the call.
PUT http://smihai-7:44459/d2l/api/LR/1.0/objects/?repositoryId=1&x_t=1339183935&x_a=L2Hd9WvDTcyiyu5n2AEgpg&x_c=tfJFhSUaczOeOGqDFPXPq8NSBPj2sOhz4U3RacqWRMY&x_b=TestToken&x_d=BEl7kdCcrjenkpBt9ri5dkt4bdEgCo6xfZDWIpkKctA HTTP/1.1
Accept: application/json, application/xml, text/json, text/x-json, text/javascript, text/xml
User-Agent: RestSharp 101.3.0.0
Host: smihai-7:44459
Content-Type: multipart/form-data; boundary=-----------------------------28947758029299
Content-Length: 1203
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
-------------------------------28947758029299
Content-Disposition: form-data; name="Resource"; filename="Hello World Module.zip"
Content-Type: application/zip
PK???u?h?]G?g???????????Hello World Topic.htmlM??
?#??A??wwo?xH+???h????b
[o+ ????oD??RYW9u??????}J,?q
d2?[!%E|Crj?Wo{34??Xg??s???L?3??+??/?????*??W?W3?Fyb w?>?cR?Zrf?*???b??PK???u?h?N2?T??n?????imsmanifest.xml?V??0?#?Q??m#TI?.b ?XF??5?????k,?$~???6I??Vt??s?=g?x???%7)??J?i4 P?f\???????????DR?W`]?(?WL???g??d???s?,.&i?q????r??jT?kI??E?C?fsmd6-?q??G? .?f?i??4???!??v\???o?7$\jH%
??K??~P??m?`E?¥a)?C????v???6????#???U~?????x???[fe?.?3????~W???;B_?,???V#B?HE???:??q?e???s??_E? 1wK??<R????T??.9YE??SkP`?????*UT??3???j??#'??#2?;?e_c?#g.????}?p?>?c??????\?
????~,u????\s?M*L?U???E??
??????Kzp\E?X#?%\p???\??R
X-????%??C??????7?|??/&?=???h????l?\?\???????P???s??))??Td??K?????{?Y?+????v?gTN??h$?
?E'E?aB?UD????PK-????u?h?]G?g?????????????????????????Hello World Topic.htmlPK-????u?h?N2?T??n???????????????????imsmanifest.xmlPK??????????E????
-------------------------------28947758029299--
And the response is like this:
HTTP/1.1 200 OK
Cache-Control: no-cache, no-store
Pragma: no-cache
Content-Length: 69
Content-Type: application/json; charset=UTF-8
Expires: -1
Server: Microsoft-IIS/7.5
X-Powered-By: ASP.NET
X-XSS-Protection: 0
Date: Fri, 08 Jun 2012 19:32:22 GMT
{"IdentId":4,"Version":1,"ExecutionMessage":null,"ExecutionStatus":0}

OData Referencing Requests in a Change Set

I have batch request to update data.
Data Is:
Item : {
id : int,
name : string,
RefItem : {
int : id,
name : string }
}
I need
Create Item
Update RefItem reference for this newly created Item, in the same batch. (Suppose RefItems(1) already exists in data set)
According to OData spec:
--batch_1872-f36a-7ce8
Content-Type: multipart/mixed; boundary=changeset_8c16-9ba3-2260
--changeset_8c16-9ba3-2260
Content-Type: application/http
Content-Transfer-Encoding: binary
PUT $1/$links/ItemRef HTTP/1.1
Content-ID: 2
Accept: application/atomsvc+xml;q=0.8, application/json;q=0.5, /;q=0.1
DataServiceVersion: 1.0
Content-Type: application/json
{"uri":"ItemRefs(1)"}
--changeset_8c16-9ba3-2260
Content-Type: application/http
Content-Transfer-Encoding: binary
POST Products HTTP/1.1
Content-ID: 1
Accept: application/atomsvc+xml;q=0.8, application/json;q=0.5, /;q=0.1
DataServiceVersion: 1.0
Content-Type: application/json
{"id":-1,"name":"seven"}
--changeset_8c16-9ba3-2260--
--batch_1872-f36a-7ce8--
I suppose it should work, but I have response error :
--batchresponse_4febeba8-dd43-4040-9fdb-866afde5304e
Content-Type: multipart/mixed; boundary=changesetresponse_424dbf12-fa9a-4a59-8284-963fa0fa7b77
--changesetresponse_424dbf12-fa9a-4a59-8284-963fa0fa7b77
Content-Type: application/http
Content-Transfer-Encoding: binary
HTTP/1.1 500 Internal Server Error
Content-ID: 2
X-Content-Type-Options: nosniff
DataServiceVersion: 1.0;
Content-Type: application/json;charset=utf-8
{"d":{"error":{"code":"","message":{"lang":"ru-RU","value":"An error occurred while processing this request."}}}}
--changesetresponse_424dbf12-fa9a-4a59-...
Can somebody tell me where I'm wrong?
Thanks.
The batch request is processed as a stream. So you can only refer to content ID which was already seen. The above sample refers to content ID 1 before the request operation with that content ID. That is not valid.
You need to first create the new instance (POST) and then add a reference to it via the $links URL.

ASP.NET MVC3 IIS7.5: Cache-Control maxage is always 0 (not good for client-side caching)

I was testing my website with Fiddler and noticed that web server always returns
Cache-Control: private, s-maxage=0
for dynamic content (MVC actions). This prevents pages from being cached on client side. I wonder if it is problem of MVC or IIS. How can I fix it? I really need client-side caching to work.
Thank you!
P.S. Below is the full set of response headers:
HTTP/1.1 200 OK
Cache-Control: private, s-maxage=0
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Vary: Accept-Encoding
Server: Microsoft-IIS/7.5
X-AspNetMvc-Version: 3.0
X-AspNet-Version: 4.0.30319
X-UA-Compatible: IE=edge,Chrome=1
Date: Sun, 20 Nov 2011 23:07:46 GMT
Content-Length: 2050
Use the OutputCacheAttribute on your controllers and/or actions to set the cache policy for that controller's actions or a particular action.
[OutputCache( Location = OutputCacheLocation.Client, Duration = 600 )]
public class HomeController : Controller
{
..
}

How to remove s-maxage header from asp.net mvc response

By default, Asp.Net MVC (at least my setup) seems to send a header with the value
Cache-Control: private, s-maxage=0
I need to remove the s-maxage=0 part. The reason is that IE6 seems to handle the content-disposition: attachment header wrong if this header is present.
The end effect I observe is that if the user chooses to Open the downloaded file instead of saving it, it is not actually saved. The difference between the working non-mvc version and the non-working mvc version is just this header.
Working response:
HTTP/1.1 200 OK
Server: ASP.NET Development Server/9.0.0.0
Date: Thu, 10 Feb 2011 19:35:47 GMT
X-AspNet-Version: 2.0.50727
Content-Disposition: attachment; filename=results.txt
Cache-Control: private
Content-Type: text/plain; charset=iso-8859-1
Content-Length: 210
Connection: Close
<<DATA>>
Non-working response:
HTTP/1.1 200 OK
Server: ASP.NET Development Server/9.0.0.0
Date: Thu, 10 Feb 2011 20:24:04 GMT
X-AspNet-Version: 2.0.50727
X-AspNetMvc-Version: 1.0
Content-Disposition: attachment; filename=results.txt
Cache-Control: private, s-maxage=0
Content-Type: text/plain
Content-Length: 90
Connection: Close
<<DATA>>

Resources