I recently wrote about how to Automatically Add Framework Config Classes to the Model Builder. Since writing that post I have come across a situation where I was writing the exact same model class in a different application. That just didn’t smell right to me, since the other applications data access layer had been packaged up by nuget I could simply add a reference to it. That’s all well and good, but I didn’t want to have to go to any special effort to add the model configuration classes I needed to my modelbuilder. So, I’ve created a fairly simple (although it’s taken me a while to get it working how I want it) base class from which I can derive my context classes.

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition.Hosting;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration;
using System.IO;
using System.Linq;
using System.Reflection;

namespace Base.EntityFramework
{
    public abstract class BaseDbContext : DbContext
    {
        protected BaseDbContext() { }
        protected BaseDbContext(string connectionString) : base(connectionString) { }

        private IEnumerable<Type> GetTypes()
        {
            var type = typeof(EntityTypeConfiguration<>);
            return GetType()
                .GetProperties(BindingFlags.Instance | BindingFlags.Public)
                .Where(p => p.PropertyType.IsGenericType &&
                            p.PropertyType.GetGenericTypeDefinition() == typeof(IDbSet<>))
                .Select(p => type.MakeGenericType(p.PropertyType.GetGenericArguments().First()))
                .ToArray();
        }

        private static void LoadAllEntityConfigurationsFromAllAssemblies(DbModelBuilder modelBuilder, IEnumerable<Type> types, string assemblyFilter, IEnumerable<string> namePartFilters)
        {
            var path = Path.GetDirectoryName((new Uri(Assembly.GetExecutingAssembly().CodeBase)).AbsolutePath);
            new DirectoryCatalog(path, assemblyFilter)
                .LoadedFiles
                .Where(x =>
                {
                    var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(x.ToLower());
                    if (fileNameWithoutExtension != null)
                    {
                        var parts = fileNameWithoutExtension.Split(".".ToCharArray());
                        return parts.Any(part => namePartFilters.Any(namePartFilter => part == namePartFilter.ToLower()));
                    }
                    return false;
                })
                .Select(Assembly.LoadFrom)
                .ToList()
                .ForEach(assembly => assembly.GetTypes()
                                         .Where(t => types.Contains(t.BaseType))
                                         .Select(Activator.CreateInstance)
                                         .ToList<dynamic>()
                                         .ForEach(instance => modelBuilder.Configurations.Add(instance)));
        }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            var typesToLoad = GetTypes();
            LoadAllEntityConfigurationsFromAllAssemblies(modelBuilder, typesToLoad, "*.dll", new[] { "Data", "Domain" });
        }

    }
}

and in english ….

  1. Discover which classes for which I need to find and load the relevant EntityTypeConfiguration<> class. This is done with a little bit of reflection in the GetTypes() method.
  2. Use MEF to find the assemblies the config classes might be in. In this case it’s all of them (*.dll), but that could be changed to whatever is best for your situation.
  3. Use the namePartFilters to narrow down the list of assemblies further. In this case I’m looking for anything with “Data” or “Domain” in the filename (after it’s been split by periods).
  4. Load each assembly.
  5. Load any generic EntityTypeConfiguration types for the model classes used within my context class (the one which is derived from this base class).
  6. Instantiate each config class.
  7. Add it to the modelBuilder’s configurations.

Since this is a base class I can get this behaviour whenever I derive from it ….

    public class DomainContext : BaseDbContext, IDomainContext
    {
        public IDbSet<Employee> Employees { get; set; }

        public DomainContext() { }
        public DomainContext(string connectionString) : base(connectionString) { }

        IQueryable<Employee> IDomainContext.Employees { get { return Employees; } }
    }

What this allows me to do is use models from any assembly I am referencing in my application and just add it to my Context. Any configuration class for it will then be automatically added to the model builder.

This seemed appropriate to post as I will soon be entering my 40th year ….

If you are 40, or older, you might think this is hilarious!

When I was a kid, adults used to bore me to tears with their tedious
diatribes about how hard things were. When they were growing up; what with
walking twenty-five miles to school every morning…. Uphill… Barefoot…
BOTH ways. yadda, yadda, yadda

And I remember promising myself that when I grew up, there was no way in
hell I was going to lay a bunch of crap like that on my kids about how hard
I had it and how easy they’ve got it!

