Parsing Last.fm Web Services' artist.getSimilar with C# and LINQ to XML

The following covers how to parse the XML response of artist.getSimilar, from Last.fm's Web Services.

Setup and assumptions

The first step is sign up for a free API account at Last.fm.

You'll also need to target .NET Framework 3.5 when you setup your project, so as to access LINQ functionality.

When writing the steps listed below, I was working on a Windows Forms Application, but the steps should be the same, or very similar, for other project types.

Creating the base class

The first thing I've done is created a new class file in my project called Lastfm.cs, resulting in the following.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace JamesRSkemp.WebServices { class Lastfm { private const string LastFmApiKey = “EnterYourApiKeyHere”;

}

}

We'll add a new method to the Lastfm class to return the base Url we'll need to make Web service requests.

		/// <summary>
		/// Get the base Url that we'll use to make Web service requests.
		/// </summary>
		/// <returns>The base Url to use to make Web service requests.</returns>
		static private string GetBaseRequestUrl() {
			string baseUrl = "http://ws.audioscrobbler.com/2.0/?api_key=" + LastFmApiKey;
			return baseUrl;
		}

Next we'll create a method to make a request to a Web service and return the full response.

		/// <summary>
		/// Gets the data from an HTTP request.
		/// </summary>
		/// <param name="requestUrl">The full Url of the request to make.</param>
		/// <returns>Returns a string with the text returned from the request.</returns>
		private string GetServiceResponse(string requestUrl) {
			string httpResponse = "";
		HttpWebRequest request = (HttpWebRequest)WebRequest.Create(requestUrl);
		request.Timeout = 15000;
		HttpWebResponse response = null;
		StreamReader reader = null;

		try {
			response = (HttpWebResponse)request.GetResponse();
			reader = new StreamReader(response.GetResponseStream());

			httpResponse = reader.ReadToEnd();
		} finally {
			if (reader != null) {
				reader.Close();
			}
			if (response != null) {
				response.Close();
			}
		}

		return httpResponse;
	}</code></pre>

The following references must also be added.

using System.Net;
using System.IO;

When we use this class, we want to have our data returned in an easy to use format. For ease, we'll have it return a DataTable. We'll have to add the appropriate reference first.

using System.Data;

We can then began our method as follows.

		public DataTable GetSimilarArtists(string artistName) {
			if (String.IsNullOrEmpty(artistName)) {
				throw new Exception("Artist name must be populated.");
			} else {
				string requestUrl = GetBaseRequestUrl();
				requestUrl += "&method=artist.getSimilar&artist=" + System.Web.HttpUtility.UrlEncode(artistName.Trim());
			string serviceResponse = GetServiceResponse(requestUrl);

			DataTable similarArtists = new DataTable();
			
			// TODO

			return similarArtists;
		}
	}</code></pre>

If we were to make a request now, we'd see that the data returned is formatted similar to the following, for a request for Bruce Springsteen. (For ease and sanity, data truncated.)

<?xml version="1.0" encoding="utf-8"?>
<lfm status="ok">
<similarartists artist="Bruce Springsteen">
<artist>
    <name>Bruce Springsteen & The E Street Band</name>
    <mbid>5a1283bf-81d5-4700-8919-683eeaaf2beb</mbid>
    <match>100</match>
    <url>www.last.fm/music/Bruce%2BSpringsteen%2B%2526%2BThe%2BE%2BStreet%2BBand</url>
    <image size="small">http://userserve-ak.last.fm/serve/34/8415485.jpg</image>
    <image size="medium">http://userserve-ak.last.fm/serve/64/8415485.jpg</image>
    <image size="large">http://userserve-ak.last.fm/serve/126/8415485.jpg</image>
    <image size="extralarge">http://userserve-ak.last.fm/serve/252/8415485.jpg</image>
    <image size="mega">http://userserve-ak.last.fm/serve/500/8415485/Bruce+Springsteen++The+E+Street+Band+estreet.jpg</image>
    <streamable>1</streamable>
</artist>
[...]
</similarartists></lfm>

We'd first need to add a reference so that we can parse through the returned response.

using System.Xml.Linq;

We can now create our LINQ to XML query to access the similar artist's name and match percentage.

				var xmlResponse = XElement.Parse(serviceResponse);
			// Parse through the returned Xml for the name and match value for each similar artist.
			var artists = from artistsSimilar in xmlResponse.Descendants("artist")
						  select new {
							  name = artistsSimilar.Element("name").Value,
							  match = artistsSimilar.Element("match").Value
						  };</code></pre>

Next we can create the DataTable that we'll use to store the name and math values.

				DataTable similarArtists = new DataTable();
				similarArtists.Columns.Add("Artist");
				similarArtists.Columns.Add("Match", System.Type.GetType("System.Double"));

Finally we can loop through each result returned from our LINQ to XML query, adding a new row to the table, for each.

				if (artists.Count() > 0) {
					DataRow artistsRow;
					foreach (var artist in artists) {
						artistsRow = similarArtists.NewRow();
						artistsRow["Artist"] = artist.name;
						artistsRow["Match"] = artist.match;
						similarArtists.Rows.Add(artistsRow);
					}
				}

