Windows Phone Mango introduced Background Agents. This are a really exciting feature that allows applications to run in the background either as a Periodic or as a Resource Intensive tasks and perform certain actions. One of the things that I needed to implement for my application was a synchronize task that would connect to the current user Live account and perform some synchronization and management tasks. This feature promised exactly what I needed and with the recently released Live SDK that included libraries for Metro and Windows Phone applications it looked like this would be really straightforward to implement... How wrong I was !
The focus of this article is not to explain how to configure the connection, how to register our application and obtain the client Id, specify the redirect URL and use the sign in control. I will assume all of this is understood and you can check this link for more information on that. My purpose is specifically to focus on the issues when trying to connect to Live from the background.
As a reference, here is what I have on my MainPage.xaml object, first the XAML:
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <live:SignInButton Name="btnSignin" ClientId="MY_CLIENT_ID" Scopes="wl.signin wl.basic wl.contacts_create" RedirectUri="MY_REDIRECT_URL" Branding="Windows" TextType="SignIn" SessionChanged="btnSignin_SessionChanged" HorizontalAlignment="Left" VerticalAlignment="Top" /> </Grid>
And in the codebehind:
private void btnSignin_SessionChanged(object sender, LiveConnectSessionChangedEventArgs e) { if (e.Error != null) { infoTextBlock.Text = e.Error.Message; return; } if (e.Status == LiveConnectSessionStatus.Connected) { StartPeriodicAgent(); } } private void StartPeriodicAgent() { string periodicTaskName = "PeriodicTask"; periodicTask = ScheduledActionService.Find(periodicTaskName) as PeriodicTask; if (periodicTask != null) { RemoveAgent(periodicTaskName); } periodicTask = new PeriodicTask(periodicTaskName); periodicTask.Description = "This is a periodic task."; try { ScheduledActionService.Add(periodicTask); // Enable debug by setting #define DEBUG_AGENT at top of class #if(DEBUG_AGENT) ScheduledActionService.LaunchForTest(Constants.PeriodicTaskName, TimeSpan.FromSeconds(20)); #endif } catch () { } } private void RemoveAgent(string name) { try { ScheduledActionService.Remove(name); } catch (Exception) { } }
Two methods in the MainXaml.cs codebehind are of interest to us. First btnSignin_SessionChanged is called after the user has clicked on the sign in button and has completed the authentication. At that stage, we want to start a Periodic Task that will be executed periodically even if our application is no longer active. This is done in the StartPeriodicAgent, to simplify the testing, there is a debug directive that will start the task 20 seconds after this code has executed. Again, the purpose of this article is not to explain how to create Background Agents, for more info on this topic refer to msdn
LiveConnectClient createContact = new LiveConnectClient(session); var contact = new Dictionary<string, object>(); contact.Add("first_name", "Henri"); contact.Add("last_name", "Armitage"); createContact.PostCompleted += new EventHandler(CreateContactProperties_PostCompleted); createContact.PostAsync("me/contacts", contact);
However, things are not that easy when dealing with Background Agents. On our ScheduledTaskAgent implementation class, we have the OnInvoke method that will be called every time the periodic task in executed. The issue here is that we cannot pass parameters to this class so we have no access to the LiveConnectSession object.
So what do we try next ? Well, we still have another option. Once the user has authenticated himself and has allowed our application to access his personal data, we can then use the wrapper class LiveAuthClient to connect to live. This class has a constructor that receives our CLIENTID and REDIRECT_URL as parameters and a LoginAsync method that we can use to retrieve again the LiveConnectSession object. We could try the following in our Scheduled Task:
protected override void OnInvoke(ScheduledTask task) { List<string> Scopes = new List<string>(); Scopes.Add("wl.basic"); Scopes.Add("wl.signin"); LiveAuthClient client = new LiveAuthClient("CLIENT_ID", "REDIRECT_URL"); client.LoginCompleted += new EventHandler(client_LoginCompleted); client.LoginAsync(Scopes); }
Unfortunately, and as you are probably expecting by now, this doesn't work. what we get when the OnInvoke method gets called is an Invalid cross-thread access Exception. The reason is that we don't have access to the UI thread in our Background Agent and apparently, the LiveAuthClient class must use that thread. So what about the following alternative ?
protected override void OnInvoke(ScheduledTask task) { Deployment.Current.Dispatcher.BeginInvoke(() => { List<string> Scopes = new List<string>(); Scopes.Add("wl.basic"); Scopes.Add("wl.signin"); LiveAuthClient client = new LiveAuthClient("CLIENT_ID", "REDIRECT_URL"); client.LoginCompleted += new EventHandler(client_LoginCompleted); client.LoginAsync(Scopes); }); }
Unfortunately, this also fails. Now we should get a weird NullReference exception in the AuthenticateAsync method called in the constructor of the LiveAuthClient class, and I couldn't find anywhere information about this error. Here I gave up ! I couldn't use the Live SDK to connect to Live from my Background Agent which got me really disappointed. There was however, a solution I could still use, it was not as elegant or easy as using the SDK but it could still be done.
It wasn't really clear to me what to do until I realised that, in order to call any REST Live api, all what I needed was the authorization token and that I could retrieve this token when the user signed in from the main page of my application. Then I could store this token to the Isolated Storage of my application and that I could access this information from the Background Agent. I changed the Session Changed event of my main page as follows:
private void btnSignin_SessionChanged(object sender, LiveConnectSessionChangedEventArgs e) { if (e.Status == LiveConnectSessionStatus.Connected) { StartPeriodicAgent(); _client = new LiveConnectClient(e.Session); IsolatedStorageSettings.ApplicationSettings["AccessToken"] = e.Session.AccessToken; _client.GetCompleted += new EventHandler(OnGetCompleted); _client.GetAsync("me", null); } }
Once we have the authorization token, we can use the REST interface to create a contact on Live. We can find detailed information on how to do that here, but to summarize, we just need to make a POST call to the service at https://apis.live.net/v5.0/me/contacts passing the authorization token as an http header following the format: 'Bearer authorization_token' and then pass the contact data as json. So this is what I did, following is the code for creating the contact in my Scheduled Task OnInvoke method.
protected override void OnInvoke(ScheduledTask task) { string accessToken = IsolatedStorageSettings.ApplicationSettings["AccessToken"].ToString(); string restUrl = "https://apis.live.net/v5.0/me/contacts"; HttpWebRequest webRequest = HttpWebRequest.CreateHttp(restUrl); webRequest.Method = "POST"; webRequest.Headers["Authorization"] = String.Format("Bearer {0}", accessToken); webRequest.ContentType = "application/json"; webRequest.BeginGetRequestStream(new AsyncCallback(GetRequestStreamCallback), webRequest); } private void GetRequestStreamCallback(IAsyncResult asynchronousResult) { HttpWebRequest request = (HttpWebRequest)asynchronousResult.AsyncState; Stream postStream = request.EndGetRequestStream(asynchronousResult); string postData = "{first_name: 'Randolph',last_name: 'Carter'}"; byte[] byteArray = Encoding.UTF8.GetBytes(postData); postStream.Write(byteArray, 0, postData.Length); postStream.Close(); request.BeginGetResponse(new AsyncCallback(RespCallback), request); } private void RespCallback(IAsyncResult asynchronousResult) { //read the response to make sure it completed succesfully! }
We can see that the resulting code is not as elegant as if we had used the LiveAuthClient class, additional work will be needed in order to read the response returned by Live and ensure everything worked. The result is that the contact gets created in the user live account, and we can use this approach to call any REST api exposed by live to access all the information the user has there from a Background Agent.
I hope you have found this useful and please leave a comment !!!
Created on 15/12/2011