But now that I’m over the ripe old age of forty, I can’t help but look
around and notice the youth of today You’ve got it so easy! I mean,
compared to my childhood, you live in a damn Utopia!
And I hate to say it, but you kids today, you don’t know how good you’ve got it!

  1. I mean, when I was a kid we didn’t have the Internet. If we wanted to
    know something, we had to go to the damn library and look it up ourselves,
    in the card catalog!!
  2. There was no email!! We had to actually write somebody a letter – with a
    pen! Then you had to walk all the way across the street and put it in the
    mailbox, and it would take like a week to get there! Stamps were 10 cents!
  3. Child Protective Services didn’t care if our parents beat us. As a
    matter of fact, the parents of all my friends also had permission to kick
    our ass! Nowhere was safe!
  4. There were no MP3′s or Napsters or iTunes! If you wanted to steal music,
    you had to hitchhike to the record store and shoplift it yourself!
  5. Or you had to wait around all day to tape it off the radio, and the DJ
    would usually talk over the beginning and @#*% it all up! There were no CD
    players! We had tape decks in our car. We’d play our favorite tape and
    “eject” it when finished, and then the tape would come undone rendering it
    useless. Cause, hey, that’s how we rolled, Baby! Dig?
  6. We didn’t have fancy crap like Call Waiting! If you were on the phone
    and somebody else called, they got a busy signal, that’s it!
  7. There weren’t any freakin’ cell phones either. If you left the house, you
    just didn’t make a damn call or receive one. You actually had to be out of
    touch with your “friends”. OH MY GOSH !!! Think of the horror… not being
    in touch with someone 24/7!!! And then there’s TEXTING. Yeah, right.
    Please! You kids have no idea how annoying you are.
  8. And we didn’t have fancy Caller ID either! When the phone rang, you had
    no idea who it was! It could be your school, your parents, your boss, your
    bookie, your drug dealer, the collection agent… you just didn’t know!!!
    You had to pick it up and take your chances, mister!
  9. We didn’t have any fancy PlayStation or Xbox video games with
    high-resolution 3-D graphics! We had the Atari 2600! With games like
    ‘Space Invaders’ and ‘Asteroids’. Your screen guy was a little square! You
    actually had to use your imagination!!! And there were no multiple levels
    or screens, it was just one screen… Forever! And you could never win.
    The game just kept getting harder and harder and faster and faster until you
    died! Just like LIFE!
  10. You had to use a little book called a TV Guide to find out what was on!
    You were screwed when it came to channel surfing! You had to get off your
    ass and walk over to the TV to change the channel!!! NO REMOTES!!! Oh, no,
    what’s the world coming to?!?!
  11. There was no Cartoon Network either! You could only get cartoons on
    Saturday Morning. Do you hear what I’m saying? We had to wait ALL WEEK for
    cartoons, you spoiled little rat-bastards!
  12. And we didn’t have microwaves. If we wanted to heat something up, we
    had to use the stove! Imagine that!
  13. And our parents told us to stay outside and play… all day long. Oh,
    no, no electronics to soothe and comfort. And if you came back inside…
    you were doing chores!
  14. And car seats – oh, please! Mom threw you in the back seat and you hung on.
    If you were lucky, you got the “safety arm” across the chest at the last
    moment if she had to stop suddenly, and if your head hit the dashboard, well
    that was your fault for calling “shot gun” in the first place!
  15. See! That’s exactly what I’m talking about! You kids today have got it too
    easy. You’re spoiled rotten! You guys wouldn’t have lasted five minutes
    back in 1970 or any time before!

    Regards,
    The Over 40 Crowd

Received via email from my Dad today!

I’ve just completed a bit of TDD. Proper TDD, I wrote the code to pass the tests and wrote the tests first. Anyway, one thing I needed control over was the concept of “now”. This particular method I was testing/writing deals with DateTime objects.

I needed a way of defining, on a per test basis, what “now” is. There were two steps to achieving this. Firstly, I needed to use an interface to get “now”.

    public interface IDateTimeProvider
    {
        DateTime Now { get; }
    }

So, rather than using DateTime.Now, I use DateTimeProvider.Now.

