Return string from Web API DownloadCompleteAsync - c#

I am developing an Windows Phone Application and I am stuck at a part.
My project is in c#/xaml - VS2013.
Problem :
I have a listpicker (Name - UserPicker) which is list of all user's names. Now I Want to get the UserID from the database for that UserName. I have implemented Web Api and I am using Json for deserialization.
But I am not able to return the String from DownloadCompleted event.
Code:
string usid = "";
selecteduser = (string)UserPicker.SelectedItem;
string uri = "http://localhost:1361/api/user";
WebClient client = new WebClient();
client.Headers["Accept"] = "application/json";
client.DownloadStringAsync(new Uri(uri));
//client.DownloadStringCompleted += client_DownloadStringCompleted;
client.DownloadStringCompleted += (s1, e1) =>
{
//var data = JsonConvert.DeserializeObject<Chore[]>(e1.Result.ToString());
//MessageBox.Show(data.ToString());
var user = JsonConvert.DeserializeObject<User[]>(e1.Result.ToString());
foreach (User u in user)
{
if (u.UName == selecteduser)
{
usid = u.UserID;
}
//result.Add(c);
return usid;
}
//return usid
};
I want to return the UserID of the selected user. But Its Giving me following errors.
Since 'System.Net.DownloadStringCompletedEventHandler' returns void, a return keyword must not be followed by an object expression
Cannot convert lambda expression to delegate type 'System.Net.DownloadStringCompletedEventHandler' because some of the return types in the block are not implicitly convertible to the delegate return type

If you check source code of DownloadStringCompletedEventHandler you will see that it is implemented like that:
public delegate void DownloadStringCompletedEventHandler(
object sender, DownloadStringCompletedEventArgs e);
That means that you can't return any data from it. You probably have some method that does something with selected user id. You will need to call this method from event handler. So if this method is named HandleSelectedUserId, then code might look like that:
client.DownloadStringCompleted += (sender, e) =>
{
string selectedUserId = null;
var users = JsonConvert.DeserializeObject<User[]>(e.Result.ToString());
foreach (User user in users)
{
if (user.UName == selecteduser)
{
selectedUserId = user.UserID;
break;
}
}
HandleSelectedUserId(selectedUserId);
};
client.DownloadStringAsync(new Uri("http://some.url"));
It's also a good idea to add event handler for DownloadStringCompleted event before you call DownloadStringAsync method.

Related

Delegate or Callback method in C# to wait for a Function to Finish

