Refactorable Settings in Visual Studio, Part 2

(Part 1, Part 3)

So how do we address the refactoring problem with generated settings? We need some way to prevent code like this:

Settings.Default.SettingChanging +=
    (sender, eventArgs) =>
    {
        // The name of the property is no longer "AutoCheckUpdates"
        // So the code in this 'if' will *never* happen
        if (eventArgs.SettingName == "AutoCheckUpdates")
        {
           // do something
        }
    };

from making it into the world.

Ideally, we'd have handlers which break at compile time; that's our surest barrier to releasing broken code. (This is a problem which could be solved with automated tests, but that's a cumbersome solution with its own maintenance challenges, and it's error-prone.)

So we want a way to compare SettingChangingEventArgs.SettingName which will break if the name of a setting changes. The simplest way to make this happen would be to have a set of constants defined for each property name, so that instead of

if (eventArgs.SettingName == "AutoCheckUpdates")

we'd have

if (eventArgs.SettingName == SettingsName.AutoCheckUpdates)

The presumption being that if the settings code were regenerated because the AutoCheckUpdates setting name was changed, SettingsName.AutoCheckUpdates would no longer be a valid constant, and we'd have a compiler error.

At first you might be really encouraged by the fact that the generated settings class is partial - this means it should be dead simple to tack a set of constants onto class. In fact, it's as simple as

partial class Settings {

    public static class SettingsNames
    {
        public const string AutoCheckUpdates = "AutoCheckUpdates";
    }
}

This is awesome - now all we have to do is generate this little partial class whenever Settings.settings changes and we're good to go! We can just leave the rest of the settings generation code alone.

This turns out to be much, much more difficult than it sounds.

The code generation part is actually pretty easy, especially with T4 templates. I'd never written a T4 template in my life and I was able to whip up one to generate the necessary partial class with just an hour of googling and coding. The hard part, it turns out, is getting the partial class to be generated when the settings file changes.

Aside from the Custom Tool option in Visual Studio, there's really no good way to trigger any sort of processing when a file changes. (It's possible that this can be achieved with a Visual Studio macro, but that struck me as a very awkward and difficult to deploy solution, so I didn't explore it too deeply). And since the Custom Tool slot is already taken up by the SettingsSingleFileGenerator, there's no good place to trigger whatever tool we want to use to generate our little partial class.

You can do it with a separate T4 template, but you'd have to remember to run the T4 custom tool (or open and save the template file) every time you change the settings file. Additional human-driven steps are exactly what we're trying to avoid, so that's a non-starter.

It is possible to automate the T4 template generation via a pre-build event (by running the templating engine from the command line), but that runs us into another complexity: I either have to find a way to not run that particular build step on my CI server, or I need to have the T4 command line tools installed on my CI server. I'd rather avoid polluting my CI server and I'm not comfortable having the two build processes be different.

At this point, we're kind of painted into a corner. We need one single custom tool we can run when the settings file changes to generate both the original settings code and our new set of constants. In other words, we're pretty much stuck with writing our own custom code generator.

As it happens, in the course of figuring all this out I wrote two. I'll talk about each one next time.