Refactorable Settings in Visual Studio, Part 3

(Part 1, Part 2)

I wrote two different solutions to this problem. The first one emerged as I was exploring T4 templates as a possible solution. My first attempt fell short because I didn't have an acceptable way to run the T4 template when the settings file changed. I turns out that if you have the T4 Toolbox extension installed, you can set the custom tool for any file to T4Toolbox.ScriptFileGenerator1 and the extension will generate a blank .tt file. You can then fill it in with your code generation template and the template will be run whenever the file changes.

With the extension installed, replace SettingsSingleFileGenerator with T4Toolbox.ScriptFileGenerator, and then replace the contents of you .tt file with the template in this gist. The generated code will basically look exactly like what comes out of SettingsSingleFileGenerator, but with the addition of the settings names as constants.

If you're already using the T4 Toolbox, this is absolutely the method I'd recommend. And if you're already using T4 in your project, you might as well install the toolbox for the Intellisense and highlighting features2.

T4, by the way, is amazing. I had to write a couple of code generators about a decade ago (before T4 was a thing), and it's astounding how much easier it is with T4.

But what if you're not using T4 and/or don't want to install the T4 toolbox? I was kind of uncomfortable with the idea of having the toolbox installed when generating this settings code was literally the only thing I was using T4 for on my project. It just seemed like overkill. So I dusted off my decade-old code-generation skills and took a shot at writing my own SingleFileGenerator.

Last time I did this (in 2004), it was a nightmare (which is why I explored every other possible solution first). Getting it installed required a lot of manual deployment of libraries in the right location and editing of registry entries; testing required either another machine with VS installed or risking your development installation. Luckily, things are a lot easier nowadays. I found a great example project on creating a Single File Generator as a VS extension, which makes for a much easier deployment process. And you can debug them in a separate experimental instance of VS without any risk. Progress!

The result is the Refactorable Settings Generator extension. Install it and replace SettingsSingleFileGenerator with RefactorableSettingsGenerator.

example of settings gui

Again, the generated code will basically look exactly like what comes out of SettingsSingleFileGenerator, but with the addition of the settings names as constants.

Whichever solution you choose, your handlers can now be written like this:

Settings.Default.SettingChanging +=
    (sender, eventArgs) =>
    {
        if (eventArgs.SettingName == Settings.SettingsNames.AutoCheckUpdates)
        {
           // do something
        }
    };

And they will helpfully fail to compile when you change the names of the settings.

You can check out the code for the Refactorable Settings Generator on GitHub (and report issues or suggest changes, if you like).

Happy refactoring!


  1. At least, that's what it's called for Visual Studio 2012 and up; the tool might have a different name in VS 2010. 

  2. If you're using a dark theme in VS, you're going to want to go to the color options and change the background of Text Template Code Block, though - with the default colors the T4 code becomes unreadable. 

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.

Refactorable Settings in Visual Studio, Part 1

(Part 2, Part 3)

One of the really nice features of Visual Studio when developing desktop applications are the built-in Application Settings generators. Basically, VS gives you an easy way to handle application-level and user-level settings and preferences in your application. The built-in interface, generators, and base classes handle several things for you:

  1. Reading and writing settings to disk - all of the serialization/deserialization is handled for you, as are the correct locations for the settings.
  2. User settings vs. application settings - you don't have to worry about keeping separate settings files per user (or where on disk to put them); all of that is handled for you.
  3. Persisting settings across application updates - this pretty much just works.

Creating settings is easy - you can either use the built-in editor (which is convenient, though I admit it could use some work) or edit the XML by hand. VS provides an interface for generating the initial XML (in the project property pages), and once it's set up any changes to the XML document trigger a custom tool which generates the C# code for your settings class.

example of settings gui

Don't let all the "Windows Forms" stuff in the MSDN link above fool you - the settings classes are perfectly usable in any type of non-web application (console, Windows Service, WPF, whatever). In fact, they work really well with XAML binding (because ApplicationSettingsBase implements INotifyPropertyChanged). Here's a chunk of XAML showing a boolean setting AutoCheckUpdates bound to a checkbox:

<StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
    <CheckBox 
        IsChecked="{Binding Source={x:Static local:Settings.Default}, Path=AutoCheckUpdates, Mode=TwoWay}" />
    <TextBlock Text="Automatically check for updates" />
</StackPanel>

The binding is set to two-way, which means that the checkbox will display the current value from Settings and that changes to the checkbox will update the value in the Settings instance. This makes settings dialogs ridiculously easy to create.

However, the generate Settings classes do have a significant weakness: the events are not refactor-friendly. Let me explain with an example. We'll use the AutoCheckUpdates setting from the XAML example. Here's what that setting might look like in the code generated by Visual Studio:

[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("False")]
public bool AutoCheckUpdates {
    get {
        return ((bool)(this["AutoCheckUpdates"]));
    }
    set {
        this["AutoCheckUpdates"] = value;
    }
}

For most refactoring purposes, this is fine. Let's say that elsewhere I have the following code:

var check = Settings.Default.AutoCheckUpdates;

A few months later I decide to change the name of the setting to make it more descriptive (this is a contrived example; don't worry whether the new name is actually more descriptive). So I change the setting name to CheckForUpdateOnStart.

example of settings gui

From a refactoring standpoint, we don't have any problem because the line of code above will now fail to compile; I'll have to change it to

var check = Settings.Default.CheckForUpdateOnStart;

in order for the project to build. This is behavior is refactor-friendly. All is well.

But there's a potential issue lurking in the generated settings classes. The problem lies with how ApplicationSettingsBase handles the SettingChanging event. SettingChanging is useful for things like validation - it fires before the setting change is committed and supports cancellation. If we wanted something to occur when the user changes the AutoCheckUpdates setting, we could do something like this:

Settings.Default.SettingChanging +=
    (sender, eventArgs) =>
    {
        if (eventArgs.SettingName == "AutoCheckUpdates")
        {
            // Validate here - maybe this setting is 
            // something the user needs to confirm or see
            // a warning about
        }
    };

The eventArgs here are of type SettingChangingEventArgs, which only gives us the SettingName property with which to identify the setting which is being changed. This becomes a major problem for refactoring. Let's again say that we're changing the name of the setting to CheckForUpdateOnStart.

The code in the event handler still compiles. It will never work - the code inside the if block will never be hit - but it will compile just fine.

This is a problem if you want to be able to safely refactor code which deals with the SettingChanging event. Ideally, we'd like to be able to validate (or otherwise react to) changing settings without having to worry that a hard-coded string is going to bite us when we change the name of a setting. In the next post I'll talk about some ways to make that possible.