The second step is creating the mock IDateTimeProvider and using the lazy loading feature of Moq. In this particular case I am using Moq and a test base class …

    [TestFixture]
    public abstract class BaseTest
    {
        private Mock<IDateTimeProvider> _mockDateTimeProvider;
        protected IDateTimeProvider DateTimeProvider { get { return _mockDateTimeProvider.Object; } }

        [SetUp]
        public void Setup()
        {
            _mockDateTimeProvider = new Mock<IDateTimeProvider>();
            Now = DateTime.MinValue;
            _mockDateTimeProvider.Setup(x => x.Now).Returns(() =>
                                                               {
                                                                   if (Now == DateTime.MinValue)
                                                                   {
                                                                       throw new Exception("'Now' property must be set in each [Test] method");
                                                                   }
                                                                   return Now;
                                                               });
        }

        protected DateTime Now { private get; set; }
    }

So, I have a DateTime property, which is evaluated whenever the IDateTimeProvider.Now method is called. I added a little check to make sure the Now property is set in each test. That, coupled with setting Now to DateTime.MinValue means any test which doesn’t set the value will get a helpful message in your chosen test runner.

The crucial part of that code and what makes it really useful is the lazy evaluation, which is done like so …

                                                            () =>
                                                               {
                                                                   if (Now == DateTime.MinValue)
                                                                   {
                                                                       throw new Exception("'Now' property must be set in each [Test] method");
                                                                   }
                                                                   return Now;
                                                               }

I’ve also made the Now and _mockDateTimeProvider private so they cannot be used, forcing the use of the DateTimeProvider.

Here is an example test …

        [Test]
        public void ExampleTest()
        {
            Now = DateTime.Parse("30 Nov 2011 15:00");

            // Arrange

            // Act
            var result = DateTimeProvider.Now;

            // Assert
            Assert.That(result, Is.EqualTo(DateTime.Parse("30 Nov 2011 15:00")));
        }

I’ve explicitly defined the dates in my asserts so it’s a little more obvious what is going on. I’ve also used DateTime.Parse as it’s easier to read. I’ve put the assignment of the Now property before the Arrange section as it’s really a pre-arrange step.

It’s not totally necessary to require Now to be configured explicitly for each test, but I think it makes things a little easier to read for developers coming to this code fresh. It would be possible to have a default value for “Now” but that isn’t very discoverable.

I hope I’m not jumping the gun here, but I have finally found the cause of the severe frame dropping I’ve been experiencing when playing MKVs (specifically 1080p content) on my (not so) new HTPC.

I spent some time investigating yesterday, trying out different codecs. Once I found out about a very useful overlay display in Media Portal. Pressing Shift-1 (ie – !) brings up a raft of information including the name of the audio and video decoders. So, I could really see what Media Portal was using the play the MKV file. It also has a very useful dropped frame count. So, I didn’t have to sit and watch for dropped frames myself.

The first thing I tried was updating the codec package I use (SAF) to the latest version (6.1). This seemed to work for a while, but I still got dropped frames and once it started going wrong it got progressively worse.

Next, I tried out different video decoders (Cyberlink PowerDVD 11, Microsoft DTV, ffdshow DXVA, LAV). All of them stuttered severely.

Next, I tried copying my test file to the local hard drive. Playback was perfect! I then tried playing back the same file (from my Drobo FS) on a different HTPC, playback was perfect! So, this lead me to believe something was wrong with the networking part of the new HTPC.

So, I went on to the gigabyte website and downloaded the latest version of the network adapter driver. After a reboot I played my test MKV file from the Drobo FS, ie – using the network again, and it played back perfectly. RESULT! To make sure I let it play a lot longer than my normal test, still perfect. I then played a couple of more movies and they also played back perfectly. GREAT I thought.

One problem HTPCs often have is that when resuming from sleep mode they don’t quite work perfectly. So, I switched the PC off via my MCE remote and made myself a cup of tea. A few minutes later I switched the PC back on (again via the MCE remote), played the same test movie again and it was perfect! So, I tried a few more movies and left one of them running for over an hour. Again, as near as makes any difference, PERFECTO.

I’m not counting my chickens just yet, I’ve had this happen before. So, I switched it off (via the MCE remote) again, and went to bed.

The next morning …

I switch the PC on with the remote, fire up the test movie … PERFECT! It’s actually still playing as I write this post.

So, all this hassle and many nights of messing around with codecs and power settings and I don’t know what else; And it’s all down to a dodgy network driver, the one on the CD that came with the motherboard no less!

GIGABYTE FAIL

The PC seems to wake up quicker too. What normally happens on a wake up, is the display comes on real quick but there is a delay while it sorts itself out and reconnects to the TV server. I’m not too bothered about that delay actually, but it would be nice not to have it :)

