After listening to another ARCast on the drive home on Friday, I finally decided to bite the bullet and try coding up the "server" side of Comicster (the online database that Comicster connects to to share information from your local collection) from scratch using "service oriented" techniques.

I resolved to do as much as possible using declarative methods - dragging and dropping rather than writing code. To that end, I was able to define my database (using Sql Server 2005 Express Edition) with a data diagram and define my typed DataSet with the DataSet designer in Visual C# Express.

Once those two steps were done, I set about defining the classes that would represent the objects sent back and forth from the "service layer" to the calling client. This I did in code (I'm not sure that Visual C# Express even allows you to define classes visually, but I was more comfortable coding them anyway). I ended up with these classes:

  • Publisher
  • Title
  • Issue
  • Creator
  • Character
  • Appearance (links Characters to Issues)
  • Job (links Creators to Issues)

These are pretty much a one-to-one mapping to the tables in the database, but that's by-the-by. Each of these classes represents a "data contract" between the client and the server.

The classes also have nested relationships. For example, "Title" has a property called "Issues" which is an array of Issue objects. Likewise, "Issue" has a "Cast" property which is an array of Appearances, and a "Crew" property which is an array of Jobs.

Next I started work on the "services". Eventually these would be ASP.NET web services or WCF services, but for now I am just putting them into a normal .NET assembly.

As a first step I tried an easy one - "PublisherService". This class has a single method, "GetPublishers", which returns an array of Publisher objects - one for each publisher in my database. This, as you'd imagine, was pretty easy. Fill the DataTable, iterate through the rows, create Publisher objects and add 'em to a list, then return list.ToArray().

Next I tried one a little more complex: TitleService. This class has a method called "FindTitles" which returns an array of titles whose name partially matches the given search text. Of course, the only really complicated part was defining the queries in the TableAdapter to get the right rows.

Eventually I realized that returning a huge array of titles, each with every issue in that title, was a lot of work to perform and a lot of data to return, so I tried defining an enum (called TitleDetailRequired) as a way for the client to tell the service how much information to return. If you don't specify TitleDetailRequired.Issues as part of your call to FindTitles, then the issues aren't returned. This sped up the calls tremendously.

Passing both a search term and a detail-required enum to the FindTitles method, though, seemed wrong, so I decided to implement a class called FindTitlesMessage which had these two pieces of information as properties. Now my FindTitles method takes one of these "message" objects as its only parameter. This means I can play with the definition of that class without having to change the method signatures of any methods that use it.

So after some work I ended up with services for each major object in my system. Designing the client to test the calls was the easiest part of all:

  1. Add a form to your project
  2. Click "Data|Show Data Sources" to bring up the Data Sources window
  3. Bring up the "new data source" wizard and choose "object" as the type
  4. Drill into Comicster.Data (the assembly holding my data contract objects) and choose "Title"
  5. Now you have a "Title" data source in your Data Sources window. Drag that onto the form and watch as it creates for you a DataGridView and BindingSource component!

The last steps, then is adding a TextBox and Button so you can type in the text you want to search for, and some easy code like this in the Button's Click event handler:

TitleService svc = new TitleService();
FindTitlesMessage msg = new FindTitlesMessage();
msg.SearchText = textBox1.Text;
msg.DetailRequired = TitleDetailRequired.None;
titleBindingSource.DataSource = svc.FindTitles(msg);

Pretty straight forward! Now when you click the button, the grid is populated with a list of titles matching the text you've entered.

The only thing bugging me at this point was that the "Publisher" column in the grid was just showing "Comicster.Data.Publisher", because the Title class' "Publisher" property is pointing to a Publisher class rather than a string or whatever. The fix for this was to handle the grid's "CellFormatting" event and tell it how to format that particular type. Here's that code:

if (e.Value is Comicster.Data.Publisher)
{
e.Value = (e.Value as Comicster.Data.Publisher).Name;
}

I'll post more about this little project as work progresses. Needless to say I'm very excited about it. It really feels like I'm finally doing things the "right way". In time I will download the .NET 3.0 bits and try my hand at converting it to WCF - assuming the integration with C# Express works ok.