I have an issue with Internet Explorer 11 and $resource service in Angular 1.4.9. My code calls web service to post some object:
var FooServiceHandle = $resource('some/Address/');
var fooServiceHandle = new FooServiceHandle ();
fooServiceHandle.bar = someData;
fooServiceHandle.$save().then(function () { console.log('saved') });
Web service on the other hand is WCF REST.
[OperationContract]
[FaultContract(typeof(RegistrationNotAllowedFault))]
[WebInvoke(UriTemplate = "some/Address/", ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json, Method = "POST", BodyStyle = WebMessageBodyStyle.Wrapped)]
string Foo(Bar bar);
When I use IE the argument of the Foo method in the web service is null (but service method is called). Just before the $save call fooServiceHandle.bar variable isn't undefined, but a proper object. My code works in Chrome 48 and Firefox 44.
UPDATE
Strange thing is that when I look at network tab of IE developer tools it shows proper request body (which is JSON). Moreover when I copy-paste that request body to SoapUI it executes correctly. HTTP method of IE request is ok (POST), also content type (application/json). First IE request has result 307 and later there goes another with 400.
UPDATE2
I can't see any major differences in request between Chrome and IE except one - in IE order of JSON keys is slightly different, however structure is all right.
UPDATE3
Exactly the same issue with $http service. In Chrome it works and WCF binds an object, in IE it doesn't and gets null argument in service method.
var req = {
method: 'POST',
url: 'some/Address/',
data: { bar: someData}
}
$http(req).then(function(){console.log('saved')}, function(){});
Cause of this issue is pretty unbelievable. Somehow Internet Explorer and WCF together cannot handle slash at the end of service method address. Removing backslash eliminates the problem:
UriTemplate = "some/Address/" => UriTemplate = "some/Address"
var FooServiceHandle = $resource('some/Address/'); => var FooServiceHandle = $resource('some/Address');
In both Chrome and Firefox backslash is not a problem, probably they trim it.
Related
I have a spring webapp with spring security(3.2.3, so no CSRF protection) and angular.
In a controller i have a method like this one to update the users pw:
#RequestMapping("/accountinfo/password", method = arrayOf(RequestMethod.PUT))
#ResponseBody
#Secured("ROLE_USER")
open fun updateOwnPassword(user: User, #RequestBody password: String) {
val editedUser = user
editedUser.password = encoder.encode(password)
userRepository.save(editedUser)
}
The request is done via angular Service:
function changeOwnPassword(newPassword) {
return $http
.put('accountinfo/password', newPassword)
.then(function (response) {
return response.data
});
}
This works fine in every browser i tested with. Except if using IE 11.0.35 in a Citrix environment (Works outside of it,but can't see any specific configuration).
In that case i get 403 on the Request. When i change the method to POST it works fine again. I could do that for every function where i got this problem of course, but that doesn't seem like a clean solution.
As far as my research goes, i think it's something wrong with the way the browser writes the Request, but that's were i can't find out what to do.
EDIT:
I compared the request headers of both IE 11.0.35 inside and outside of Citrix and they seem exactly the same. The only difference is that the working version uses DNT=1 and the non-working version as WOW64 in the User-Agent attributes?
UPDATE:
I found out that it happens with DELETE too
Found the problem: The client sends the Requests through an additional Proxy that doesn't like PUT and DELETE and just cuts the session cookies off of it. We are adressing that problem with putting the tokens in the header in the future.
I have an angular js application, and when trying to issue the following post request :
$resource('api/'+API_VERSION+'/projects/:projectId/users/:userId')
.save(
{
projectId:$scope.project.id,
userId:id
},
{}
,function(){
// Handle callback
});
I get a 400 bad request error.
the request is handled by a spring RestController and the method looks like the following :
#RequestMapping(value = "/projects/{projectId}/users/{userID}",
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_VALUE)
#RolesAllowed(AuthoritiesConstants.USER)
void addUsers(#PathVariable Long projectId, #PathVariable Long userId) {
log.debug("REST request to add admin to project");
projectService.addUser(projectId, userId);
}
I Checked the request that is been sent to the server, and nothing bad strikes me.
The url is correct (all parameter are of valid type), and the content type is set to Application json.
Thanks in advance for any help.
Your API consumes JSON and returns void, so I think you should have consumes = MediaType.APPLICAT_JSON_VALUE in your #RequestMapping.
[Edit]:
Apart from the consumes annotation everything is fine with your back-end. Can you try making your post request with the following code :
$resource('api/'+API_VERSION+'/projects/:projectId/users/:userId',
{projectId: $scope.project.id, userId: id}).$save();
or again, creating an instance of the resource :
var Project = $resource('api/'+API_VERSION+'/projects/:projectId/users/:userId',
{projectId: $scope.project.id, userId: id});
var newProject = new Project();
newProject.$save();
And let me know if it worked ?
I have some code that loads a list from a MVC controller.
I load the data with
$scope.loadData = function () {
$scope.promise = myhttp = $http.get("forms");
myhttp.success(function (response) {
$scope.rows = response;
This works fine, the data loads and I can see the data on my html page.
But, if I reload with code, eg I just run $scope.loadData again, it fails in IE.
Works in Chrome, FF
Fails in IE and Spartan (Tested on non windows 10 same issue)
The headers look like
Cache-Control: private, s-maxage=0
The MVC controller has the authorise attribute hence the s-maxage=0, this prevents the proxy from caching.
I have tried
$scope.promise = myhttp = $http.get("forms");
When I fiddle, i dont see the request going to the server but the code does get fired.
If I nav to a new page and nav back it does reload fresh data as you would expect. It just when the reload is done with $http
Thanks
EDIT
Post works, eg
$scope.promise = myhttp = $http.post("forms");
I have a factory to retrieve json from my own server via POST, I'm getting the errror "TypeError: Cannot read property '$http' of undefined {query: ctor, stack: (...), message: "Cannot read property '$http' of undefined"}". Walking through breeze.debug.js I see my request is turned into a GET and thus mungs up the call. How do I force Breeze to execute my POST? Below is the datacontext based on the BreezeJS documentation on doing posts at http://www.getbreezenow.com/documentation/breezeajaxpostjs :
angular.module('rmBreezeApp.datacontext', [])
.factory('datacontext', function ($http, breeze, jsonResultsAdapter, logger, model, RMServer,AuthService) {
breeze.ajaxpost();
var postData = function (selector, argsArray) {
return {"selector": selector, "arguments": argsArray}
};
var ds = new breeze.DataService({
serviceName: RMServer,
hasServerMetadata: false,
useJsonp: false,
jsonResultsAdapter: jsonResultsAdapter
});
var manager = new breeze.EntityManager({dataService: ds});
model.initialize(manager.metadataStore);
return {
getShiftsForWeek: getShiftsForWeek
};
/*** implementation details ***/
function getShiftsForWeek() {
var arguments = [];
var query = breeze.EntityQuery.from("session?token=" + AuthService.authToken())
.withParameters({
$method: 'POST',
$encoding: 'JSON',
$data: postData("getShiftsForWeek",[])
});
return manager.executeQuery(query).then(
function (data) {
return data;
},
function (reject) {
console.log(reject);
}
)
}
});
I've got these includes in my index.html
<!-- ionic/angularjs js -->
<script src="lib/ionic/js/ionic.bundle.js"></script>
<script src="lib/breezejs/breeze.debug.js"></script>
<script src="lib/breeze.js.labs/breeze.angular.js"></script>
<script src="lib/breeze.js.labs/breeze.ajaxpost.js"></script>
Good thing you wrote a plunker. It revealed a number of errors and opportunities which I'll summarize here.
I forked your plunker and "fixed it". It's now returning data and displaying on screen.
I tried not to change too much but I did re-organize for what seems to me a readable style. I begin by showing the complete application component set at the top:
angular.module('myApp', ['breeze.angular'])
.value('jsonResultsAdapter', createJsonResultsAdapter())
.service('datacontext', DataContext)
.service('model', Model)
.controller('sitesCtrl', SitesCtrl)
//.config(configForCORS);
Observations
CORS
Your attempt to configure the browser for CORS (configForCors) makes no difference to my browsers (IE10, Chrome, FF). I don't think it will help you. Either a browser supports CORS or it doesn't. I haven't found any way around that. You're in trouble if you need to support IE8 because it doesn't do CORS.
Configure ajaxAdapter for Breeze
The 'breeze.angular' module does that for you. Including that module as a dependency (angular.module('myApp', ['breeze.angular'])) and then injecting the 'breeze' service into the first service that needs breeze (e.g., Datacontext) is all you needed to do.
Configure Breeze for POST queries
Breeze by default sends query requests as GET requests. Your service expects POST requests. Breeze can do that.
You neglected to include the breeze.ajaxpost.js library script tag in your index.html. You need that Breeze Labs plug-in to make POST queries.
You also have to tell the Angular ajaxAdapter about it. Look in DataContext#configAjaxAdapter. You'll see:
// configure that adapter to use ajaxPost plugin
breeze.ajaxpost(ajaxAdapter);
Actually, you don't need to specify the ajax adapter; breeze will enable POST query support for whatever is the current default ajax adapter instance if you write this:
// configure that adapter to use ajaxPost plugin
breeze.ajaxpost();
But I was more explicit because I had to get the ajax adapter anyway in order to set your default headers:
// default ajax adapter is already a wrapper around current $http
// as a result of injecting the breeze service.
var ajaxAdapter = breeze.config.getAdapterInstance("ajax");
// add the app-specific default headers
// no need to set "Content-Type" for JSON; breeze does that by default
ajaxAdapter.defaultSettings = {
headers: {
"JsonStub-User-Key": "2d3b6e81-b556-4049-ab54-ec8422237c63",
"JsonStub-Project-Key": "a753777a-bbff-4db6-8755-ea8c5e60f032"
}
};
Notice I called getAdapterInstance, not initializeAdapterInstance. We want to modify the existing instance created by breeze.angular, not overwrite it with a new one.
Client metadata and jsonResultsAdapter
After making those changes, I was getting data from the service. Unfortunately, the
shape of that data didn't quite match the expectations in the jsonResultsAdapter or the metadata.
The JSON arrives in an object with a pathwaySchool property. That property returns an array. The array contains the entity data of interest; the array itself is useless and should not become a Breeze entityType.
I trained the 'jsonResultsAdapter.visitNode' method to recognize objects with a "buildingCode" property. Such objects contain the data for the PathwaySchool entities ... and we say so by returning {entityType: "PathwaySchool"} for those nodes.
Patrick's metadata describe a type named "Site". But his jsonResultsAdapter called the type "PathwaySchool". Had to call it one thing or the other. I chose "PathwaySchool".
The metadata also said we should expect an "_id" property that is the entity key. There was no such property in the data. The buildingCode property looked most like a unique identifier so I made that property the key. I also added a name property so we can display the school on screen.
Extracting entities from the ajax results
The service returns a deeply nested object. The datacontext should hide that mess from the ViewModel/Controller.
So the success callback digs in and extracts the array of "PathwaySchool"s.
return data.results[0].pathwaySchool;
We should do something if the ajax call fails. The fail callback writes the error to the console (using Angular's $log service for future testability) and then re-rejects the error.
Re-rejecting is important! We want to make sure the caller sees the error so it can process it appropriately. If we did nothing, the Angular would assume we had fixed the error and pass undefined to the caller's success callback. That's not what we want. We want to pass the error along to the caller's fail callback. So we re-reject the error and return that (failed) promise.
ViewModel/Controller
Patrick's controller called the datacontext and simply assigned the results of the call to the $scope.sites array, expecting angular to bind to those results:
$scope.sites = datacontext.getSites(); // Not good!
That won't work. We don't want Angular to display the promise. We want to set the $scope.sites array after the promise resolves with the appropriate entities.
We should also be prepared to display an error if our call for data fails.
Here's how I get the sites into $scope:
$scope.sites = [];
datacontext.getSites()
.then(function(sites){
$scope.error = ''; // clear previous error if any
$scope.sites = sites;
})
.catch(function(error){
$scope.error = "'getSites' failed: " + error.message +
". Check console log for details.";
});
The View can bind to $scope.error if there's a problem.
View Binding
Finally, we display the results or error on screen. I need a small amount of HTML for the purpose:
<div ng-controller="sitesCtrl">
<div ng-repeat="site in sites">#{{site.buildingCode}} {{site.name}}</div>
<p class="errorMessage" ng-if="error">{{error}}</p>
</div>
I added a passing test that looks like yours to the ngDocCode sample:
var resource = '/breeze/Northwind/CustomersWithFilterOptions?token=1234ABCD';
beforeEach(function () {
breeze.ajaxpost(); // add POST option to ajax adapter
em = newEm(); // fresh EntityManager before each test
});
...
it("by CompanyName w/ appended URL query string", function (done) {
// See SO question
// http://stackoverflow.com/questions/27364078/breezejs-datacontext-post-in-angular-turns-into-get
var companyName = 'Die Wandernde Kuh';
// Notice the query string appended to the resource
EntityQuery.from(resource + '?token=1234ABCD')
.withParameters({
$method: 'POST',
$encoding: 'JSON',
$data: {
CompanyName: companyName
}
})
.using(em).execute()
.then(success)
.then(done, done);
function success(data){
expect(data.results).to.have.length(1);
var cust = data.results[0];
expect(cust.CompanyName).to.equal(companyName);
}
});
The request over the network looks like this:
POST /breeze/Northwind/CustomersWithFilterOptions?token=1234ABCD HTTP/1.1
Host: localhost:58066
Connection: keep-alive
Content-Length: 35
Pragma: no-cache
Cache-Control: no-cache
Accept: application/json, text/plain, */*
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko)
Content-Type: application/json;charset=UTF-8
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.8
The request payload is
{"CompanyName":"Die Wandernde Kuh"}
I'm not sure what's different between ours and yours. But the ball is in your court to provide a repro (plunker or jsFiddle).
I didn't see anything glaringly obvious in your question's code. I don't know why you're injecting $http but that shouldn't hurt.
I don't see where you're taking the dependency on the breeze labs 'breeze.angular' module but you must be doing it somewhere or Angular would have failed ("can't find breezeProvider") when it injected "breeze" into your "datacontext" factory.
I need to use web sockets for some interactions with the user. I have pretty much copypasted solution from here - http://xsockets.net/blog/angular-js-xsocketsnet and got an issue with Firefox (27.0.1).
When I try to make this call (TwoWayBinding is my XSockets controller, I'm using .NET MVC on host side):
var connect = function (url) {
var deferred = $q.defer();
socket = new XSockets.WebSocket(url);
socket.on(XSockets.Events.open, function (conn) {
$rootScope.$apply(function () {
deferred.resolve(conn);
});
});
return deferred.promise;
};
connect("ws://localhost:49200/TwoWayBinding").then(function (ctx) {
isConnected = true;
queued.forEach(function (msg, i) {
publish(msg.t, msg.d);
});
queued = [];
});
I always get an error from Firebug:
Firefox can't establish a connection to the server at ws://localhost:49200/TwoWayBinding.
this.webSocket = new window.WebSocket(url, subprotocol || "XSocketsNET");
The same code works well in Chrome, it gets connected and I'm getting messages sent from host. Mentioned methods are wrapped into angular service, but this all works, I do not think this should be a problem.
One thing I was able to figure out from Fiddler was this:
Chrome:
Result Protocol Host URL Body Caching Content-Type Process Comments Custom
3 200 HTTP Tunnel to localhost:49200 0 chrome:3976
Result Protocol Host URL Body Caching Content-Type Process Comments Custom
6 101 HTTP localhost:49200 /TwoWayBinding?XSocketsClientStorageGuid=5cf5c99aafd141d1b247ed70107659e0 0 chrome:3976
Firefox:
Result Protocol Host URL Body Caching Content-Type Process Comments Custom
1740 200 HTTP Tunnel to localhost:49200 0 firefox:1420
Result Protocol Host URL Body Caching Content-Type Process Comments Custom
1741 - HTTP localhost:49200 /TwoWayBinding -1 firefox:1420
Simply said - there is some additional parameter XSocketsClientStorageGuid in the response for Chrome which does not occur in the respose to FF. I'm not sure if that has any impact or if I'm completely wrong but will appreciate any advice if somebody experiences same issue.
Update:
It looks like the critical line is this one
socket = new XSockets.WebSocket(url);
as the socket is not created properly in Firefox. But I still not have the cause of this.
What version are you running on , did you make a new installation of XSockets.NET using the Nuget Package or did you use the git example mentioned in the quesion above?
I just did a test on FF 26.0 and 27.0.1 , and it did go well using this pice of example;
http://xsockets.github.io/XSockets.JavaScript.API/test/index.html
I will have a look at the old Angular example asap and makre sure it is fixed of there is a problem!
Kind regards
Magnus