Although applications can acquire any number of environmental settings, there are two basic groups of settings that the environment can't provide. First, there are application settings, which are settings that are shared between all users of an application on a machine; an example is the list of the folders that contain the assemblies to show in the VS05 Add Reference dialog. Second, there are user settings, which are specific to both an application and a user, such as Minesweeper high scores or almost anything you'll find in the Tools | Options dialog. Additionally, there is a special variation of user settings for roaming users; both application and user settings are specific to a machine, but roaming-user settings are machine-independent user settings. For example, if Minesweeper high scores were roaming, they'd be available to a specific user no matter what computer the user was logged in to.[2] Roaming-user settings are good for things that don't depend on a machine, such as a list of color preferences, but not for things that are dependent on a current machine's configuration, such as a window location.[3] Roaming-user settings presuppose that the machine is properly configured to support roaming. Otherwise, roaming-user settings are equivalent to nonroaming-user settings.
The .NET Framework and VS05 provide comprehensive support for creating and managing application, user, and roaming-user settings, all of which are built on a fundamental unit of information: the setting. What Is a Setting?A setting is comprised of four pieces of information: name, type, value, and scope. The name is a string value that uniquely identifies each setting. The type defines the sort of value a setting is and can be a simple type, a complex type, a user-defined type, or an enumeration. The value must be a valid string representation of the selected type. The scope is used to differentiate whether a setting is an application or user setting; scope is a key factor in several scenarios discussed throughout this chapter, including whether settings support roaming. Settings FilesOne or more settings are stored together in a settings file, which is a file with the .settings extension. By default, VS05 automatically creates a settings file for you when the Windows Application project template is run. The settings file is called Settings.settings and is located in the project's Properties folder, as shown in Figure 15.7. Figure 15.7. The VS05 Windows Application Wizard-Generated settings File
Note that you can manually add settings files to your projects by right-clicking your project in Solution Explorer and clicking Add | New Item | Settings File. However, one settings file has always been plenty for me, so the rest of this chapter discusses settings from the default settings file point of view, which applies equally to manually created settings files. By default, a new settings file contains six lines of XML, including namespace information. A settings file with two settingsHighScore and AssemblyPathsis shown here:[4]
<?xml version='1.0' encoding='utf-8'?> <SettingsFile ...> ... <Settings> <Setting Name="HighScore" ...> <Value...>0</Value> </Setting> <Setting Name="AssemblyPaths"...> <Value...>c:\windows\microsoft.net</Value> </Setting> </Settings> </SettingsFile> As with .resx files (discussed in Chapter 13: Resources), the XML format of the settings file is optimized more for persistence than for editing by hand. VS05 provides a rich Settings Editor UI to help you manage settings files with aplomb. Managing SettingsThe Settings Editor, shown in Figure 15.8, is opened when you double-click a settings file in Solution Explorer.[5]
Figure 15.8. VS05 Settings DesignerYou use the Settings Designer grid like any other grid to create, navigate, select, and delete settings. Additionally, you can use the Properties window to configure the name, scope, and value of existing settings; you can specify type only from the settings grid. Editing SettingsEach setting property captures a different piece of information, so editing differs from one property to the next. NameBecause a setting's Name property is a string value, you can simply type it into the cell directly. TypeType, on the other hand, must be selected from a drop-down list of items that, by default, includes a variety of common simple and complex types. It also includes two special settings typesdatabase connections and web service URLswhich require specialized storage considerations that are provided by the settings system.,[6] All these options are shown in Figure 15.9.
Figure 15.9. Selecting a Default Settings Type
If the required type does not appear in the list by default, you can choose one by clicking the Browse list option to open the Select a Type dialog shown in Figure 15.10. Figure 15.10. Selecting a Nondefault Settings Type
The only items that appear in this list are .NET Framework types that can be serialized, whether by TypeConverter (by converting to and from a string) or with the XmlSerializer.[7] Note that you can also add custom types of your own by entering their fully qualified names into the Selected Type text box, although they'll need a TypeConverter just as the .NET Framework types do.[8] When selected, the chosen settings type is added to the type drop-down list as a default list item and remains there until VS05 is closed.
ScopeThe Scope drop-down list has two options: Application and User. Application settings are read-only and are used to store permanent settings values. User settings are read-write and store values that can be changed by users as required; the value you specify for a user setting becomes its default value. Both application settings and user settings are used in a variety of ways that are covered in more detail throughout the remainder of this chapter. ValueIn most situations, editing a value in the Settings Designer is the same as editing a value in the Properties window. Specifically, if the type of the setting you specified is associated with a type converter, you can provide a string value that can be converted to the desired type because settings values are stored as strings.[9] Additionally, if the settings type has a UI type editor associated with it, such as the System.Drawing.Font type, you can use a full-featured dialog to construct the value and return a suitable string representation, as illustrated in Figure 15.11.[10]
Figure 15.11. Using a UI Type Editor to Edit a Settings ValueIf the settings type is an enumeration, you can select its value from a drop-down list of items, one for each value of the enumeration, as illustrated in Figure 15.12. Figure 15.12. Editing a Complex Type Value in the Settings DesignerIn some situations, a setting's type simply doesn't provide enhanced Properties window-style editing support. In these cases, the settings system uses XML serialization to store the type, but only if the type supports XML serialization. The following code shows how the Settings Designer persists a settings configuration to the settings file: <?xml version='1.0' encoding='utf-8'?> <SettingsFile ...> ... <Settings> <Setting Name="HighScore" Type="System.Int32" Scope="User"> <Value...>0</Value> </Setting> <Setting Name="AssemblyPaths" Type="System.String" Scope="Application"> <Value ...>c:\windows\microsoft.net</Value> </Setting> <Setting Name="DefaultFont" Type="System.Drawing.Font" Scope="User"> <Value ...>Microsoft Sans Serif, 8.25pt</Value> <Setting Name="DefaultWindowState" Type="System.Windows.Forms.FormWindowState" Scope="Application"> <Value ...>Normal</Value> </Settings> </SettingsFile> Note that FontConverter and EnumConverterthe type converters used by the Settings Editor to help out with Font type and FormWindowState type settings, respectivelyare used to persist values for the DefaultFont and DefaultWindowState settings as strings. After you've configured the settings for your application, you need to make them available to your application for debugging and deployment. For this, we have a special file known as the application configuration file. Application Configuration FilesWhen a project is compiled, VS05 stores the configured settings and values in app.config, which is automatically created by VS05 and added to the project root. A project with one application setting and one user setting, app.config would look like this: <?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <sectionGroup name="userSettings"...> <section name="ApplicationSettingsSample.Properties.Settings".../> </sectionGroup> <sectionGroup name="applicationSettings"...> <section name="ApplicationSettingsSample.Properties.Settings".../> </sectionGroup> </configSections> <userSettings> <ApplicationSettingsSample.Properties.Settings> <setting name="HighScore" serializeAs="String"> <value>0</value> </setting> </ApplicationSettingsSample.Properties.Settings> </userSettings> <applicationSettings> <ApplicationSettingsSample.Properties.Settings> <setting name="AssemblyPaths" serializeAs="String"> <value>c:\windows\microsoft.net</value> </setting> </ApplicationSettingsSample.Properties.Settings> </applicationSettings> </configuration> In app.config, user and application settings are grouped by the userSettings and applicationSettings section groups. Within those section groups, the settings and their values are stored in a section whose name conforms to the following convention: Namespace.SettingsFileName Because the settings and values are grouped by their settings files, app.config can manage all settings and values stored in settings files across your project. This situation might occur when your application has so many settings that it is far easier to manage them by splitting them across several smaller settings files, while still requiring them to be merged into app.config: <?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <sectionGroup name="userSettings"...> <section name="ApplicationSettingsSample.MyOtherSettings".../> <section name="ApplicationSettingsSample.Properties.Settings".../> </sectionGroup> <sectionGroup name="applicationSettings"...> <section name="ApplicationSettingsSample.MyOtherSettings".../> <section name="ApplicationSettingsSample.Properties.Settings".../> </sectionGroup> </configSections> <userSettings> <ApplicationSettingsSample.MyOtherSettings> ... </ApplicationSettingsSample.MyOtherSettings> <ApplicationSettingsSample.Properties.Settings> ... </ApplicationSettingsSample.Properties.Settings> </userSettings> <applicationSettings> <ApplicationSettingsSample.MyOtherSettings> ... </ApplicationSettingsSample.MyOtherSettings> <ApplicationSettingsSample.Properties.Settings> ... </ApplicationSettingsSample.Properties.Settings> </applicationSettings> </configuration> The app.config file is really managed by VS05 to represent the current settings for a project. Before you execute an applicationunder the auspices of VS05 or from a client-installed locationthe settings in app.config need to be deployed with the application executable. Therefore, when a project is compiled, VS05 creates a file called app.exe.config, where app is the name of the generated application. The app.exe.config file is an exact copy of the app.config file that's generated to the same folder as all the other project compilation output, including the application assembly. As such, app.exe.config contains all application and user settings for all settings files in a project. When an application is deployed, its app.exe.config file could reside in one of several locations that depend on how the user configured things and how the application was installed. For locally installed applications, app.exe.config is located in the same folder as the assembly. For ClickOnce-deployed applications, app.exe.config is stored in the following location: %SystemDrive%\Documents and Settings\UserName\Local Settings\Apps\HashedPath\ For roaming profiles, app.exe.config is installed here: %SystemDrive%\Documents and Settings\UserName\Local Settings\Application Data\ProductName\HashedPath The application- and user-scoped settings contained within app.exe.config are considered the default values for an application's lifetime. The Configuration ManagerAfter settings are deployed, they need to be pulled from app.exe.config into the application for use. Some of those settingsuser-scoped settingsare expected to change and may need to be written to disk. Manually writing the code to open, navigate, read, write, and close the XML-encoded app.exe.config file is nontrivial. Instead, the ConfigurationManager class and its friends in System.Configuration, wrap this raw XML processing into a settings-oriented abstraction. You use ConfigurationManager to open a .config file by calling its OpenExeConfiguration method, which returns a Configuration class that wraps the actual .config file: // ConfigurationManagerForm partial class ConfigurationManagerForm : Form { Configuration configuration; public ConfigurationManagerForm() { InitializeComponent(); // Open .config file (current local userSettings) this.configuration = ConfigurationManager.OpenExeConfiguration( ConfigurationUserLevel.PerUserRoamingAndLocal); } } OpenExeConfiguration accepts a ConfigurationUserLevel argument, which specifies the kind of settings to load: namespace System.Configuration { enum ConfigurationUserLevel { None = 0, // Open application settings PerUserRoaming = 10, // Open user settings (Roaming) PerUserRoamingAndLocal = 20 // Open user settings (Local) } } In our example, we're opening the local user settings. Reading SettingsThe in-memory .config file wrapped by the Configuration object is structured in section groups, sections, and settings to represent the file structure naturally. For example, the HighScore setting is located in the "ApplicationSettingsSample.Properties.Settings" section of the "userSettings" section group: <?xml version="1.0" encoding="utf-8" ?> <configuration> ... <userSettings> <!-- Section Group --> <ApplicationSettingsSample.Properties.Settings> <!-- Section --> <setting name="HighScore" serializeAs="String"> <value>0</value> </setting> ... </ApplicationSettingsSample.Properties.Settings> </userSettings> ... </configuration> Consequently, we need to navigate through these elements to find the appropriate setting. The following code uses the ConfigurationSectionGroup, ClientSettingsSection, and SettingElement objects to do just that: // ConfigurationManagerForm.cs partial class ConfigurationManagerForm : Form { ... public ConfigurationManagerForm() { ... // Read a setting from the config file string setting = this.ReadSetting( "userSettings", "ApplicationSettingsSample.Properties.Settings", "HighScore"); MessageBox.Show(setting); } string ReadSetting( string sectionGroupName, string sectionName, string settingName) { // Get sectionGroup ConfigurationSectionGroup sectionGroup = this.configuration.GetSectionGroup(sectionGroupName); // Get section ClientSettingsSection section = (ClientSettingsSection)sectionGroup.Sections.Get(sectionName); // Get setting SettingElement setting = section.Settings.Get(settingName); // Read setting value return setting.Value.ValueXml.InnerText; } } As we know, user settings are read-write, so we need to write them back to disk so that the updated value is available during the new application session. Writing SettingsWriting a setting back to disk uses the same technique to find a setting, although this time you're setting the value instead of reading it: // ConfigurationManagerForm partial class ConfigurationManagerForm : Form { ... public ConfigurationManagerForm() { ... // Write a setting to the config file this.WriteSetting( "userSettings", "ApplicationSettingsSample.Properties.Settings", "HighScore", "200"); } ... void WriteSetting( string sectionGroupName, string sectionName, string settingName, string newSettingValue) { // Get sectionGroup ConfigurationSectionGroup sectionGroup = this.configuration.GetSectionGroup(sectionGroupName); // Get section ClientSettingsSection section = (ClientSettingsSection)sectionGroup.Sections.Get(sectionName); // Get setting SettingElement setting = section.Settings.Get(settingName); // Writing a setting value setting.Value.ValueXml.InnerText = newSettingValue; } } However, this step goes only as far as writing the value to the in-memory .config file available from the Configuration object. To persist it to disk, we take the additional step of calling the Configuration object's Save method: // ConfigurationManagerForm partial class ConfigurationManagerForm : Form { ... public ConfigurationManagerForm() { ... // Write a setting to the config file this.WriteSetting( "userSettings", "ApplicationSettingsSample.Properties.Settings", "HighScore", "200"); // Save all settings this.configuration.Save(ConfigurationSaveMode.Full, true); } ... } This use of the Save method ensures that the entire configuration file is written back to disk. User Configuration FilesAll settings deployed with the app.exe.config file, including both user settings and application settings, are considered read-only. It's this characteristic that turns application settings into permanent settings and turns user settings into default settings. As we know, however, user settings are designed to allow users to change them. Because the default user settings deployed with app.exe.config are effectively read-only, the settings system creates a new file, user.config, to store any user settings whose values differ from their defaults stored in app.exe.config. The user.config file is located in one of four special Windows-compliant folders, depending on where the application was executed from. Settings saved from an application run from within VS05 are stored in the following folder: %SystemDrive%\Documents and Settings\UserName\Local Settings\Application. Data\UserName\ApplicationName.vshost.exe_StrongNameHash\AssemblyVersionNumber If settings are saved from a locally installed application, user.config ends up in this folder: %SystemDrive%\Documents and Settings\UserName\Local Settings\Application Data\ProductName\ApplicationName.exe_Url_UrlHash\AssemblyVersionNumber If settings are saved from an application deployed via ClickOnce, you'll find user.config here: %SystemDrive%\Documents and Settings\UserName\Local Settings\Apps\Data\HashedPath\Data\ProductVersion Finally, if roaming profiles are enabled for the current userwhether or not settings are being saved from a locally installed or ClickOnce-deployed applicationuser.config is placed here: %SystemDrive%\Documents and Settings\UserName\Local Settings\Application Data\ProductName\HashedPath So, when our code wrote the local user settings, a user.config was created and stored in the locally installed user.config location, containing only the user settings: <?xml version="1.0" encoding="utf-8"?> <configuration> <userSettings> <ApplicationSettingsSample.MyOtherSettings /> <ApplicationSettingsSample.Properties.Settings> <setting name="HighScore" serializeAs="String"> <value>200</value> </setting> </ApplicationSettingsSample.Properties.Settings> </userSettings> </configuration> Unmodified user settings are copied from the defaults located in app.exe.config, whereas modified settings are updated with the new values. In addition to logically separating application settings from user settings, the division enables a wide variety of additional settings-oriented scenarios for rollback and migration of settings. During development, you may find that multiple user.config files are createdfor example, as a result of versioning changes. This can lead to weird issues derived from a lack of synchronicity between the settings your application expects and those that are actually stored in user.config. The Settings Editor provides the Synchronize button, shown in Figure 15.13, to quickly remove all user.config files for a current application from all possible paths. Figure 15.13. Synchronize Button to Clean All user.config FilesNote that it is possible to write the following code to specifically update app.exe.config: // ConfigurationManagerForm partial class ConfigurationManagerForm : Form { ... public ConfigurationManagerForm() { // BAD - writing to app.exe.config violates // the spirit of read-only application settings this.configuration = ConfigurationManager.OpenExeConfiguration( ConfigurationUserLevel.None); // Write a setting to the config file this.WriteSetting( "applicationSettings", "SettingsSample.Properties.Settings", "AssemblyPaths", "naughtyValue"); // Save updated settings this.configuration.Save(ConfigurationSaveMode.Full, true); ... } Writing to the app.config file circumvents the philosophy and practice of the settings system, which treats the app.config file as read-only and stores the changed user settings in the user.config file. This philosophy is practiced in a much simpler and safer way provided by VS05 to deal with settings of all kinds: strongly typed settings. |