Command line and vso-agent

Written to the tune of:

A while ago Microsoft released this wonderful thing called the VSO agent – a cross-platform build agent that you can set up on MacOS X and/or Linux and hook it directly to a VSO or TFS instance to handle automated builds with a lot of customization options. You can get it here.

So here comes the challenge – more often than not, the build agent should be automatically set up, but the documentation mentions that the instance details, such as the service URL, username and password are manually entered. Not exactly what you want to do in an automated scenario. The good news is that there is a (not so) secret option to use command line parameters for the vso-agent:

node agent/vsoagent.js --u YOUR_USERNAME --p VSO_ONE_USE_TOKEN --s https://VSO_URL.visualstudio.com --a AGENT_NAME --l AGENT_POOL_CAN_BE_DEFAULT

Voila! All of a sudden, you can include this in your deployment scripts.

Last.fm API for a Windows Phone App – Auth

As per the request of many Beem users, I am implementing Last.fm track scrobbling. The first part of this task is to implement an API client for the Last.fm web service, and step one is user authentication. Last.fm is not using OAuth, but rather its own implementation of an authentication engine that relies on a composite MD5 secret.

But let’s begin with the basics. As it is a mobile application, I need to perform a request to the core endpoint with the auth.getMobileSession method. The URL is https://ws.audioscrobbler.com/2.0/. Remember to use HTTPS, as it is an inherent requirement for the request to be successful. Two required components of the request are the API key and the API secret – both can be obtained as you register your own application on the Last.fm developer portal.

NOTE: Ignore the is+[space] part and just use the code that comes afterwards.

The API call is complete only when it is accompanied by a signature. The signature is generated by building a composite string, made of each parameter and value concatenated together (with no delimiters) in alphabetical order, followed by the API secret, that are later hashed with an MD5 helper. Since by default the Windows Phone 7.1 SDK comes without the standard .NET MD5CryptoServiceProvider, I have to carry an internal implementation. You could take a look at the specifics of the MD5 algorithm here, or you could download a ready-to-go class created by Reid Borsuk and Jenny Zheng here (which is what I am using in the app). The method I am using to get the signature looks like this:

 public string GetSignature(Dictionary<string, string> parameters)
{
string result = string.Empty;

IOrderedEnumerable<KeyValuePair<string, string>> data = parameters.OrderBy(x=>x.Key);

foreach (var s in data)
{
result += s.Key + s.Value;
}

result += SECRET;
result = MD5Core.GetHashString(Encoding.UTF8.GetBytes(result));

return result;
}

The next step is to perform the authentication request itself, to get the session key. A raw implementation of the necessary method can look like this:

 public void GetMobileSession(string userName, string password, Action<string> onCompletion)
{
var parameters = new Dictionary<string, string>();
parameters.Add("username", userName);
parameters.Add("password", password);
parameters.Add("method", "auth.getMobileSession");
parameters.Add("api_key", API_KEY);

string signature = GetSignature(parameters);

string comboUrl = string.Concat(CORE_URL, "?method=auth.getMobileSession", "&api_key=", API_KEY,
"&username=", userName, "&password=", password, "&api_sig=", signature);

var client = new WebClient();
client.UploadStringAsync(new Uri(comboUrl),string.Empty);
client.UploadStringCompleted += (s, e) =>
{
try
{
onCompletion(e.Result);
}
catch (WebException ex)
{
HttpWebResponse response = (HttpWebResponse)ex.Response;
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
Debug.WriteLine(reader.ReadToEnd());
}
}
};
}

As the signature is obtained, I still need to include the parameters in the URL, including the method, API key and the proper credentials. The request has to be a POST one, therefore I am using UploadStringAsync instead of DownloadStringAsync, which will execute a GET request.

Simple as that, you have the auth session key.

Last.fm API for a Windows Phone App – Scrobbling a Track

As I discussed the basic of authentication in my previous post, the most important Last.fm feature that is added to Beem in itself is track scrobbling, which will allow you to keep records of what you listened to from your favorite music aggregation service. The implementation of the method used to send the track from the app to Last.fm is extremely similar to GetMobileSession.


