This happened to me, a few years ago when I purchased a CD Jukebox (one that holds a maximum of 300 CDs), and decided to create an application that would catalog my CDs, register their positions in the jukebox, and even enter the tracks. Also, I wanted to be able to use it from my living room and preferably as a web application, since that's what I was mostly doing at work and also didn't want to be constrained to a Windows client-server application. One last requirement: I didn't want to use anybody else's program like RealPlayer or WindowsMedia Player.
I started with a solid database design using SQL 2005, which I still use today, but that's as far as I got.
A web front end based on Asp.Net would not have been difficult, but the problem was that I didn't want to enter all those tracks by hand (potentially around 4000). Back in those days (early 2006) there were not many options to get those tracks from the internet, let's say via an RSS feed or a web service. So, my idea started collecting dust until recently when I ran into MusicBrainz.org which is a free service that contains and exposes a huge database of CDs and all their information.
An Asp.Net web front end would still do the trick but I wanted to explore more modern technologies such as Silverlight, WCF and Entity Framework. To make a long story short, I have developed a tool that is based on Silverlight, that is hosted via browser, allows me to edit albums (uses WCF to implement self tracking entities) and makes REST-ful web service calls to MusicBrainz to populate the CD tracks with a few clicks.
I am not going to post all the code for this tool here, but I will describe the main steps to have this up and running, plus a few tips for a successful deployment:
- Create a Silverlight application project, and select the defaults. I've chosen the name CDJukebox.
- Visual Studio will create another project called CDJukebox.Web, which is the ASP Net web application that will host your Silverlight application.
- Add a Silverlight class library to your solution and call it 'Entities'.
- Add the Entity Framework to CDJukebox.Web by adding a new item of type "ADO.Net Entity Data Model". Here where I selected my old database from the connection wizard, and selected only 'tables' since I was not using any views or stored procedures.
- The previous step automatically launches the entity designer. From there, right mouse click and select 'Add Code Generation Item', and choose the self tracking generator (under C#, code).
- Select the 'Entities' project and add existing item, select 'Model.tt' from 'CDJukebox.Web' and add as a link.
- Add a reference to 'System Runtime Serialization' to project 'Entities'.
Your project solution explorer should look like this:
At this point, you have all the necessary plumbing to start building a Silverlight front end which will communicate with the database back-end using the WCF service calls. So we'll need to create a few CRUD methods on DirectoryService.svc.cs:
using System;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.Collections.ObjectModel;
namespace CDJukebox.Web
{
[ServiceContract(Namespace = "")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class DirectoryService
{
[OperationContract]
public ObservableCollection<ALBUM> GetAlbums()
{
using (cdsEntities context = new cdsEntities())
{
var query = from c in context.ALBUMs
select c;
var result = new ObservableCollection<ALBUM>();
foreach (ALBUM currAlbum in query)
{
result.Add(currAlbum);
}
return result;
}
}
[OperationContract]
public ObservableCollection<ALBUM> UpdateAlbums(ObservableCollection<ALBUM> albums)
{
using (cdsEntities context = new cdsEntities())
{
foreach (ALBUM currentAlbum in albums)
{
if (currentAlbum.ChangeTracker.State == ObjectState.Modified)
{
// Have to fetch the album and see if anyone already changed it
var freshAlbum = context.ALBUMs.Single(x => x.al_pk == currentAlbum.al_pk);
// synchronize values
freshAlbum.al_iYear = currentAlbum.al_iYear;
if (freshAlbum.al_tLastUpdated == currentAlbum.al_tLastUpdated)
{
freshAlbum.al_tLastUpdated = DateTime.Now;
context.ALBUMs.ApplyCurrentValues(freshAlbum);
}
....
Once you have completed the implementation of your service, you need to build you Silverlight pages that will consume this service. There's plenty of documents on this, so I am not going to cover that, only note that these service calls are asynchronous; therefore, your programming style has to accommodate to that. To illustrate this style change, I am listing the code that runs when the Silverlight page loads, which currently fetches all albums in my collection:
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
if (!System.ComponentModel.DesignerProperties.GetIsInDesignMode(this))
{
//
_master = this.Resources["CDJukeboxViewSource"] as CollectionViewSource;
// set events
directoryService.GetAlbumsCompleted += new EventHandler<GetAlbumsCompletedEventArgs>(directoryService_GetAlbumsCompleted);
directoryService.UpdateAlbumsCompleted += new EventHandler<UpdateAlbumsCompletedEventArgs>(directoryService_UpdateAlbumsCompleted);
directoryService.GetArtistCompleted += new EventHandler<GetArtistCompletedEventArgs>(directoryService_GetArtistCompleted);
directoryService.GetTracksCompleted += new EventHandler<GetTracksCompletedEventArgs>(directoryService_GetTracksCompleted);
directoryService.AddTracksCompleted += new EventHandler<System.ComponentModel.AsyncCompletedEventArgs>(directoryService_AddTracksCompleted);
// Load albums
directoryService.GetAlbumsAsync();
}
}
void directoryService_GetAlbumsCompleted(object sender, GetAlbumsCompletedEventArgs e)
{
if (e.Error != null)
{
MessageBox.Show(e.Error.Message);
}
else
{
if (e.Result != null)
{
SetDirectory(e.Result);
}
else
{
MessageBox.Show("Observable Colletion is NULL" + DateTime.Now.ToShortTimeString());
}
}
}
void SetDirectory(ObservableCollection<ALBUM> newDirectory)
{
var selectedIndex = AlbumCombobox.SelectedIndex;
if (AlbumCombobox.SelectedIndex == -1 && newDirectory != null && newDirectory.Count > 0)
{
selectedIndex = 0;
}
_directory = newDirectory;
_master.Source = _directory;
AlbumCombobox.SelectedIndex = selectedIndex;
}
Finally, the data-entry-saving feature was implemented as a web service call to MusicBrainz, that has a pretty solid documentation (http://musicbrainz.org/doc/XMLWebService). Since my search is based on artist and album name, I only needed to create a string template with the right address, make the service call and finally parse through the XML response and eventually create the tracks for my albums:
#region variables
public string address = "http://musicbrainz.org/ws/1/track/?type=xml&artist={0}&release={1}";
#endregion
private void FetchTracks(string artistName, string albumName)
{
string uri = BuildURI(artistName, albumName);
clientService.OpenReadAsync(new Uri(uri));
}
private string BuildURI(string artistName, string albumName)
{
return string.Format(address, artistName, albumName);
}
Four final recommendations that could potentially save you some time:
- Don't forget to register ASP.NET 4.0 using aspnet_regiis (first uninstall using parameter -ua, then install using -i -enable)
- Set your virtual directory to ASP 4.0
- Give user ASPNET at least user access in SQL.
- Don't forget to add the files CrossDomain.xml and ClientAccessPolicy.xml wherever your service lives.
Good luck.