So here's the background, and I'll post some pseudo code of what I'm trying to do since the actual code is quite long. So in our Mobile Apps we have a Authentication Server that generates a sessionID for each user when they log into the App. While they are using the App the sessionID needs to be verified against the Authentication Server so this way if the user is idle for too long they would need to log back in and get a new sessionID. So what I need to do is that before another web service method is called I need to check if the user has an existing sessionID and if that sessionID is still valid (this part I've been able to do fine). So far once I step into the session verification method after the first Async call to the service the other method is moving on which is causing problems since the session is still being verified or a new sessionID is being generated. Below is what I have for the VerifySession Method (lots of pseudo code to get the idea of what's going on since the actual code is pretty long)
public MobileServiceSoapClient CheckSession()
{
AuthenticationService amProxy = new AuthenticationService();
MobileServiceSoapClient serviceClient = new MobileServiceSoapClient();
//first check if there's an existing session UUID and if that session is still valid
AMUserSessionRequest sessionRequest = new AMUserSessionRequest();
String sessionID = HelperMethods.GetStringForKey(Constants.keySessionID);
if (sessionID.Length <= 0)
{
Debug.WriteLine("Generating new session ID");
//no existing session available so send login request to AM Service and
AMUserLoginRequest loginRequest = new AMUserLoginRequest();
amProxy.GetSessionCompleted += delegate(object sender, GetSessionCompletedEventArgs eventArgs)
{
//get sessionID from response and generate work session for Mobile Service
Guid session = eventArgs.Result.session_ID;
Debug.WriteLine(eventArgs.Result.session_ID);
serviceClient.Get_SessionCompleted += delegate(object sender2, Get_SessionCompletedEventArgs eventArgs2)
{
Debug.WriteLine(eventArgs2.Result);
String validSession = String.Format("{0}", eventArgs2.Result);
//store session value in preferences
HelperMethods.StoreStringForKey(Constants.keySessionID, validSession);
};
serviceClient.Get_SessionAsync(session);
};
amProxy.GetSessionAsync(loginRequest);
}
else
{
Debug.WriteLine("validating existing sessionID");
//there is a session so check if it's still valid
sessionRequest.session_ID = Guid.Parse(sessionID);
sessionRequest.signature = Helper_Classes.RsaSha1Signing.Sign(sessionID);
amProxy.CheckSessionCompleted += delegate(object sender, CheckSessionCompletedEventArgs eventArgs)
{
bool status = eventArgs.Result.ok;
if (!status)
{
//existing session isn't valid so generate new session
AMUserLoginRequest loginRequest = new AMUserLoginRequest();
amProxy.GetSessionCompleted += delegate(object getsessionSender, GetSessionCompletedEventArgs getsessionEvent)
{
//get sessionID from response and generate work session for Mobile Service
Guid session = getsessionEvent.Result.session_ID;
Debug.WriteLine(getsessionEvent.Result.session_ID);
serviceClient.Get_SessionCompleted += delegate(object sender2, Get_SessionCompletedEventArgs eventArgs2)
{
Debug.WriteLine(eventArgs2.Result);
String validSession = String.Format("{0}", eventArgs2.Result);
//store session value in preferences
HelperMethods.StoreStringForKey(Constants.keySessionID, validSession);
};
serviceClient.Get_SessionAsync(session);
};
amProxy.GetSessionAsync(loginRequest);
}
else
{
//sessionID is still a good value so use that to keep working
serviceClient.Get_SessionCompleted += delegate(object sender2, Get_SessionCompletedEventArgs eventArgs2)
{
Debug.WriteLine(eventArgs2.Result);
String validSession = String.Format("{0}", eventArgs2.Result);
//store session value in preferences
HelperMethods.StoreStringForKey(Constants.keySessionID, validSession);
};
serviceClient.Get_SessionAsync(Guid.Parse(sessionID));
}
};
amProxy.CheckSessionAsync(sessionRequest);
}
return serviceClient;
}
So now when I want to call a method in the Service method I need to do something like this below but I'm at a loss on how to do so
public void GetClientList(String agentID)
{
Debug.WriteLine("checking session");
MobileServiceSoapClient serviceClient = CheckSession();
//I need some kind of callback here or something that will wait until the CheckSession method is completed before moving on to call the service I want
serviceClient.GetClientsAsync(agentID) //this will get called pretty much right after the first if/else statement in CheckSession so in some cases it's returning incorrect results
From what I understand, the CheckSession method is asynchronous.
So you could do 2 things:
Make it return Task<MobileServiceSoapClient> and await it. You could use a TaskCompletionSource<MobileServiceSoapClient> and set its result in the appropriate events/places.
Add an Action<MobileServiceSoapClient> parameter to the method and invoke it after the method has completed its work (these are the same places that you would be setting the result in option 1).
I'd go with option 1.

Cannot await 'void' error in windows phone

I am stuck in this problem. I am creating a Windows Phone application. Here is the code.
private async void btn_signup_Click(object sender, RoutedEventArgs e)
{
obj = new ServiceReference2.Service1Client();
if (!txt_id.Text.Equals("") && !txt_name.Text.Equals("") && !txt_password.Equals(""))
{
user r = new user();
r.ID = txt_id.Text;
r.FULLNAME = txt_name.Text;
r.PASSWORD = txt_password.Text;
var g = await obj.GetDataAsync(r);
string message = g;
if (message.Equals("done"))
{
lbl_show.Text = "you have signed up !! Hurrah";
NavigationService.Navigate(new Uri("/mainmenu.xaml", UriKind.Relative));
}
else
{
lbl_show.Text = " Please fill all the field.Enter again";
}
}
}
I am getting this error "cannot await void". I am using WCF service to access the db.
Please guide me with the appropriate solution.
GetDataAsync is not a TAP method; it is an EAP method.
Try to re-create the WCF proxy and tell it to create TAP asynchronous methods. If Visual Studio doesn't give you that option, then see if the proxy has Begin*/End* methods that you can wrap into a TAP method.
If nothing else, you can wrap the EAP method/event into a TAP method.

differentiate among multiple webclient results

There is a List.
I want to download each url via webclient.DownloadStringAsync
the problem I encounter is:
how do I know which e.Result corresponds to what url ?
public class ressource{
public string url { get; set; }
public string result { get; set; }
}
List<ressource> urlist = new List<ressource>();
urlist.Add(new ressource(){url="blabla", result=string.empty});
....etc
var wc= new WebClient();
foreach(var item in urlist)
{
wc.DownloadStringCompleted += new DownloadStringCompletedEventHandler(wc_DownloadStringCompleted);
wc.DownloadStringAsync(new Uri(item.url, UriKind.Absolute));
}
void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
urlist[?].result = e.Result;
}
I feel completely stuck.
Thanks for your ideas.
the problem I encounter is: how do I know which e.Result corresponds to what url ?
There are various different options for this:
UserState
You can pass in a second argument to DownloadStringAsync, which is then available via DownloadStringCompletedEventArgs.UserState. For example:
// In your loop....
var wc = new WebClient();
wc.DownloadStringAsync(new Uri(item.url, UriKind.Absolute), item);
void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
var item = (ressource) e.UserState;
item.result = e.Result;
}
Multiple WebClients
You can create a new WebClient for each iteration of the loop, and attach a different event handler to it. A lambda expression is useful here:
// Note: this is broken in C# 3 and 4 due to the capture semantics of foreach.
// It should be fine in C# 5 though.
foreach(var item in urlist)
{
var wc = new WebClient();
wc.DownloadStringCompleted += (sender, args) => item.result = args.Result;
wc.DownloadStringAsync(new Uri(item.url, UriKind.Absolute));
}
DownloadStringTaskAsync
You could DownloadStringTaskAsync instead, so that each call returns a Task<string>. You could keep a collection of these - one for each element in urlist - and know which is which that way.
Alternatively, you could just fetch all the results synchronously, but I suspect you don't want to do that.
Additional information
Unfortunately, WebClient doesn't support multiple concurrent connections, so with all the options above you should create a new WebClient per iteration anyway.
Another alternative, and the one I prefer, is to use Microsoft's Reactive Framework (Rx). It handles all the background threading for you, similar to the TPL, but often easier.
Here's how I would do it:
var query =
from x in urlist.ToObservable()
from result in Observable.Using(
() => new WebClient(),
wc => Observable.Start(() => wc.DownloadString(x.url)))
select new
{
x.url,
result
};
Now to get the results back into the original urlist.
var lookup = urlist.ToDictionary(x => x.url);
query.Subscribe(x =>
{
lookup[x.url].result = x.result;
});
Simple as that.

