Most of the code I've written in .NET to make REST calls have been synchronous. Since Silverlight on Windows Phone only supports Async WebClient and HttpWebRequest calls, I was wondering what a good async pattern is for a Class that exposes methods that make REST calls.
For example, I have an app that needs to do the following.
Login and get token
Using token from #1, get a list of albums
Using token from #1 get a list of categories
etc
my class exposes a few methods:
Login()
GetAlbums()
GetCategories()
since each method needs to call WebClient using Async calls what I need to do is essentially block calling Login till it returns so that I can call GetAlbums().
What is a good way to go about this in my class that exposes those methods?
You might take a look at the Reactive (Rx) framework extensions:
http://www.leading-edge-dev.de/?p=501
http://themechanicalbride.blogspot.com/2009/07/introducing-rx-linq-to-events.html
[edit: ooh - found a good link:]
http://rxwiki.wikidot.com/101samples
They provide a way to "sequence" events, acting only upon certain conditions met - for example, say you had a method "AuthenticationResult Authenticate(string user, string pass)"
You could do something like:
var foo = Observable.FromAsyncPattern<string, string, AuthenticationResult>
(client.BeginAuthenticate, client.EndAuthenticate);
var bar = foo("username","password");
var result = bar.First();
Effectively turning an asynchronous method to a synchronous one. You can extend this to include "chaining":
var bar = foo("username", "password")
.Then(authresult => DoSomethingWithResult(authresult));
Neat stuff. :)
It really depends on what you want to do with this information. If for instance you are attempting to display the list of albums/categories etc, one way to model this would be to
Have one or more classes which implements the INotifyPropertyChanged interface, and are used as data sources for your views (look at the files under the Models folder in a new PhoneListApplication for an example)
Start an async operation to login and get the token, have the async method's callback store the token for you and call a function which will start an async operation to get list of albums and categories.
The callback for the async operation to get a list of albums/categories can update an ObservableList (by adding items to it). I'd imaging you have one class each for albums and categories, each with an observable list. Anyways, once you are done adding, just call NotifyPropertyChanged with the name of the property you changed, and your data should show up.
There is an obvious problem with cases where you want to wait and not proceed until you receive something back over the network (for instance if you want to keep the login page around until you know that you have successfully authenticated). In this case you could just change the page in the async callback.
You could obviously also do something fancier and have a thread wait for an event set by the async callback. I recommend not have the UI thread do this, since it limits your ability to have things like timeouts, and is generally very messy.
We wrote our client-side service layer with all async function signatures that look like this:
public void MyFunction(
ArtType arg,
Action<ReturnType> success,
Action<FailureType> failure);
The service code does an async call to the web service, and when that returns it calls the success callback if the call was successful, and the failure callback if there was a fault/exception. Then the calling code kinda looks like this:
MyServiceInstance.MyFunction(
blahVar,
returnVal => UIInvoker.Invoke(() =>
{
//some success code here
}),
fault => UIInvoker.Invoke(() =>
{
//some fault handling code here
}));
(UIInvoker is just a utility that dispatches back to the UI from a background thread.)
I put something together that is a bit more fluent.
Restful-Silverlight is a library I've created to help with both Silverlight and WP7.
I have included code below to show how you can use the library to retrieve tweets from Twitter.
Sample Usage of Restful-Silverlight retrieving tweets from Twitter:
//silverlight 4 usage
List<string> tweets = new List<string>();
var baseUri = "http://search.twitter.com/";
//new up asyncdelegation
var restFacilitator = new RestFacilitator();
var restService = new RestService(restFacilitator, baseUri);
var asyncDelegation = new AsyncDelegation(restFacilitator, restService, baseUri);
//tell async delegation to perform an HTTP/GET against a URI and return a dynamic type
asyncDelegation.Get<dynamic>(new { url = "search.json", q = "#haiku" })
//when the HTTP/GET is performed, execute the following lambda against the result set.
.WhenFinished(
result =>
{
textBlockTweets.Text = "";
//the json object returned by twitter contains a enumerable collection called results
tweets = (result.results as IEnumerable).Select(s => s.text as string).ToList();
foreach (string tweet in tweets)
{
textBlockTweets.Text +=
HttpUtility.HtmlDecode(tweet) +
Environment.NewLine +
Environment.NewLine;
}
});
asyncDelegation.Go();
//wp7 usage
var baseUri = "http://search.twitter.com/";
var restFacilitator = new RestFacilitator();
var restService = new RestService(restFacilitator, baseUri);
var asyncDelegation = new AsyncDelegation(restFacilitator, restService, baseUri);
asyncDelegation.Get<Dictionary<string, object>>(new { url = "search.json", q = "#haiku" })
.WhenFinished(
result =>
{
List<string> tweets = new List();
textBlockTweets.Text = "";
foreach (var tweetObject in result["results"].ToDictionaryArray())
{
textBlockTweets.Text +=
HttpUtility.HtmlDecode(tweetObject["text"].ToString()) +
Environment.NewLine +
Environment.NewLine;
}
});
asyncDelegation.Go();
Related
I am trying to develop an app for my fantasy baseball league to use for our draft (we some kind of quirky stuff all the major sites don't account for) - I want to pull some player data to use for the app by using MLB's API. I have been able to get the response from MLB, but can't do anything with the data after I get it back. I am trying to store the JSON into an array, and if I console.log the array as a whole, it will give me the entire chunk of data, but if I try to call the specific index value of the 1st item, it comes back as undefined.
let lastName = 'judge';
let getData = new XMLHttpRequest;
let jsonData = [];
function getPlayer () {
getData.open('GET', `http://lookup-service-
prod.mlb.com/json/named.search_player_all.bam?
sport_code='mlb'&active_sw='Y'&name_part='${lastName}%25'`, true)
getData.onload = function() {
if (this.status === 200) {
jsonData.push(JSON.parse(this.responseText));
}
}
getData.send();
console.log(jsonData);
}
When I change the above console.log to console.log(jsonData[0]) it comes back as undefined. If I go to the console and copy the property path, it displays as [""0""] - Either there has to be a better way to use the JSON data or storing it into an array is doing something abnormal that I haven't encountered before.
Thanks!
The jsonData array will be empty after calling getPlayer function because XHR loads data asynchronously.
You need to access the data in onload handler like this (also changed URL to HTTPS to avoid protocol mismatch errors in console):
let lastName = 'judge';
let getData = new XMLHttpRequest;
let jsonData = [];
function getPlayer () {
getData.open('GET', `https://lookup-service-
prod.mlb.com/json/named.search_player_all.bam?
sport_code='mlb'&active_sw='Y'&name_part='${lastName}%25'`, true)
getData.onload = function() {
if (this.status === 200) {
jsonData.push(JSON.parse(this.responseText));
// Now that we have the data...
console.log(jsonData[0]);
}
}
getData.send();
}
First answer from How to force a program to wait until an HTTP request is finished in JavaScript? question:
There is a 3rd parameter to XmlHttpRequest's open(), which aims to
indicate that you want the request to by asynchronous (and so handle
the response through an onreadystatechange handler).
So if you want it to be synchronous (i.e. wait for the answer), just
specify false for this 3rd argument.
So, you need to change last parameter in open function as below:
getData.open('GET', `http://lookup-service-
prod.mlb.com/json/named.search_player_all.bam?
sport_code='mlb'&active_sw='Y'&name_part='${lastName}%25'`, false)
But from other side, you should allow this method to act asynchronously and print response directly in onload function.
I'm developing an Angular Shopping Cart with Breezejs handling the data.
Before saving the transaction I need to authorize with the gateway.
But Breeze doesn't seem to let me do anything before saving, since these are entities.
My question is:
How I deserialize the entities to create the objects for the gateway?
Like so:
public SaveResult CheckoutSave(JObject saveBundle)
{
var billingAddress = saveBundle[0]; //this doesn't work,
//how to separate the data from the bundle?
var paymentMethod = saveBundle[1];
var products = saveBundle[2];
var user = UserManager.FindById(User.Identity.GetUserId());
// here I'd call the gateway...
// and then, I can call the changes...
return _repository.SaveChanges(saveBundle);
}
Was pretty easy, as a matter of fact...
I used the delegate:
_contextProvider.BeforeSaveEntitiesDelegate
and extended the function by calling the gateway... the EF handles the transaction if I raise an exception.
I have been given a Project which is written entirely in Backbone.js, which I am supposed to change according to our specific needs. I have been studying Backbone.js for the past 2 weeks. I have changed the basic skeleton UI and a few of the features as needed. However I am trying to understand the flow of the code so that I can make further changes.
Specifically, I am trying to search some content on Youtube. I have a controller which uses a collection to specify the url and parse and return the response. The code is vast and I get lost where to look into after I get the response. I tried to look into views but it only has a div element set. Could someone help me to proceed. I wont be able to share the code here, but a general idea of where to look into might be useful.
Code Snippet
define([
'models/youtubeModelForSearch',
'coretv/config',
'libs/temp/pagedcollection',
'coretv/coretv'
],function( youtubeModelForSearch, Config, PagedCollection, CoreTV ) {
"use strict";
return PagedCollection.extend({
model: youtubeModelForSearch,
initialize: function() {
this.url = 'http://gdata.youtube.com/feeds/api/videos/?v=2&alt=json&max-results=20';
},
fetch: function(options) {
if (options === undefined) options = {};
if (options.data === undefined) options.data = {};
//options.data.cmdc = Config.getCMDCHost();
//CoreTV.applyAccessToken(options);
PagedCollection.prototype.fetch.call(this, options);
},
parse: function(response) {
var temp = response.feed
/*temp["total"] = 20;
temp["start"] = 0;
temp["count"] = 10; */
console.log(temp);
return temp.entry;
},
inputChangeFetch: function(query) {
this.resetAll();
if(query) {
this.options.data.q = query;
// this.options.data.region = Config.api.region;
//this.options.data.catalogueId = Config.api.catalogueId;
this.setPosition(0);
}
}
});
});
Let's assume your collection endpoint is correctly set and working. When you want to get the data from the server you can call .fetch() on you collection.
When you do this, it will trigger an request event. Your views or anybody else can listen to it to perform any action.
When the data arrives from the server, your parse function is called, it is set using set or reset, depending the options you passed along fetch(). This will trigger any event related to the set/reset (see the documentation). During set/reset, the data retrieved from your server will be parsed using parse (you can skip it, passing { parse: false }.
Right after that, if you passed any success callback to your fetch, it will be called with (collection, response, options) as parameters.
And, finally, it will trigger a sync event.
If your server does not respond, it will trigger an error event instead of all this.
Hope, I've helped.
I'm implementing a system that require access to Google Places JS API. I've been using rails for most of the project, but now I want to inject a bit of AJAX in one of my views. Basically it is a view that displays places near your location. For this, I'm using the JS API of Google places. A quick workflow would be:
1- The user inputs a text query and hits enter.
2- There is an AJAX call to request data from Google Places API.
3- The successful result is presented to the user.
The problem is primarily in step 2. I want to use backbone for this but when I create a backbone model, it requests to the 'rootURL'. This wouldn't be a problem if the requests to Places was done from the server but it is not.
A place call is done like this:
service = new google.maps.places.PlacesService(map);
service.nearbySearch(request, callback);
Passing a callback function:
function callback(results, status) {
if (status == google.maps.places.PlacesServiceStatus.OK) {
for (var i = 0; i < results.length; i++) {
var place = results[i];
createMarker(results[i]);
}
}
}
Is it possible to override the 'fetch' method in backbone model and populate the model with the successful Places result? Is this a bad idea?
It is possible to override the fetch method of your backbone model.
var mapModel = Backbone.Model.extend({
fetch: function (options) {
// do your call to google places here
},
callBackFunctionForGoogleMaps: function (results, status) {
// call back function here would set model properties
}
});
return mapModel;
This way you override fetch and remove the defaults behavior of Backbone to make an ajax call.
Just as an FYI if you want to override Backbone models fetch but still have the default behavior of model.fetch you can do the following. Note the return calling Backbone.Model.fetch.
var mapModel = Backbone.Model.extend({
fetch: function (options) {
// do any pre-fetch actions here
return Backbone.Model.fetch.call(options);
}
});
return mapModel;
It is probably not a bad idea to override the fetch method here because you are still fetching data for your model, just not through ajax calls on your end. It would be smart though to leave comments noting that you are overriding fetch in this manner for a reason.
I have two set of collections. One is for the categories and the other is for the Items. I ned to wait for the categories to finish fetching everything for me to set the category for the Items to be fetched.
Also i everytime i click a category i must re-fetch a new Items Collection because i have a pagination going on everytime i click on a category it doesn't refresh or re-fetch the collection so the pagination code is messing with the wrong collection. Any ideas?
this.categoryCollection = new CategoryCollection();
this.categoryCollection.fetch();
this.itemCollection = new ItemCollection();
this.itemCollection.fetch();
Just ran into a similar situation. I ended up passing jquery.ajax parameters to the fetch() call. You can make the first fetch synchronous. From the backbone docs:
jQuery.ajax options can also be passed directly as fetch options
Your code could be simplified to something like:
this.categoryCollection.fetch({async:false});
this.itemCollection.fetch();
One quick way would be to just pass a callback into the first fetch() call that invokes the second. fetch() takes an options object that supports a success (and error) callback.
var self = this;
this.categoryCollection = new CategoryCollection();
this.categoryCollection.fetch({
success: function () {
self.itemCollection = new ItemCollection();
self.itemCollection.fetch();
}
});
Not the most elegant, but it works. You could probably do some creative stuff with deferreds since fetch() returns the jQuery deferred that gets created by the $.ajax call that happens.
For the pagination issue, it's difficult to tell without seeing what your pagination code is doing. You're going to have to roll the pagination stuff yourself since Backbone doesn't support it natively. What I'd probably do is create a new Collection for the page criteria that are being queried and probably create a server action I could hit that would support the pagination (mapping the Collection's url to the paginated server action). I haven't put a ton of thought into that, though.
I had to react to this thread because of the answers there.
This is ONLY WAY OF DOING THIS RIGHT!!!
this.categoryCollection = new CategoryCollection();
this.itemCollection = new ItemCollection();
var d1 = this.categoryCollection.fetch();
var d2 = this.itemCollection.fetch();
jQuery.when(d1, d2).done(function () {
// moment when both collections are populated
alert('YOUR COLLECTIONS ARE LOADED :)');
});
By doing that you are fetching both collections at same time and you can have event when both are ready. So you don't wait to finish loading first collections in order to fetch other, you are not making ajax calls sync etc that you can see in other answers!
Here is a doc on Deferred objects.
Note: in this example case when one or more deferred object fails it's not covered. If you want to cover that case also beside .done you will have to add .fail callback on .when and also add error handler that will mark failed d1 or d2 in this example.
I am using RelationalModel and I created a queued fetch, that only calls the 'change' event when done loading:
var MySuperClass = Backbone.RelationalModel.extend({
//...
_fetchQueue : [],
fetchQueueingChange : function(name){
//Add property to the queue
this._fetchQueue.push(name);
var me = this;
//On fetch finished, remove it
var oncomplete = function(model, response){
//From _fetchQueue remove 'name'
var i = me._fetchQueue.indexOf(name);
me._fetchQueue.splice(i, 1);
//If done, trigger change
if (me._fetchQueue.length == 0){
me.trigger('change');
}
};
this.get(name).fetch({
success: oncomplete,
error : oncomplete
});
},
//...
});
The class would call:
this.fetchQueueingChange('categories');
this.fetchQueueingChange('items');
I hope you can improve on this, it worked well for me.
I ended up with the same problem today and figured out a solution to this:
var self = this;
this.collection = new WineCollection();
this.collection.url = ApiConfig.winetards.getWineList;
this.collection.on("reset", function(){self.render()});
this.collection.fetch({reset: true});
Now when the fetch on the collection is complete a "reset" is triggered and upon "reset" call the render() method for the view.
Using {async: false} is not the ideal way to deal with Backbone's fetch().
just set jQuery to become synchronous
$.ajaxSetup({
async: false
});
this.categoryCollection.fetch();
this.itemCollection.fetch();
$.ajaxSetup({
async: true
});
This is the simplest solution, I guess. Of course, starting new requests while these fetches run will be started as synchronous too, which might be something you don't like.