Effective C# 原则37:使用标准的配置机制
Item 37: Use the Standard Configuration Mechanism
我们要寻求一种避免直接写代码的应用程序配置和信息设置方法,我们已经创建了多种不同的策略来存储配置信息。而我们是要寻求一种正确的方法,我们要不断提高和改我们的想法,关于哪里是放置这些信息的好地方。INI文件?这是Windows3.1做的事,配置信息的结构是受限制的,而且在文件名上可能还会与其它程序程序相冲突。注册表?是的,是这个正确的想法,但它也有它的限制。乱七八糟的程序可能会通过在注册表里写一些错误信息来严重破坏计算机。正因为写注册表存在危险,一个应用程序必须有管理员权限来写注册表的一部份。你的所有用户都会是以具有修改注册表权利的管理员身份在运行吗?希望不是,如果你使用注册表,而你的用户不是以管理员身份运行的,在试图读写注册表时,将会得到一个异常和错误。
谢天谢地,还有很多更好的方法来存储设置信息,这样你的程序可以根据用户的选择不同适应不同的行为,例如安装参数,机器设置,或者其它任何事情。.Net框架提供了一个标准的设置位置,这样你的程序可以使用它来存储配置信息。这些存储位置是由应用程序特别指定的,而且当程序执行的机器上的用户被限制了权限时一样可以有效的工作。
只读的信息是属于配置文件的,XML文件控制应用程序中不同类型的行为;定义的结构表指明了所有的元素和属性,而这些都是.NET FCL从配置文件中分析出来的。 这些元素控制一些设置,例如正在使用那个框架版本,支持的调试级别(参见原则36),以及程序集的搜索路径。有一个节点你是必须要明白的,那就是appSettings部份,它可以同时应用与web应用程序和桌面应用程序。运行程序在启动时读取这一节点的信息,它加载所有的关键字和值到一个属于应用程序的名字值集合(NameValueCollection)中。这是你自己程序的一部份,你可以添加任何程序须要的值来控制程序行为。当修改配置文件时,也就修改了程序行为。
对于使用配置文件来说,ASP.Net应用程序比桌面应用程序的伸缩性稍灵活一点。每个个虚拟目录可以有一个自己的配置文件,这个文件被每个虚拟目录依次读取,而每个虚拟目录也就对应一个URL的一部分。The most local wins. 例如,这个URL:http://localhost/MyApplication/SubDir1/SubDir2/file.aspx 可能被4个不同的配置文件所控制。machine.config最先读取,其次是在MyApplication中的web.config文件,接着是在SubDir1 和SubDir2中的web.config文件。而它们每一个都可以修改前一个配置文件设置的值,或者是添加自己键/值对。你可以通过这种配置继承方式,来配置一个全局应用程序的参数选择,而且可以限制一些私有资源的访问。web应用程序在不同的虚拟目录中有不同的配置。
在桌面应用程序中,对于每个应用程序域只有一个应用程序程序配置文件。.Net运行时在载入每个可执行文件时,为它创建一个默认的应用程序域,然后读取一个预先军定义的配置文件到这个应用程序域中。默认的配置文件在与应用程序运行时的同一个目录中,而且就以<应用程序名>.<扩展名>.config来命名的。例如:MyApp.exe可能就有一个名为MyApp.exe.config的配置文件。appsettings部份可以用于创建你自己的键/值对到应用程序中。
配置文件是存储一些控制程序行为的信息的最好的地方。但你可能很快会发现,应用程序没有API来写配置文件信息。配置文件不是用于存储任何有序设置的地方。不要急着写注册表,也不要自己乱写。这里有一个更好的方法让你配置桌面应用程序。
你可能须要定义配置文件的格式,而且把配置文件放到正确的地方。通过在全局设置上定义一些设置结构和添加公共的读写属性,你可以很简单的存储和取回这些设置:
[ Serializable( ) ]
public struct GlobalSettings
{
// Add public properties to store.
}
XML序列化来存储你的设置:
XmlSerializer ser = new XmlSerializer(
typeof( GlobalSettings ));
TextWriter wr = new StreamWriter( "data.xml" );
ser.Serialize( wr, myGlobalSettings );
wr.Close( );
使用XML格式就意味着你的设置可以很容易的阅读,很容易的解析,以及很容易的去调试。如果须要,你可以对这些用户设置进行加密存储。这只是一个使用XML序列化的例子,不是对象持久序列化(参见原则25)。XML序列化存储文件,不是整个对象树。配置设置以及用户设置一般不会包含网状对象,而且XML序列化是一个简单的文件格式。
最后一个问题就是,应该在哪里存储这些信息。你应该在三个不同的地方放置配置信息文件。选择哪一个要根据配置的使用情况:全局,单用户,或者单用户且单机器。这三个位置可以通过调用System.Environment.GetFolderPath() 而取得。你应该在GetFolderPath()返回的路径后添加上应用程序的详细目录。请格外小心的在所有用户或者机器范围上填写信息。这样做要在目标机器是取得一些特权。
Environment.SpecialFolder.CommonApplicationData返回存储信息的目录,这一目录是被机器上的所有用户所共享的。如果在一台机上使用的是默认安装,GetFolderPath(SpecialFolder.CommonApplicationData)会返回 C:\Documents and Settings\All Users\Application Data。存储在这一目录的的设置应该是被机器上的所有用户所使用的。当你要在这里创建信息时,让安装程序给你做或者以管理员模式进行。不应该在这里写一些用户级(译注:users级是windows里的一个用户组,权利比管理员小。)的程序数据。偶然可能会让你的应用程序在用户机上没有足够的权限来访问。
Environment.SpecialFolders.ApplicationData返回当前用户的路径,而且在网络上被所有机器共享的。在默认安装中,GetFolderPath(SpecialFolders.ApplicationData)返回 C:\Documents and Settings\<用户名>\Application Data。每个用户有他(或她)自己的应用程序数据目录。当用户登录到一个域是,使用这个列举进入到共享网络上,而且在网络上包含了用户的全局设置。存储在这里的数据只由当前用户使用,不管是从网络上的哪台机器登录过来的。
Environment.SpecialFolders.LocalApplicationData返回一个特殊的目录,该目录队了存储设置信息以外,同时也是一个用户的私人目录,它只属于从这台机器上登录的用户。一般GetFolderPath(SpecialFolders.LocalApplicationData)返回:C:\Documents and Settings\<用户名>\Local Settings\Application Data
这三个不同的位置可以让你存储每个人的设置信息,给定用户的信息,或者是给定用户并给定机器的信息。具体的使用哪一个取决于应用程序。但考虑一些明显的例子:数据库链接字符串是一个全局设置,它应该存在在通用应用程序数据(Common Application Data) 目录中。一个用户的工作内容应该存在在应用程序数据(Application Data)目录中,因为它只取决于用户。窗口的位置信息应该在本地应用程序数据(Local Application Data)目录中。因为它们取决于机器上的用户的属性(不的机器可能有不同的分辨率)。
应该有一个特殊的目录,它为所有应用程序的所有用户设置存储,描述顶层的目录结构。这里,你须要在顶层目录结构下创建子目录。.Net框架的System.Windows.Application类定义了一些属性,这些属性可以为你创建一些通用的配置路径。Application.LocalAppDataPath属性返回GetFolderPath(SpecialFolders.CommonApplicationData)+"\\CompanyName\\ProductName\\ProductVersion"的路径。类似的,Application.UserDataPath和Application.LocalUserDataPath产生位于用户数据和本地数据目录下的路径名,这一目录包括公司,应用程序,以及版本号。如果你组合这些位置,你就可以为你自己公司的所有应用程序创建一个配置信息,或者为某一程序的所有版本,或者是特殊版本。
注意到了,这些目录中我没有提到过应用程序目录,就是在Program Files下的目录。你决不应该在Program Files或者在Windows 系统目录以及子目录里写数据。这些目录要更高的特权,因此你并不能指望你的用户有权利来写这些数据。
当在哪里存储应用程序的设置数据成为一个很重要的问题,就像每个企业级用户到家庭用户所担心的机器安全问题一样时,把信息放在正确的位置就意味着对于使用你的应用程序的用户来说没有折衷的办法。你还是要给用户提供私人的感觉,用.Net的顺序组合正确的位置,这样可以很容易的给每个用户一种私有的感觉,而且不用折衷安全问题。
==============================
Item 37: Use the Standard Configuration Mechanism
In our quest to avoid hard-coding configuration and settings information, we have created many different strategies for storing configuration information. In our quest to get it right, we kept improving and changing our minds about where to put such information. INI files? That was so Windows 3.1. You were limited in the structure of your configuration information, and you had to contend with filename collisions with other applications. The Registry? Yes, this was a step in the right direction, but it had its limitations as well. Malicious programs could do serious damage to a machine writing the wrong things to the Registry. Because of the dangers inherent in writing to the Registry, a program must have administrative rights to write in parts of the Registry. Are all your users running as admins with the capability to edit the Registry? You hope not. If you use the Registry, users running as nonadmins will get exceptions and errors when they attempt to save or read their settings.
Thankfully, there are much better ways to store settings so that your program can adapt its behavior to your users' preferences, the install parameters, the machine settings, or just about anything else. The .NET Framework provides a standard set of locations that your application can use to store configuration information. These locations are specific to your application and will work when the user has limited privileges on the machine where the code executes.
Read-only information belongs in configuration files, XML files that control various types of behavior in the application. Defined schemas dictate all the elements and attributes that the .NET FCL parses from config files. These elements control settings such as the version of the framework being used, the level of debugging support (see Item 36), and the search path for assemblies. One section you must understand is the appSettings section, which applies to both web and desktop applications. The runtime reads this section when your application starts. It loads all the keys and values into a NameValueCollection owned by your application. This is your section. You add any values that your application needs to control its behavior. If you modify the config file, you modify the application's behavior.
ASP.NET applications have a little more flexibility than desktop applications do with respect to config files. Each virtual directory can have its own config file. The files are read in order for each virtual directory that is part of the URL. The most local wins. For example, the URL http://localhost/MyApplication/SubDir1/SubDir2/file.aspx could be controlled by four different config files. The machine.config file gets read first. Second is the web.config file in the MyApplication directory. Following is the web.config files in SubDir1 and SubDir2, in that order. Each can change values set by a previous config file or add its own key/value pairs. You can use this configuration inheritance to set up global application preferences and limit access to some private resources. Web applications can have different configurations for different virtual directories.
On the desktop, there is only one application configuration file for each app domain. The .NET runtime creates a default application domain for each executable that it loads, and reads one predefined config file into that domain. This default configuration file is located in the same directory as the executable and is called <applicationname>.<ext>.config. For example, MyApp.exe would have a config file named MyApp.exe.config. The appsettings section can be used to create your own key/value pairs for your application.
Config files are great to store information that controls the behavior of your application at runtime. But you will quickly notice that there are no APIs to write configuration information from your application. Configuration files are not the place for user settings of any sort. Don't go running for the Registry yet. Don't write your own from scratch. There is a better way for your .NET desktop applications.
You need to define the format for your configuration information and put that configuration information in the right location. You can easily store and retrieve these settings by defining a settings structure and adding public read/write properties for the global settings:
[ Serializable( ) ]
public struct GlobalSettings
{
// Add public properties to store.
}
Use the XML serializer to save your settings:
XmlSerializer ser = new XmlSerializer(
typeof( GlobalSettings ));
TextWriter wr = new StreamWriter( "data.xml" );
ser.Serialize( wr, myGlobalSettings );
wr.Close( );
Using XML format means that your settings will be easy to read, easy to parse, and easy to debug. You can use encrypted storage for these user settings, if necessary for your application. This example uses the XML serializer, not the object serializer for persistence (see Item 25). The XML serializer stores documents, not entire object trees. Configuration settings and user settings typically do not contain webs of objects, and the XML serializer is a simpler file format.
The only question remaining is where to store the information. You should put settings information in three different locations. Which you choose depends on when it should be used: Globally, per user, or per user and machine. All three locations are returned by different calls to by the System.Environment.GetFolderPath() method. You should append your application-specific directories on the end of the path returned by GetFolderPath(). Be extremely careful about writing information in the all-user or machine-wide directories. Doing so requires more privileges on the target machine.
Environment.SpecialFolder.CommonApplicationData returns the directory for storing information that is shared by all users on all machines. On a machine with a default installation, GetFolderPath (SpecialFolder.CommonApplicationData) returns C:\Documents and Settings\All Users\Application Data. Settings stored under this location should be used by all users, on all machines. When you create information that should go here, write it with the installer or an admin module. Avoid writing data here in your user programs. Chances are, your application does not have the necessary access rights on users' machines.
Environment.SpecialFolders.ApplicationData returns the directory for this user, shared by all machines in the network. On a default installation, GetFolderPath(SpecialFolders.ApplicationData) gives you C:\Documents and Settings\<username>\Application Data. Each user has his or her own application data directory. When the user logs into a domain, using this enumeration points to the network share that contains the user's global settings. Settings stored under this location are used by the current user, no matter what machine in the network he has logged in from.
Environment.SpecialFolders.LocalApplicationData returns the directory for storing information that is personal for this userand only when logged in on this machine. A typical value returned by GetFolderPath(SpecialFolders.LocalApplicationData) is C:\Documents and Settings\<username>\Local Settings\Application Data.
These three different locations let you store settings that should apply to everyone, the given user, or the given user on the given machine. Exactly which you use depends on the application. But consider some obvious examples: The database connection is a global setting. It should be stored in the Common Application Data directory. A user's working context should be stored in the Application Data directory because it depends only on the user. Window locations should be in the Local Application Data directory because they depend on the user and properties of the machine (different machines might have different screen resolutions).
These special folders describe the top-level directory structure for user settings stored by all applications. In all cases, you should create subdirectories underneath these top-level structures. The .NET Framework's System.Windows.Application class defines properties that build common configuration paths for you. The Application.LocalAppDataPath property returns the path for GetFolderPath(SpecialFolders.CommonApplicationData)+"\\CompanyName\\ProductName\\ProductVersion". Similarly, Application.UserDataPath and Application.LocalUserDataPath produce pathnames underneath the user's data and local data directories for this company, application, and version. If you combine these locations, you can create configuration information for all your company's applications, for this application across all versions, and for this specific version.
Note that nowhere in those directories did I mention the application directory, under Program Files. You should not ever write data in any directory under Program Files or in the Windows system directory. Those locations require more security privileges, so you should not expect your users to have permission to write in them.
Where you store your application's settings has become more important as everyone from enterprise users to home users worries about the security of machines. Putting the information in the right location means that it is easier for your users to work with your application without compromising security. You can still provide your users with a personalized experience. Combine the right location with .NET serialization, and it's easy to have your application provide a personalized appearance for each user without compromising security.