Finally we return the populated DataTable.

				return similarArtists;

Assuming a Windows Form Application with a text box (textBox1), a DataGridView (dataGridView1), and a button (button1), we could do the following (assuming the appropriate references have been added).

		private void button1_Click(object sender, EventArgs e) {
			if (!String.IsNullOrEmpty(textBox1.Text)) {
				Lastfm lastFmRequest = new Lastfm();
			DataTable results = lastFmRequest.GetSimilarArtists(textBox1.Text);

			dataGridView1.DataSource = results;
		} else {
			MessageBox.Show("You must enter an artist to continue.");
			textBox1.Focus();
		}
	}</code></pre>

Taken together, that results in the following. (Download JamesRSkemp.WebServices.Lastfm.cs.)

/*
Created by James Skemp - http://jamesrskemp.com/
Version 1.0
More information at http://strivinglife.com/words/post/Parsing-Lastfm-Web-Services-artistgetSimilar-with-C-and-LINQ-to-XML.aspx
Shared under a Creative Commons Attribution 3.0 United States License - http://creativecommons.org/licenses/by/3.0/us/
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.IO;
using System.Data;
using System.Xml.Linq;

namespace JamesRSkemp.WebServices { class Lastfm { /// <summary> /// Key used to access Last.fm Web services. /// </summary> private const string LastFmApiKey = “EnterYourApiKeyHere”;

	/// &lt;summary&gt;
	/// Return artists similar to the one passed, with a match percentage.
	/// &lt;/summary&gt;
	/// &lt;param name="artistName"&gt;The name of the artist to use for the request.&lt;/param&gt;
	/// &lt;returns&gt;DataTable with artist names and match percentage, as a Double.&lt;/returns&gt;
	public DataTable GetSimilarArtists(string artistName) {
		if (String.IsNullOrEmpty(artistName)) {
			throw new Exception("Artist name must be populated.");
		} else {
			string requestUrl = GetBaseRequestUrl();
			requestUrl += "&amp;method=artist.getSimilar&amp;artist=" + System.Web.HttpUtility.UrlEncode(artistName.Trim());

			string serviceResponse = GetServiceResponse(requestUrl);

			var xmlResponse = XElement.Parse(serviceResponse);

			// Parse through the returned Xml for the name and match value for each similar artist.
			var artists = from artistsSimilar in xmlResponse.Descendants("artist")
						  select new {
							  name = artistsSimilar.Element("name").Value,
							  match = artistsSimilar.Element("match").Value
						  };

			DataTable similarArtists = new DataTable();
			similarArtists.Columns.Add("Artist");
			similarArtists.Columns.Add("Match", System.Type.GetType("System.Double"));

			if (artists.Count() &gt; 0) {
				DataRow artistsRow;
				foreach (var artist in artists) {
					artistsRow = similarArtists.NewRow();
					artistsRow["Artist"] = artist.name;
					artistsRow["Match"] = artist.match;
					similarArtists.Rows.Add(artistsRow);
				}
			}

			return similarArtists;
		}
	}

	/// &lt;summary&gt;
	/// Get the base Url that we'll use to make Web service requests.
	/// &lt;/summary&gt;
	/// &lt;returns&gt;The base Url to use to make Web service requests.&lt;/returns&gt;
	private string GetBaseRequestUrl() {
		string baseUrl = "http://ws.audioscrobbler.com/2.0/?api_key=" + LastFmApiKey;
		return baseUrl;
	}

	/// &lt;summary&gt;
	/// Gets the data from an HTTP request.
	/// &lt;/summary&gt;
	/// &lt;param name="requestUrl"&gt;The full Url of the request to make.&lt;/param&gt;
	/// &lt;returns&gt;Returns a string with the text returned from the request.&lt;/returns&gt;
	private string GetServiceResponse(string requestUrl) {
		string httpResponse = "";

		HttpWebRequest request = (HttpWebRequest)WebRequest.Create(requestUrl);
		request.Timeout = 15000;
		HttpWebResponse response = null;
		StreamReader reader = null;

		try {
			response = (HttpWebResponse)request.GetResponse();
			reader = new StreamReader(response.GetResponseStream());

			httpResponse = reader.ReadToEnd();
		} finally {
			if (reader != null) {
				reader.Close();
			}
			if (response != null) {
				response.Close();
			}
		}

		return httpResponse;
	}
}

}

Advanced error handling is missing, but this should give you a basic idea of how you can go about easily accessing Last.fm's Web Services, and parsing returned data.

Final thoughts

Questions? Comments? Concerns? Please add a comment below.

Updated 9/12/2009, 15:55: I was doing it in my implementation, but added simple check for a populated textBox1 to the above code.

All content copyright 1999-2021 James Skemp, unless otherwise noted. This work is licensed under the Creative Commons License Attribution-Noncommercial-No Derivative Works 3.0. The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway. Privacy Policy. For more information, contact the administrator at strivinglife [at] gmail [dot] com.

Generated using Hugo and the Phlat Theme.