public void ScrobbleTrack(string artist, string track, string sessionKey,
Action<string> onCompletion)
{
string currentTimestamp = DateHelper.GetUnixTimestamp();

var parameters = new Dictionary<string, string>();
parameters.Add("artist[0]", artist);
parameters.Add("track[0]", track);
parameters.Add("timestamp[0]", currentTimestamp);
parameters.Add("method", "track.scrobble");
parameters.Add("api_key", API_KEY);
parameters.Add("sk", sessionKey);

string signature = GetSignature(parameters);

string comboUrl = string.Concat(CORE_URL, "?method=track.scrobble", "&api_key=", API_KEY,
"&artist[0]=", artist, "&track[0]=", track, "&sk=", sessionKey,
"&timestamp[0]=", currentTimestamp,
"&api_sig=", signature);

var client = new WebClient();
client.UploadStringAsync(new Uri(comboUrl), string.Empty);
client.UploadStringCompleted += (s, e) =>
{
try
{
onCompletion(e.Result);
}
catch (WebException ex)
{
HttpWebResponse response = (HttpWebResponse)ex.Response;
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
Debug.WriteLine(reader.ReadToEnd());
}
}
};
}

The new required parameters here are the artist name, the track name, a UNIX-style timestamp and the session key that you obtained from the core authentication method. Although there is no method in C# to give you the UNIX timestamp right away, you can easily do it like this:

 using System;

namespace Beem.Utility
{
public static class DateHelper
{
public static string GetUnixTimestamp()
{
TimeSpan t = (DateTime.UtcNow - new DateTime(1970, 1, 1));
return ((int)t.TotalSeconds).ToString();
}
}
}

Also notice that the parameters for the track are sent in array format. Since I am only scrobbling one track at a time, I can use the index zero [0]. Your situation might be different. ScrobbleTrack can be invoked like this:

 LastFmClient client = new LastFmClient();
client.ScrobbleTrack("Armin van Buuren", "In and Out Of Love", "SESSION_KEY",
(s) =>
{
Debug.WriteLine("Success!");
});

You should now see the track registered on Last.fm.

Moving Beem from Static XML to Azure Mobile Services

While working on Beem, I always relied on a static XML file to fetch me the list of available online radio streams. It’s a good way to keep the content dynamic, and when new stations are added, I do not have to re-submit the application for certification but rather just update the XML file. This worked fine for a while, but I thought about having a more optimal way to get the necessary data. Specifically, I wanted to have a backend that can be easily extended and modified when necessary. Relying on an XML file means that I am restricted to the static data set, that has to be re-downloaded all the time whenever something from it is needed.

The switch was done in favor of Azure Mobile Services. That way, I can go as far as run LINQ queries on my data and get a JSON-formatted output exactly for what I need, if I am building a companion app for Windows Phone 8 or Windows 8. More than that, the data can be easily updated directly from the mobile device, without having the XML file downloaded in its entirety. And while it is not that large, on devices with limited data this is a consideration I have to throw into the play.

Let’s start with the fact that there is no Azure Mobile Services SDK for Windows Phone 7.5 applications. If the application would be designed for Windows Phone 8, you can download the official toolset. Beem supports multiple devices that are running Windows Phone OS 7.5, therefore I cannot do the full switch to Windows Phone OS 8. Does this mean that I cannot use AMS? No. There are some trade-offs, such as the fact that I no longer have SDK-based access to LINQ queries out-of-the-box, but I can still access the information in the way I want due to the fact that AMS sports a REST API. So all I needed is implement my own client class.

My recommendation would also be to use JSON.NET in your application, since the data returned by HTTP requests will be JSON-formatted, but you can also parse JSON manually (who would reinvent the wheel anyway?) if you want to. Getting back to the application, I am operating on a Station model:

 using System;
using System.Windows;
using System.Xml.Serialization;
using System.Collections.ObjectModel;
using System.ComponentModel;

namespace Beem.Models
{
public class Station : INotifyPropertyChanged
{
// Required by Azure Mobile Services
// As we transition from XML to ZUMO, this will be persistent.
[XmlIgnore()]
public int? Id { get; set; }

[XmlElement("name")]
public string Name { get; set; }
[XmlElement("location")]
public string Location { get; set; }
[XmlElement("image")]
public string Image { get; set; }
[XmlElement("description")]
public string Description { get; set; }
[XmlElement("jsonid")]
public string JSONID { get; set; }

private ObservableCollection<Track> _trackList;
[XmlIgnore()]
public ObservableCollection<Track> TrackList
{
get
{
return _trackList;
}
set
{
if (_trackList != value)
{
_trackList = value;
NotifyPropertyChanged("TrackList");
}
}
}

private Track _nowPlaying;
public Track NowPlaying
{
get
{
return _nowPlaying;
}
set
{
if (_nowPlaying != value)
{
_nowPlaying = value;
NotifyPropertyChanged("NowPlaying");
}
}
}

public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
Deployment.Current.Dispatcher.BeginInvoke(() => { PropertyChanged(this, new PropertyChangedEventArgs(info)); });
}
}
}
}