Note: When I say PERFECT, it is still dropping the odd frame. It’s only one in every 6 or 7 thousand frames and barely noticeable. I suspect that is down the the frame rate not being exactly right for 24p content. This is apparently a known issue with the Sandy Bridge CPU/GPUs.

I’ve just recently restored my blog from backups after having tried a few alternative blog engines. Well I’m back on wordpress and blogging again!

In restoring my blog I noticed my very first post from way back in 2005, it was all about my computer rack, which I way very proud of at the time. Looking back, it was all a bit OTT.

Anyway, I reckon it’s about time for an update!

I’ve gone through 2 different rack mount cases since 2005. Both 4U and very roomy. My MediaPortal TV server has been upgraded over the years to the state it is now, with 3 dual DVB-T tuners, a quad core AMD CPU and 4 gigabytes of RAM. Until recently it also had a 4 drive RAID5 in there with a hardware raid card. That is now retired (and soon to be appearing on ebay). It’s been replaced by a Drobo FS with 5 x 1TB hard drives and dual drive redundancy enabled.

My server is now in a Fractal Define XL case, but soon to be moved to a much smaller case because of the recent Drobo purchase no longer requiring me to house all those hard drives. It has also been moved out of the loft into a cupboard downstairs for easier access.

I have also upgraded the two network switches to a single 24-port gigabit switch. I have a separate 3com wireless access point for laptop/tablet use. Once I have moved my main PC out of the rack case it currently resides in, I will be dismantling the home made rack and moving all the network gear into a wall mounted rack case.

I recently started writing a webpage where I wanted some information pre-loaded, without the delay of making an ajax call! What I had was the classic case of the content of one drop down list being controlled by another drop down list.

So, in my controller I loaded up all the data into my model …

public class MyController : Controller
{
    private readonly IMyRepository _myRepository;

    public MyController(IMyRepository myRepository)
    {
        _myRepository = myRepository;
    }

    [HttpGet]
    public ActionResult MyAction()
    {
        var parentList = _myRepository
            .GetParentList()
            .ToList()
            .Select(x => new SelectListItem
                              {
                                  Value = x.ID.ToString(),
                                  Text = x.Description,
                                  Selected = false
                              })
            .ToList();

        var childList = _myRepository
            .GetChildList()
            .ToList()
            .AsQueryable();

         var model = new MyViewModel
                         {
                             ParentList = parentList,
                             ChildList = childList
                         };

        return View(model);
    }
}

The GetChildList() method returns a list of items which have a ParentID

This is what my view looks like …

@model MyViewModel

<div class="MyViewClass">
    <h2>Parent/Child Example using jQuery Linq</h2>

    <label for="ParentList">Parent Item</label>
    @Html.DropDownListFor(model => model.ParentList, null, "Please select a Parent item ...", new { @class = "test" })

    <label for="ChildList">Child Item</label>
    <select id="ChildList"></select>
</div>

and finally, here is the javascript (which is also in the view) …

var children;
$(document).ready(function() {
    children = @Html.Raw(Json.Encode(Model.ChildList)) ;

    $('#ParentList').change(ParentChanged);
});

function ParentChanged() {
    var parentID = parseInt($('#ParentList').val(), 10);
    var childItems = $.Enumerable.From(children)
        .Where(function(x) { return x.ParentID == parentID })
        .Select(function(x) { return { value: x.ID, text: x.Description } })
        .ToArray();
    var html = "<option id='-1'>(All Items)</option>";
    $.each(childItems, function(index, value) {
        html += "<option value='" + value.value + "'>" + value.text + "</option>";
    });
    $('#ChildList').empty().html(html);
}

So, all the work is done in the web browser. The dataset is relatively small so shouldn’t cause any memory issues. Obviously for larger datasets using ajax is the way to go, but for this particular case this solution works great.

I have been “in the process” of building a dedicated home cinema room for some time now. I shall write about that in more detail some other time.

This post is a “progress” update which will make more sense in the fullness of time, after I have written about my progress up to now in more detail.

I have been having difficulty getting my HTPC to stream movies from my server reliably for quite a while now. Somewhere in the order of 4 months! This Friday just gone I decided to take adanage of my 2 year old son’s extra long sleep during the day and do a little experimentation with my network setup.

