Environment variables and command line arguments are both ways for the user to specify run-time settings to a particular application. .NET provides several more ways, including OS favorites like the Registry and special folders, as well as new ways like .config files and isolated storage. Types of SettingsWhen saving settings, you should consider several different localities of settings:
Localities and PermissionsDifferent storage mechanisms support different localities (as well as other characteristics, such as whether they can be written to as well as read from). In addition, different localities require different user permissions. For example, writing application settings to the Registry requires Administrator group permissions, something you cannot assume that your user has. Before shipping an application that needs to read or write settings, you should test it under the most restricted set of permissions that your users could have. .config Files.NET provides .config files to serve as a read-only location for text-based application settings. A .config file is a file placed in the same folder as the application and having the same name as the application except for a .config extension. For example, the .config file associated with foo.exe would be named foo.exe.config. .NET itself uses .config files for all kinds of things, such as resolving assemblies and assembly versions. [8]
You can add a new .config file to your VS.NET project by right-clicking on the project in Solution Explorer and choosing Add Add New Item Text File and naming the file "app.config" (without the quotation marks). This action will add an empty .config file to your project and, when your project is built, will copy and rename the app.config file to the output folder alongside your application. A minimal .config file looks like this: <configuration> </configuration> In addition to the .NET-specific settings, .config files can be extended with custom XML sections as designated with uniquely named elements. One general-purpose custom section built into .NET is designated with the element named appSettings. For example, the following .config file contains a custom value for pi (in case the 20 digits provided by System.Math.Pi just isn't enough): <configuration> <appSettings> <add key="pi" value="3.141592653589793238462" /> </appSettings> </configuration> Each .config section has a specific section reader that knows how to read values from that section. These section readers can be defined in an application's .config file or in the systemwide machine.config, as shown here for the appSettings section reader: <configuration> <configSections> ... <section name="appSettings" type="System.Configuration.NameValueFileSectionHandler, ..." /> ... </configSections> ... </configuration> A section reader is a class that implements IConfigurationSectionHandler and is registered with an entry in the configSections section of a .config file. For example, the NameValueFileSectionHandler class knows how to read a section in the appSettings format and return a NameValueCollection from the System.Collections.Specialized namespace. However, instead of creating an instance of NameValueFileSectionHandler yourself, it's more robust to use the ConfigurationsSettings class (from the System.Configuration namespace) to map the name of the section to a section reader for you: Imports System.Configuration Imports System.Collections.Specialized ... Shared Sub Main() Dim settings As NameValueCollection = _ CType(ConfigurationSettings.GetConfig("appSettings"), _ NameValueCollection) ... End Sub The ConfigurationSettings class finds the appropriate section handler. The section handler then looks in the current app configuration data for the appSettings section ( parts of which can be inherited from machine.config), parses the contents, builds the NameValueCollection, and returns it. Because different section handlers can return different data types based on the data provided in their sections, the GetConfig method returns an object that must be cast to the appropriate type. As a shortcut that doesn't require the cast, the ConfigurationSettings class provides built-in support for the appSettings section via the AppSettings property: Shared Sub Main() Dim settings As NameValueCollection = _ ConfigurationSettings.AppSettings MessageBox.Show(settings("pi")) End Sub When you've got the settings collection, you can access the string values using the key as an indexer key. If you'd like typed data (pi is not much good as a string), you can manually parse the string using the type in question. Alternatively, you can use the AppSettingsReader class (also from the System.Configuration namespace) to provide typed access to the appSettings values: Shared Sub Main() ' Parse the value manually Dim settings As NameValueCollection = _ ConfigurationSettings.AppSettings Dim pil As Decimal = Decimal.Parse(settings("pi")) ' Let AppSettingsReader parse the value Dim reader As AppSettingsReader = New AppSettingsReader() Dim pi2 As Decimal = CType(reader.GetValue("pi", _ GetType(Decimal)), Decimal) ... End Sub The AppSettingsReader class's GetValue method uses .NET type conversion classes to do its work, making things a bit easier for you if your application's .config file uses different types. Dynamic PropertiesIf you'd like even easier access to values from the appSettings section of the .config file, you can bind a property of a form or control to a value by using the Property Browser and dynamic properties. A dynamic property is a property value that's pulled from the .config file, with the extra benefit that WinForms Designer writes the reader code for you in InitializeComponent. For example, to bind the Opacity property of a form to a value in the .config file, you bring up the properties for the form and press the " " button under the Advanced property of the DynamicProperties item, as shown in Figure 11.6. Figure 11.6. Dynamic Properties in the Property Browser
In the Dynamic Properties dialog, check the box next to Opacity and notice the Key mapping, as shown in Figure 11.7. Figure 11.7. Dynamic Properties Dialog
The Key mapping defaults to <objectName>.<propertyName>, but you can name it whatever you like. When you press the OK button and open the project's app.config, you'll notice a new key in the appSettings section: <?xml version="1.0" encoding="Windows-1252"?> <configuration> <appSettings> <add key="Form1.Opacity" value="1" /> <add key="pi" value="3.141592653589793238462" /> </appSettings> </configuration> Similarly, each dynamic property has a little document icon next to it in the Property Browser, as shown in Figure 11.8. Figure 11.8. Opacity Marked as a Dynamic Property
When a property is marked as dynamic, the Designer writes the value you set in the Property Browser to the app.config file instead of to InitializeComponent. Subsequently, InitializeComponent will read the property from the .config: Sub InitializeComponent() Dim configurationAppSettings As AppSettingsReader = _ New AppSettingsReader() ... Me.Opacity = _ CType(configurationAppSettings.GetValue( _ "Form1.Opacity", GetType(System.Double)), System.Double) ... End Sub Now, when your application is deployed and your power users want to change the Opacity of the form, they can crack open the .config file with any text editor and have at it. Unfortunately, if the power user removes the Opacity setting or gives it an invalid format, the application will throw an exception at run time, and the InitializeComponent method won't do anything to deal with it. If you'd like to guard against that, you need to provide a UI for your users to set their preferences that is more robust than Notepad. And if that's the case, .config files are not for you. There is no API in .NET for writing .config files, only for reading them. This makes .config files effectively read-only for applications (although read/write for humans is facile in XML). [9]
The RegistryThe Registry, on the other hand, has been the place to keep read/write application settings and roaming user settings from Windows 3.1 through Windows NT (it has fallen out of favor in more recent versions of Windows). The Registry gives you hierarchical, machinewide storage of arbitrary name/value pairs split into application and roaming user localities based on the path to the value. The Registry Editor (regedit.exe) is a built-in tool for setting and updating Registry values, [10] as shown in Figure 11.9.
Figure 11.9. The Registry Editor
The Registry is used a lot by Win32 applications, including the file Explorer shell, so you can find yourself reading and writing Registry values regardless of whether you use it to store your own application's settings. For example, to use the Registry to associate a particular file extension with your application would use the RegistryKey class from the Microsoft.Win32 namespace: Imports Microsoft.Win32 ... Shared Sub Main(args() As String) ' Create a key and set its default value Dim key As RegistryKey = _ Registry.ClassesRoot.CreateSubKey(cmdkey) ' Map ProgID to an Open action for the shell key.SetValue(Nothing, Application.ExecutablePath & " ""%L""") End Sub The RegistryKey class is a named "folder" in the Registry. This folder can have one or more named values, which are like the "files" (a name of null denotes the default value for a key). The values can be of several types, including string, unsigned integer, and arbitrary bytes. Writing to the Registry is a matter of opening or creating a subkey from one of the hive keys (which represent the top-level localities) and writing values. The hive keys are properties on the Registry object and translate into keys with well-known names in the Registry, as shown in Table 11.2. Reading values from the Registry is similar to writing them: Shared Sub Main(args() As String) ' Check whether someone has hijacked the .tlf extension Dim mapExtension As Boolean = True ' Open an existing key Dim key As RegistryKey = Registry.ClassesRoot.OpenSubKey(".tlf") ' If the reference is Nothing, the key doesn't exist If Not (key Is Nothing) AndAlso _ (key.GetValue(Nothing).ToString().ToLower() <> "tlffile") _ Then Dim ask As String = "Associate .tlf with this application?" Dim res As DialogResult = _ MessageBox.Show(ask, "Oops!", MessageBoxButtons.YesNo) If res = DialogResult.No Then mapExtension = False End If If mapExtension Then ... End If End Sub Table 11.2. Registry Properties and Key Names
To use the Registry to store settings, Microsoft recommends putting them under the hive key using the following format: <hiveKey>\Software\<companyName>\<productName>\<productVersion> Here's an example: HKEY_CURRENT_USER\Software\Sells Brothers, Inc.\My Settings Test.0.1124.31077 The variable values are, coincidentally, exactly the same values provided by Application.CompanyName, Application.ProductName, and Application.Version, so you can construct a top-level key name by using the following: Dim appkey As String = _ String.Format( _ "Software\{0}\{1}\{2}", _ Application.CompanyName, _ Application.ProductName, _ Application.ProductVersion) Dim key As RegistryKey = Registry.LoclalMachine.OpenSubKey(appkey) ... Similarly, for roaming user settings, Microsoft recommends using the same subkey but under the HKEY_CURRENT_USER hive instead of the HKEY_LOCAL_MACHINE hive. To accommodate the many people desiring to open subkeys for application and roaming user settings in the Microsoft-recommended spots, the WinForms Application object includes two properties that provide preopened registry keys at the right spot depending on whether you'd like application data or roaming user data. These properties are named CommandAppDataRegistry and UserAppDataRegistry. For example, to save the main form's position you could use the UserAppDataRegistry key: Sub MainForm_Closing(sender As Object, e As CancelEventArgs) ' Save the form's position before it closes Dim key As RegistryKey = Application.UserAppDataRegistry ' Restore the window state to save location and ' client size at restored state Dim state As FormWindowState = Me.WindowState Me.WindowState = FormWindowState.Normal key.SetValue("MainForm.Location", ToString(Me.Location)) key.SetValue("MainForm.ClientSize", ToString(Me.ClientSize)) key.SetValue("MainForm.WindowState", ToString(state)) End Sub ' Convert an object to a string Function ToString(obj As Object) As String Dim converter As TypeConverter = _ TypeDescriptor.GetConverter(obj.GetType()) Return converter.ConvertToString(obj) End Function This example uses the Closing event to notice when the main form is about to close (but before it does) to save the window state, location, and client size. In addition to remembering to restore the window state before saving the location and the client size, this code uses the ToString helper function. This function uses a type converter, which is a helper to aid in the conversion between instances of a type and strings (for more on type converters, see Chapter 9: Design-Time Integration). Any type can have an associated type converter, and most of the simple ones do. After the application is run once, notice the settings that are saved in the Registry, as shown in Figure 11.10. Figure 11.10. Using the Registry for User Settings
Notice that the size is shown as "292, 54" instead of "{292, 54}", as would have happened if we had used the built-in Size class's ToString method. Because Size's type converter doesn't know how to translate the surrounding braces, it's important to use the type converter's conversion to the string format instead of the type's conversion to ensure that the value can be read back during loading of the form: Sub MainForm_Load(sender As Object, e As EventArgs) ' Restore the form's position Dim key As RegistryKey = Application.UserAppDataRegistry Try ' Don't let the form's position be set automatically Me.StartPosition = FormStartPosition.Manual Me.Location = _ CType(FromString(key.GetValue("MainForm.Location"), _ GetType(Point)), Point) Me.ClientSize = _ CType(FromString(key.GetValue("MainForm.ClientSize"), _ GetType(Size)), Size) Me.WindowState = _ CType(FromString(key.GetValue("MainForm.WindowState"), _ GetType(FormWindowState)), FormWindowState) ' Don't let missing settings scare the user Catch ... End Try End Sub ' Convert a string to an object Function FromString(obj As Object, type As Type) As Object Dim converter As TypeConverter = TypeDescriptor.GetConverter(type) Return converter.ConvertFromString(obj.ToString()) End Function In this case, the form's Load method uses the Registry key to load the settings that it saved during the Closing event, this time using the type converters to convert from a string to the appropriate type. Notice also that the Load method uses a try-catch block to wrap the attempts to pull values from the Registry; in this way, we avoid throwing an exception if the values aren't present. These values typically are missing when the application is first run. If you'd like, you can set defaults to avoid the exception when a value is not found: Me.WindowState = _ CType(FromString(key.GetValue("MainForm.WindowState", "Normal"), _ GetType(FormWindowState)), FormWindowState) Although the Registry can be used for application and user settings, its use has fallen out of fashion. Corruption issues with early implementations of the Registry have given it a bad reputation. There's nothing inherently wrong with it that more modern versions of Windows haven't fixed long ago. However, because the Registry doesn't support nonroaming user settings or use from partially trusted applications, you'll want to consider special folder-based settings media, discussed next. Special FoldersSpecial folders are folders that Windows designates as having a special purpose. For example, the default folder where programs are installed is special and is available this way: ' Generally "C:\Program Files" Dim programFiles As String = _ Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) There are three special folders for settings: one each for the application, user, and roaming user localities. Table 11.3 shows them, along with some sample paths running on Windows XP. The special folder serves as the top-level folder in the folder under which applications can store application settings, user settings, and roaming user settings (just as Registry.LocalMachine and Registry.CurrentUser provide the top level for application and roaming user settings). Under that folder, an application is expected to construct a subfolder to avoid colliding with other applications or even versions of itself. This subfolder has the following format: <specialFolder>\<companyName>\<productName>\<productVersion> Table 11.3. Special Folders, Localities, and Examples
For example: [View full width]
And just as the Application object provides shortcut access to Registry keys via properties, it also provides shortcut access to prefabricated folder names and folders via the CommonAppDataPath, LocalUserAppDataPath, and UserAppDataPath properties. For example, here's how to rewrite the Registry-based setting code using special folders: Imports System.IO ... Sub MainForm_Closing(sender As Object, e As CancelEventArgs) ' Save the form's position before it closes Dim fileName As String = _ Application.LocalUserAppDataPath & "\MainForm.txt" Dim writer As StreamWriter = New StreamWriter(fileName) ' Restore the window state to save location and ' client size at restored state Dim state As FormWindowState = Me.WindowState Me.WindowState = FormWindowState.Normal writer.WriteLine(ToString(Me.Location)) writer.WriteLine(ToString(Me.ClientSize)) writer.WriteLine(ToString(state)) End Sub Sub MainForm_Load(sender As Object, e As EventArgs) Dim asReader As AppSettingsReader = New AppSettingsReader() Dim pi As Decimal = CType(asReader.GetValue("pi", _ GetType(Decimal)), Decimal) piTextBox.Text = pi.ToString() ' Restore the form's position Try Dim fileName As String = _ Application.LocalUserAppDataPath & "\MainForm.txt" Dim reader As StreamReader = New StreamReader(fileName) ' Don't let the form's position be set automatically Me.StartPosition = FormStartPosition.Manual Me.Location = CType(FromString(reader.ReadLine(), _ GetType(Point)), Point) Me.ClientSize = CType(FromString(reader.ReadLine(), _ GetType(Size)), Size) Me.WindowState = CType(FromString(reader.ReadLine(), _ GetType(FormWindowState)), FormWindowState) ' Don't let missing settings scare the user Catch ex As Exception End Try End Sub In this case, only two things are different from the use of the Registry. The first is the use of a file to hold the form's settings instead of a Registry key to hold the data. This means that the values must be read in the same order as they're written (unless you use a smarter serialization strategy). The second is the use of the path to the user settings data instead of roaming user settings data. With the Registry, all user data is roaming, whether you want it to be or not (unless you do something custom under the HKEY_LOCAL_MACHINE hive). User settings that are related to the capabilities of the machine itself, such as the location, size, and state of a form, are better suited to nonroaming data. Settings and StreamsIt is convenient to use type conversions with the Registry because it maintains distinct values and makes them available via the Registry Editor, but when you've got a stream, real .NET serialization [11] becomes an attractive option:
Imports System.Runtime.Serialization Imports System.Runtime.Serialization.Formatters Imports System.Runtime.Serialization.Formatters.Soap ... ' Custom type to manage serializable form data <SerializableAttribute> _ Class FormData Public Location As Point Public ClientSize As Size Public WindowState As FormWindowState Public Sub New(theform As Form) Me.Location = theform.Location Me.ClientSize = theform.ClientSize Me.WindowState = theform.WindowState End Sub End Class Sub MainForm_Closing(sender As Object, e As CancelEventArgs) ' Save the form's position before it closes Dim fileName As String = _ Application.LocalUserAppDataPath & "\MainForm.txt" Dim mystream As Stream = _ New FileStream(fileName, FileMode.Create) ' Restore the window state to save location and ' client size at restored state Dim formatter As IFormatter = New SoapFormatter() Formatter.Serialize(mystream, New FormData(Me)) mystream.Dispose() End Sub Sub MainForm_Load(sender As Object, e As EventArgs) ' Restore the form's position Try Dim fileName As String = _ Application.LocalUserAppDataPath & "\MainForm.txt" Dim mystream As Stream = _ New FileStream(fileName, FileMode.Open) ' Don't let the form's position be set automatically Me.StartPosition = FormStartPosition.Manual ' Deserialize custom FormData object Dim formatter As IFormatter = New SoapFormatter() Dim data As FormData = _ CType(formatter.Deserialize(mystream), FormData) ' Set data from FormData object Me.Location = data.Location Me.ClientSize = data.ClientSize Me.WindowState = data.WindowState Catch ex As Exception ' Don't let missing settings scare the user MessageBox.Show(ex.Message, ex.GetType().Name) End Try End Sub This example serializes an instance of a custom type that represents the setting data that we'd like to keep between sessions. To serialize an object is to read it from or write it to a stream. A stream is an object that provides access to a storage medium, such as a file or a database. To have something to serialize, we've got a custom type called FormData, which keeps track of the location, client size, and window state. When it's time to save the form data, the code creates an instance of the new type and then hands it to the formatter, along with the file stream opened in the special folder. Similarly, when loading, we use a formatter to deserialize the form data and use it to restore the form. This is a much easier way to go than the type converter because it takes less code. In addition, serialization provides some nice extension options as time goes on and the FormData class needs to include extra information, such as font and color preferences. Isolated StorageOne more technology that .NET provides for reading and writing settings data is isolated storage . It's called "isolated" because it doesn't require the application to know where on the hard drive the settings files are stored. In fact, it's just like using the special folder shortcuts provided by the Application class except that the path to the root of the path on the file system isn't even available to the application. Instead, named chunks of data are called streams, and containers (and subcontainers) of streams are called stores . The model is such that the implementation could vary over time, although currently it's implemented on top of special folders with subfolders and files. The special folder you get depends on the scope you specify when getting the store you want to work with. You specify the scope by combining one or more flags from the IsolatedStorageScope enumeration: Enum IsolatedStorageScope Assembly ' Always required Domain None Roaming User ' Always required End Enum Isolated storage stores must be scoped by, at a minimum, assembly and user. This means that there are no user-neutral settings available from isolated storage, only user and roaming user settings (depending on whether the Roaming flag is used). In addition, you can scope settings to a .NET application domain using the Domain flag, but typically that's not useful in a WinForms application. Table 11.4 shows the valid combinations of scope flags as related to settings localities and sample folder roots under Windows XP. Obtaining a store to work with is a matter of specifying the scope to the GetStore method of the IsolatedStorageFile class from the System.IO.IsolatedStorage namespace: Dim scope As IsolatedStorageScope = _ IsolatedStorageScope.Assembly Or IsolatedStorageScope.User Dim store As IsolatedStorageFile = _ IsolatedStorageFile.GetStore(scope, Nothing, Nothing) Because getting the user store for the assembly is so common, the IsolatedStorageFile class provides a helper with that scope already in place: ' Scope = User Or Assembly Dim store As IsolatedStorageFile = _ IsoltatedStorageFile.GetUserStoreForAssembly() After you've got the store, you can treat it like a container of streams and subcontainers by using the members of the IsolatedStorageFile class: NotInheritable Class IsolatedStorageFile Inherits IsolatedStorage Implements IDisposable ' Properties Property AssemblyIdentity() As Object Property CurrentSize() As UInt64 Property DomainIdentity() As Object Property MaximumSize() As UInt64 Property Scope() As IsolatedStorageScope ' Methods Sub Close() Sub CreateDirectory(dir As String) Sub DeleteDirectory(dir As String) Sub DeleteFile(file As String) Function GetDirectoryNames(searchPattern As String) As String() Shared Function GetEnumerator(scope As IsolatedStorageScope) _ As IEnumerator Shared Function GetStore(...) As IsolatedStorageFile Shared Function GetUserStoreForAssembly() As IsolatedStorageFile Shared Function GetUserStoreForDomain() As IsolatedStorageFile MustOverride Overloads Sub Remove() Shared Overloads Shared Remove(scope As IsolatedStorageScope) End Class Table 11.4. Isolated Storage Scope, Locality, and Folder Roots
Don't be confused by the fact that the IsolatedStorageFile class is actually implemented as a directory in the file system. This is only one implementation of the IsolatedStorageStorage abstract base class. Other implementations are certainly possible (although none is currently provided by .NET). The most common thing you'll want to do with a store is to create a stream on it using an instance of IsolatedStorageFileStream. The IsolatedStorageFileStream class is just another implementation of the virtual methods of the FileStream class to hide the details of the underlying implementation. After you've got the stream, you can write to it or read from it just as if you'd opened it yourself as a file. Here's the same code again to store the main form's location using isolated storage: Sub MainForm_Closing(sender As Object, e As CancelEventArgs) ' Save the form's position before it closes Dim store As IsolatedStorageFile = _ IsolatedStorageFile.GetUserStoreForAssembly() Dim mystream As Stream = _ New IsolatedStorageFileStream("MainForm.txt", _ FileMode.Create, store) ... End Sub Sub MainForm_Load(sender As Object, e As EventArgs) ' Restore the form's position Try Dim store As IsolatedStorageFile = _ IsolatedStorageFile.GetUserStoreForAssembly() Dim mystream As Stream = _ New IsolatedStorageFileStream("MainForm.txt", _ FileMode.Open, store) ... Catch ex As Exception ' Don't let missing settings scare the user End Try End Sub Managing Isolated Storage and StreamsYou can manage the isolated storage using the Store Admin tool (storeadm.exe) from the command line. To list the user settings, use storeadm /list, as shown in Figure 11.11. Figure 11.11. Using the Store Admin Tool to List Stores and Streams
Some records indicate storage, and others indicate streams. To remove all the user isolated storage, use storeadm /remove. Both of these commands operate by default on user settings. To act on roaming user settings, add the /roaming switch to either command. Unfortunately, the Store Admin tool does not show the contents of any storage or stream, nor does it show the mapping in the file system. So your best bet is to use the dir and find shell commands, starting at the root of the isolated storage folder you're interested in exploring. Isolated Storage and Partial TrustAlthough it may seem that isolated storage is only a more complicated use of special folders, there is an important benefit to using isolated storage instead of opening settings files directly: Using isolated storage lets you use partially trusted assemblies. A partially trusted assembly is one that runs in a security sandbox that limits its permissions ”for example, a WinForms control hosted in Internet Explorer. Partially trusted assemblies are not allowed to read or write to the Registry or the file system as needed for settings, but they are allowed to read and write to isolated storage. If you need settings for a partially trusted assembly, your only option is to use isolated storage for read/write user settings and to use .config files for read-only settings. Chapter 15: Web Deployment covers partially trusted assemblies in detail. Versioned Data PathsYou may have noticed that all the paths provided by the Application object for the Registry and the special folders are built using a version number, including the build number and revision. This means that the next version of your application will be protected from the changing format of settings data. It also means that the next version of the application cannot use the paths provided by Application to find the settings written by the preceding version. If you're stuck on using the Registry or special folders for user settings, you can get version-independent settings in one of two ways. One way is to bypass the path helper properties on the Application object and build your own by omitting the version. For example, you could use a roaming user special folder path in the following format: <specialFolder>\<companyName>\<productName> Another way to get version-independent settings is to migrate the settings to the new version's path during the installation of the new version. This allows you access to the old version's settings in the new version while still letting you change the format of any settings values that have changed between versions. Of course, it also requires that you write the code to do the migration. Each of the remaining two forms of settings management ”.config files and isolated storage ”has a built-in form of version independence. Settings in the appSettings portion of the .config file aren't keyed to versions at all, so those values are carried along from version to version (whether or not you want them to be). Isolated storage has two forms of versioned access to settings. If an assembly is unsigned ” that is, there is no key file associated with it at build time via the assemblywide AssemblyKeyFileAttribute [12] ”then isolated storage is just as version-independent as .config files are (again, whether or not you want it to be). All versions of the assembly will share the same settings.
Signing an assembly requires first obtaining a key: C:\>sn -k key.snk After you have a key, you can sign an assembly by passing the name of the key as the value to the AssemblyKeyFileAttribute: <Assembly: AssemblyKeyFile("..\..\key.snk")> After an assembly is signed, access to isolated storage is versioned, but only on the major part of the version number. In other words, all 1.x versions of an application will share the same settings, but 2.x versions will need their own settings. This is probably the best of both worlds . Minor versions aren't likely to change the format of existing settings and will at most gain new settings (with appropriate defaults). Major versions are likely to change fundamental things, including the format of settings data, so it makes sense to give a new major version number a clean slate when it comes to settings data. When using isolated storage, you'll still have to move user settings forward during the installation of the new version, but only if it's a major version upgrade, such as 1.1 to 2.0. No migration is needed for an upgrade from 1.1 to 1.2. Note that this version discussion is about the paths to the data stores themselves . If you're serializing versioned types to an isolated storage stream, for example, you'll still need to deal with standard .NET versioning on those. Choosing a Settings MechanismTable 11.5 summarizes the characteristics of the various mechanisms for retrieving and storing settings for WinForms applications. Choosing which settings mechanism to use depends on what your needs are. If you have read-only application settings, the .config file is a good choice because it's simple, it has some built-in Designer support, and it works from a partially trusted environment. If you've got user settings, then isolated storage is a good choice because it supports reading and writing, partial trust, and roaming (although not roaming in combination with partial trust), and it has a nice versioning story. Special folders or the Registry is really useful only for legacy applications or read/write settings (which are pretty darn rare). Table 11.5. Summary of Settings Mechanisms
|