What we need to have in the AMS storage is a reference to the station ID, name, description, stream URL (location), image URL and the JSON ID that will be subsequently used to query the currently playing track, as well as the previously played content. One way to do this is enable dynamic schema, where I can push items into the store, but I can also access the server through SQL Server Management Studio.

You can get the connection information in the Azure Management Portal.

Back to the fact that I needed to implement my own connection client. Here is what I did:

 public class MobileServicesClient
{
private const string CORE_URL = "https://beem.azure-mobile.net/tables/";
private const string KEY = "";

private WebClient client;

public MobileServicesClient()
{
client = new WebClient();
client.Headers["X-ZUMO-APPLICATION"] = KEY;
client.Headers["Accept"] = "application/json";
}

public void GetAllStations(Action<IEnumerable<Station>> onCompletion)
{
client.DownloadStringCompleted += (s, e) =>
{
IEnumerable<Station> stations = JsonConvert.DeserializeObject<IEnumerable<Station>>(e.Result);
onCompletion(stations);
};
client.DownloadStringAsync(new Uri(string.Concat(CORE_URL, "Station")));
}

public void AddStation(Station station, Action onCompletion)
{
var serializedObject = JsonConvert.SerializeObject(station,
new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore });
string content = serializedObject;

client.Headers[HttpRequestHeader.ContentType] = "application/json";
client.Headers[HttpRequestHeader.ContentLength] = content.Length.ToString();

var uri = new Uri(string.Concat(CORE_URL, "Station"));

client.UploadStringCompleted += (s, e) => {
onCompletion();
};
client.UploadStringAsync(uri, content);
}
}

In this class, I need to define the connection API key (once again, obtained in the Azure Management Portal). Remember that depending on the actions that you will take on the database, you need to make sure that the operation is permitted for the given key.

The requests against the store are performed with the help of a WebClient class, where I am also setting the proper authentication (X-ZUME-APPLICATION) and content headers. When I am simply trying to get the data, the only part that I need to add to the core URL is the table name – in my case, Station. When the response is received, I can deserialize it as IEnumerable<Station> and then bind it anywhere in the application, which ultimately results in this:

2 of 6

Adding data is done with the help of AddStation. The process is pretty much the opposite of how the data retrieval happens. I am serializing a station to JSON and calling UploadStringAsync to perform a POST request. Easy and effective – the new release of Beem will be running on top of AMS.

Careful With Deserialization on Windows Phone – Encoding Matters

Serialization is a process that is prone to errors, especially with a poorly structured data layer. However, that is not always the case and a seemingly normal serialization/deserialization scenario might turn out to produce unexepected results. As I was working on a Windows Phone application, I had this standard routine:

public static void SerializeToFile(object serializationSource, Type serializationType, string fileName)
{
XmlSerializer serializer = new XmlSerializer(serializationType);
MemoryStream targetStream = new MemoryStream();
serializer.Serialize(targetStream, serializationSource);
LocalStorageHelper.WriteData(fileName, targetStream.ToArray());
}

The deserialization would occur like this:

XmlSerializer serializer = new XmlSerializer(typeof(List));
string data = await LocalStorageHelper.ReadData("imagestack.xml");
TextReader reader = new StringReader(data);
var resultingObject = serializer.Deserialize(reader);

But this caused an exception to be thrown, and not just a regular one, but one that would crash the application and break the debugging process.

Looking at the snippet above, it is really hard to tell what the issue is, especially if the XML that is being retrieved from the file is valid. However, upon deserialization there is no place where the string encoding is actually specified, which results in the unexpected behavior that we are seeing. By default, the serialization engine sets the encoding to be UTF-8, which should also be respected when deserializing.

So instead of the deserialization snippet above, you need to use this:

 XmlSerializer serializer = new XmlSerializer(typeof(List));
string data = await LocalStorageHelper.ReadData("imagestack.xml");
MemoryStream memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(data));
var resultingObject = serializer.Deserialize(memoryStream);