I use a gigabit wired ethernet network with a 24 port switch and wall plates wherever I can. I suspected the stuttering problems I was having was down to the network somehow. So I tried a few things to eliminate a few possibilities. As it turned out, when I moved my Drobo to a location other than my newly created cloakroom/server room it worked perfectly. Well the few tests I performed worked where they didn’t before. So, it would seem there is some kind of problem with either the network cabling I had pulled through to that room, or the sockets I’d installed in that room.

That was yesterday, today I came to watch a few things in the cinema room and I got stuttering. To a lesser extent than befofe, but still quite bad and certainly not watchable. So, I loaded up Graph Studio to see which decoders/filters/splitters were being used to play back my test movie. As it turned out the audio decoder being used was the MPC MPA Decoder, which I hadn’t set the SPDIF passthru on. So, I did that and it was instantly better, in fact it seemed pretty much perfect to me!

So, I decided to also switch the Media Portal settings back to the defaults for the SAF 6 package I’ve been using. The reason for doing that is that Graph Studio was telling me it was using decoders other than the ones I have configured in the Media Portal configuration, so I figured “what the hell!”

It, still seems fine to me. I’ll continue to perform “extensive testing” (ie – watching plenty of movies)

I should start off by saying this isn’t my code, I copied it from somewhere, but I cannot remember where. So, if you know send me a comment and I’ll update this post accordingly.

One of my pet peeves with entity framework code first, is having to add all my config classes to the modelBuilder within my context, like so….

public class MyContext : DbContext
{
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Configurations.Add(new UserConfig());
        modelBuilder.Configurations.Add(new TitleConfig());
        modelBuilder.Configurations.Add(new UserTitleConfig());
    }
}

But that’s a real PITA. I don’t want to have to remember to add every config class in that method every time I extend my model. It also starts to become untidy very quickly.

namespace MyProject
{
    public class MyContext : DbContext
    {
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            Assembly.GetExecutingAssembly().GetTypes()
                .Where(type => type.Namespace == "MyProject.Config")
                .ToList()
                .ForEach(type =>
                         {
                             dynamic instance = Activator.CreateInstance(type);
                             modelBuilder.Configurations.Add(instance);
                         });
        }
    }
}

That’s better! Now I can forget about that minor detail and get on with creating my model. I’ve used the namespace of the type to limit the scope to just my “config” folder. You may want to do it some other way or even choose not to limit it in any way.

EDIT (13 Dec 2011)

I have since found a better way of filtering the types to load …

                .Where(type => type.BaseType != null &&
                               type.BaseType.IsGenericType &&
                               type.BaseType.GetGenericTypeDefinition() == typeof (EntityTypeConfiguration<>))

Today, I had a requirement to read a bunch of values from an xml file (web.config) and replace some place-holders in another file (asp inc file). I came up with (what I think is) a nice little solution to the problem.

First, I need to read the web.config file …

function readWebConfig
{
    param([string] $fileName)

    $encoding = detectEncoding $fileName
    $content = [IO.File]::ReadAllText($fileName, $encoding)
    $content
}

next I set up an array which I’ll use to process the file with …

function configureReplacements
{
    $list = @(
        ("CONNECTION_STRING", "//configuration/connectionStrings/add[@name='MyConnectionString']/@connectionString"),
        ("GOOGLE_MAPS_API_KEY", "//configuration/appSettings/add[@key='GoogleMapsApiKey']/@value")
    )
    $list
}

to make things a bit more flexible, this could easily be put into a simple text file.

then I process the file …

$replacements = configureReplacements
$webConfigContent = readWebConfig $webConfigFilename

$file = [IO.File]::ReadAllText($filename)
foreach ($replacement in $replacements)
{
    $text = $webConfigContent | Select-Xml $replacement[1]
    $file = $file.Replace('$' + $replacement[0] + '$', $text)
}
[IO.File]::WriteAllText($filename, $file)

I’ve used a convention of surrounding the replacement tags with $ signs

My asp inc file

<script language="VBScript" runat="Server">
Const DATABASE_CONNECTION_STRING = "Provider=SQLOLEDB;$CONNECTION_STRING$"
Const GOOGLE_MAPS_KEY = "$GOOGLE_MAPS_API_KEY$"
</script>

the relevant bits of my web.config file

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <connectionstrings>
        <add name="MyConnectionString" connectionString="Data Source=(local);Initial Catalog=MyDatabase;Integrated Security=SSPI;" providerName="System.Data.SqlClient" />
    </connectionstrings>
    <appsettings>
        <add key="GoogleMapsApiKey" value="YOU_DIDNT_THINK_I_WOULD_GIVE_THAT_AWAY_DID_YOU_!!" />
    </appsettings>