How to get the name of a place in Nokia Maps on Windows Phone 8?

I want to get the name of a place (similar to Foursquare or Google Maps) from my current location using Maps API in Windows Phone 8. I can already get my current location using the code from this tutorial.
Can anybody help me?
You can use the ReverseGeocodeQuery class.
var rgc = new ReverseGeocodeQuery();
rgc.QueryCompleted += rgc_QueryCompleted;
rgc.GeoCoordinate = myGeoCoord; //or create new gc with your current lat/lon info
rgc.QueryAsync();
You can then get the data from within your rgc_QueryCompleted event handler using the Result property of the event args passed in.
If #keyboardP answer wasn't sufficient enough, here's (hopefully) working example to get the information about your location. There's no "name" property you could look up, at least not from the API's side.
public async Task<MapLocation> ReverseGeocodeAsync(GeoCoordinate location)
{
var query = new ReverseGeocodeQuery { GeoCoordinate = location };
if (!query.IsBusy)
{
var mapLocations = await query.ExecuteAsync();
return mapLocations.FirstOrDefault();
}
return null;
}
For this to work you'll need to add the following extension method for async query (from compiledexperience.com blog)
public static class GeoQueryExtensions
{
public static Task<T> ExecuteAsync<T>(this Query<T> query)
{
var taskSource = new TaskCompletionSource<T>();
EventHandler<QueryCompletedEventArgs<T>> handler = null;
handler = (sender, args) =>
{
query.QueryCompleted -= handler;
if (args.Cancelled)
taskSource.SetCanceled();
else if (args.Error != null)
taskSource.SetException(args.Error);
else
taskSource.SetResult(args.Result);
};
query.QueryCompleted += handler;
query.QueryAsync();
return taskSource.Task;
}
}

Async XML Reading in Windows Phone 7