</configuration>

I put all of that into an AfterBuild target in my csproj file and it updates my asp inc file every time I build.

Resulting asp inc file

<script language="VBScript" runat="Server">
Const DATABASE_CONNECTION_STRING = "Provider=SQLOLEDB;Data Source=(local);Initial Catalog=MyDatabase;Integrated Security=SSPI;"
Const GOOGLE_MAPS_KEY = "YOU_DIDNT_THINK_I_WOULD_GIVE_THAT_AWAY_DID_YOU_!!"
</script>

I am in the process of writing a website which has quite a few graphics in them, so I figured it would be nice to just have them all in one PNG file. Which has the nice side effect of reducing the number of requests to my web server.

I came up with (what I think) is a nice piece of javascript and jQuery code to display the images…

HTML

This is the html, which is pretty simple. For the sake of completeness, I have left in a little d-pad imagemap which forms part of the page. The script doesn’t do anything with the imagemap part of the page.

<div class="commands">
    <span class="command back"></span>
    <span class="command info"></span>

    <map id="d-pad" name="d-pad">
        <area shape="circle" coords="200,200,80" onclick="Command('ok');" />
        <area shape="rect" coords="158,14,244,104" onclick="Command('up');" />
        <area shape="rect" coords="158,290,244,380" onclick="Command('down');" />
        <area shape="rect" coords="14,150,102,238" onclick="Command('left');" />
        <area shape="rect" coords="296,150,386,238" onclick="Command('right');" />
    </map>
    <image class="dpad" src='<%= ResolveUrl("~/Content/Images/d-pad.jpg") %>' alt="Site map" usemap="#d-pad">
        <span class="command play"></span>
        <span class="command pause"></span>
        <span class="command stop"></span>
        <span class="command volup"></span>
        <span class="command voldown"></span>
        <span class="command volmute"></span>
    </image>
</div>

The Javascript

Firstly, I set the css for all my “buttons”. The reason for doing the background-image this way is to work around a bug in safari on the iPad.

$(document).ready(function () {
    var buttonsFilename = '< %=ResolveUrl("~/Content/Images/Buttons.png") %>';

    $('.commands .command').css({
        background: 'url(' + buttonsFilename + ')',
        width: '55px',
        height: '55px',
        display: 'inline-block',
        'margin-right': '30px'
    });

Next I set up an array of x-offsets for my images. I got these numbers by loading up my image in Paint.NET and placing my cursor on the left of the desired bit of the image and prefixing a minus sign on the front. All my “buttons” are the same size, so I can set the width and height attributes here too.

var offsets = {
    play: -357,
    pause: -488,
    stop: -425,
    volup: -75,
    voldown: -9,
    volmute: -146,
    back: -888,
    info: -1016
};

The next step is to look through each of the command elements and set the background-position and also wire up a click event handler. It simply uses the index of the array. All javascript objects are effectively associative arrays, so I can simply iterate through the offsets object using the index to find the command element based on the css class. I can also use the index to pass in to the Command javascript function.

$.each(offsets, function (index, value) {
    $('.commands .' + index)
        .css('background-position', value + 'px -2px')
        .click(function () {
            Command(index);
        });
    });
});

The Javascript (in it’s entirety)

function Command(command) {
    $.ajax({
        url: '< %=ResolveUrl("~/Home/Command/")%>',
        type: 'POST',
        data: {
            command: command
        }
    });
}

$(document).ready(function () {
    var buttonsFilename = '< %=ResolveUrl("~/Content/Images/Buttons.png") %>';

    $('.commands .command').css({
        background: 'url(' + buttonsFilename + ')',
        width: '55px',
        height: '55px',
        display: 'inline-block',
        'margin-right': '30px'
    });

    var offsets = {
        play: -357,
        pause: -488,
        stop: -425,
        volup: -75,
        voldown: -9,
        volmute: -146,
        back: -888,
        info: -1016
    };

    $.each(offsets, function (index, value) {
        $('.commands .' + index)
            .css('background-position', value + 'px -2px')
            .click(function () {
                Command(index);
            });
    });
});

So, I can now easily add new buttons into my page by tacking them on the end of my wide image and adding the name to the offsets object.