So I have a Win Phone app that is finding a list of taxi companies and pulling their name and address from Bing successfully and populating a listbox that is being displayed to users. Now what I want to do is, to search for each of these terms on Bing, find the number of hits each search term returns and rank them accordingly (a loose sort of popularity ranking)
void findBestResult(object sender, DownloadStringCompletedEventArgs e)
{
string s = e.Result;
XmlReader reader = XmlReader.Create(new MemoryStream(System.Text.UTF8Encoding.UTF8.GetBytes(s)));
String name = "";
String rName = "";
String phone = "";
List<TaxiCompany> taxiCoList = new List<TaxiCompany>();
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element)
{
if (reader.Name.Equals("pho:Title"))
{
name = reader.ReadInnerXml();
rName = name.Replace("&","&");
}
if (reader.Name.Equals("pho:PhoneNumber"))
{
phone = reader.ReadInnerXml();
}
if (phone != "")
{
string baseURL = "http://api.search.live.net/xml.aspx?Appid=<MyAppID>&query=%22" + name + "%22&sources=web";
WebClient c = new WebClient();
c.DownloadStringAsync(new Uri(baseURL));
c.DownloadStringCompleted += new DownloadStringCompletedEventHandler(findTotalResults);
taxiCoList.Add (new TaxiCompany(rName, phone, gResults));
}
phone = "";
gResults ="";
}
TaxiCompanyDisplayList.ItemsSource = taxiCoList;
}
}
So that bit of code finds the taxi company and launches an asynchronous task to find the number of search results ( gResults ) to create each teaxicompany object.
//Parses search XML result to find number of results
void findTotalResults(object sender, DownloadStringCompletedEventArgs e)
{
lock (this)
{
string s = e.Result;
XmlReader reader = XmlReader.Create(new MemoryStream(System.Text.UTF8Encoding.UTF8.GetBytes(s)));
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element)
{
if (reader.Name.Equals("web:Total"))
{
gResults = reader.ReadInnerXml();
}
}
}
}
}
The above snipped finds the number of search results on bing, but the problem is since it launches async there is no way to correlate the gResults obtained in the 2nd method with the right company in method 1. Is there any way to either:
1.) Pass the name and phone variables into the 2nd method to create the taxi object there
2.) Pass back the gResults variable and only then create the corresponding taxicompany object?
Right well there is a lot do here.
Getting some small helper code
First off I want to point you to a couple of blog posts called Simple Asynchronous Operation Runner Part 1 and Part 2. I'm not suggesting you actually read them (although you're welcome too but I've been told they're not easy reading). What you actually need is a couple of code blocks from them to put in your application.
First from Part 1 copy the code from the "AsyncOperationService" box, place it in new class file in your project called "AsyncOperationService.cs".
Second you'll need the "DownloadString" function from Part 2. You could put that anywhere but I recommend you create a static public class called "WebClientUtils" and put it in there.
Outline of solution
We're going to create a class (TaxiCompanyFinder) that has a single method which fires off the asynchronous job to get the results you are after and then has an event that is raised when the job is done.
So lets get started. You have a TaxiCompany class, I'll invent my own here so that the example is as complete as possible:-
public class TaxiCompany
{
public string Name { get; set; }
public string Phone { get; set; }
public int Total { get; set; }
}
We also need an EventArgs for the completed event that carries the completed List<TaxiCompany> and also an Error property that will return any exception that may have occured. That looks like this:-
public class FindCompaniesCompletedEventArgs : EventArgs
{
private List<TaxiCompany> _results;
public List<TaxiCompany> Results
{
get
{
if (Error != null)
throw Error;
return _results;
}
}
public Exception Error { get; private set; }
public FindCompaniesCompletedEventArgs(List<TaxiCompany> results)
{
_results = results;
}
public FindCompaniesCompletedEventArgs(Exception error)
{
Error = error;
}
}
Now we can make a start with some bare bones for the TaxiCompanyFinder class:-
public class TaxiCompanyFinder
{
protected void OnFindCompaniesCompleted(FindCompaniesCompletedEventArgs e)
{
Deployment.Current.Dispatcher.BeginInvoke(() => FindCompaniesCompleted(this, e));
}
public event EventHandler<FindCompaniesCompletedEventArgs> FindCompaniesCompleted = delegate {};
public void FindCompaniesAsync()
{
// The real work here
}
}
This is pretty straight forward so far. You'll note the use of BeginInvoke on the dispatcher, since there are going to be a series of async actions involved we want to make sure that when the event is actually raised it runs on the UI thread making it easier to consume this class.
Separating XML parsing
One of the problems your original code has is that it mixes enumerating XML with trying to do other functions as well, its all a bit spagetti. First function that I indentified is the parsing of the XML to get the name and phone number. Add this function to the class:-
IEnumerable<TaxiCompany> CreateCompaniesFromXml(string xml)
{
XmlReader reader = XmlReader.Create(new StringReader(xml));
TaxiCompany result = new TaxiCompany();
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element)
{
if (reader.Name.Equals("pho:Title"))
{
result.Name = reader.ReadElementContentAsString();
}
if (reader.Name.Equals("pho:PhoneNumber"))
{
result.Phone = reader.ReadElementContentAsString();
}
if (result.Phone != null)
{
yield return result;
result = new TaxiCompany();
}
}
}
}
Note that this function yields a set of TaxiCompany instances from the xml without trying to do anything else. Also the use of ReadElementContentAsString which makes for tidier reading. In addition the consuming of the xml string is much smoother.
For similar reasons add this function to the class:-
private int GetTotalFromXml(string xml)
{
XmlReader reader = XmlReader.Create(new StringReader(xml));
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element)
{
if (reader.Name.Equals("web:Total"))
{
return reader.ReadElementContentAsInt();
}
}
}
return 0;
}
The core function
Add the following function to the class, this is the function that does all the real async work:-
private IEnumerable<AsyncOperation> FindCompanies(Uri initialUri)
{
var results = new List<TaxiCompany>();
string baseURL = "http://api.search.live.net/xml.aspx?Appid=<MyAppID>&query=%22{0}%22&sources=web";
string xml = null;
yield return WebClientUtils.DownloadString(initialUri, (r) => xml = r);
foreach(var result in CreateCompaniesFromXml(xml))
{
Uri uri = new Uri(String.Format(baseURL, result.Name), UriKind.Absolute);
yield return WebClientUtils.DownloadString(uri, r => result.Total = GetTotalFromXml(r));
results.Add(result);
}
OnFindCompaniesCompleted(new FindCompaniesCompletedEventArgs(results));
}
It actually looks pretty straight forward, almost like synchonous code which is the point. It fetchs the initial xml containing the set you need, creates the set of TaxiCompany objects. It the foreaches through the set adding the Total value of each. Finally the completed event is fired with the full set of companies.
We just need to fill in the FindCompaniesAsync method:-
public void FindCompaniesAsync()
{
Uri initialUri = new Uri("ConstructUriHere", UriKind.Absolute);
FindCompanies(initialUri).Run((e) =>
{
if (e != null)
OnFindCompaniesCompleted(new FindCompaniesCompletedEventArgs(e));
});
}
I don't know what the initial Uri is or whether you need to paramatise in some way but you would just need to tweak this function. The real magic happens in the Run extension method, this jogs through all the async operations, if any return an exception then the completed event fires with Error property set.
Using the class
Now in you can consume this class like this:
var finder = new TaxiCompanyFinder();
finder.FindCompaniesCompleted += (s, args) =>
{
if (args.Error == null)
{
TaxiCompanyDisplayList.ItemsSource = args.Results;
}
else
{
// Do something sensible with args.Error
}
}
finder.FindCompaniesAsync();
You might also consider using
TaxiCompanyDisplayList.ItemsSource = args.Results.OrderByDescending(tc => tc.Total);
if you want to get the company with the highest total at the top of the list.
You can pass any object as "UserState" as part of making your asynchronous call, which will then become available in the async callback. So in your first block of code, change:
c.DownloadStringAsync(new Uri(baseURL));
c.DownloadStringCompleted += new DownloadStringCompletedEventHandler(findTotalResults);
to:
TaxiCompany t = new TaxiCompany(rName, phone);
c.DownloadStringAsync(new Uri(baseURL), t);
c.DownloadStringCompleted += new DownloadStringCompletedEventHandler(findTotalResults);
Which should then allow you to do this:
void findTotalResults(object sender, DownloadStringCompletedEventArgs e)
{
lock (this)
{
TaxiCompany t = e.UserState;
string s = e.Result;
...
}
}
I haven't tested this code per-se, but the general idea of passing objects to async callbacks using the eventarg's UserState should work regardless.
Have a look at the AsyncCompletedEventArgs.UserState definition on MSDN for further information.

Categories