1、缘起html
最近作项目开始使用C#,由于之前一直使用的是C++,所以面向对象思想方面的知识仍是比较全面的,反而是因没有通过完整、系统的.Net方面知识的系统学习,常常被一些在C#老鸟眼里几乎是常识的小知识点给绊倒。网络
为何这么说呢,由于我在网络上查找的资料的时候,常常大部分问题,都是可以找到或多或少的参考资料,可是这些小知识点却不多可以找到正确的解决方法,有也是只有提问,没有回到,那么这种状况出现,就只有2种解释:
一、这个方面的问题很难,难到没有人可以解决;
二、这个问题太简单,简单到稍微熟悉的人都不屑于回答,提问者也在一番思考后,轻松找到答案。(我比较倾向这个,呵呵,所以我也把这些小知识,叫作:容易被忽略的细节)app
然而,不管问题是否简单,既然我会被绊倒,耽搁时间,确定也会有人被一样耽搁,所以我想把这些细节整理出来,仍是具备必定意义的。ide
因而,本系列文章开始...函数
2、问题描述post
除了正常状况下的config文件,使用ConfigurationManager加载,咱们还可能会碰到一下这样的状况:
一、加载非当前应用程序yyy.exe默认的config文件的xxx.exe.config文件;(好比:与yyy.exe.config不在同一目录下 或者 文件名不一样)
二、加载非应用程序的xxx.config文件;
三、让类库xxx.dll内的函数读取默认config文件的时候,读取的是xxx.dll同级目录下的xxx.dll.config文件,而不是加载xxx.dll的应用程序yyy.exe的默认应用程序配置文件:yyy.exe.config;
以上三种状况,都不能直接使用ConfigurationManager来加载学习
3、解决过程测试
让咱们从最基础、最简单、最多见的config文件的加载来入手,解决上面三个问题:this
step1:研究基础的config文件加载加密
config文件,是给客户端应用程序保存配置信息用的,通常状况,一个应用程序只会有一个这样的文件,在编译以前,叫作App.config,每次使用Visual Studio编译,都会Copy到应用程序的生成目录,且Rename为:应用程序名.config。
要读取config文件内的信息,只须要使用ConfigurationManager的相关函数和属性便可,所以咱们来研究下ConfigurationManager,看看是否能找到解决问题的相关信息。
打开MSDN,找到这样一个方法:
OpenExeConfiguration 已重载。 将指定的客户端配置文件做为 Configuration 对象打开。
OK,要找的就是这个,由于这个方法有一个重载方法是:
OpenExeConfiguration(String) 将指定的客户端配置文件做为 Configuration 对象打开。
step2:加载非当前应用程序默认的config文件
因而,第一个问题的解决方案,彷佛、应该、可能找到了,按照MSDN上的说明,若咱们把要打开的xxx.exe.config的路径做为参数传入便可,代码以下:
Configuration config = ConfigurationManager.OpenExeConfiguration("C:\\xxx.exe.config");
DllInfo dllInfo = config.GetSection("DllInfo") as DllInfo;
Console.WriteLine(dllInfo);
可是,事情并无这么顺利,这样是没法打开xxx.exe.config文件的,通过调试,发现:config的属性FilePath的值为:"C:\\xxx.exe.config.config",程序本身在传入的参数后增长了“.config”做为要打开的config文件的路径,这显然和咱们以前从MSDN上所看到的不同,不用说,咱们被微软小小的耍了一把。这里要传入的参数,不该该是要打开的config的路径,而应该是这个config文件对应的应用程序的路径,也就是说上面的代码应该这样写:
Configuration config = ConfigurationManager.OpenExeConfiguration("C:\\xxx.exe"); // 写的是应用程序的路径
DllInfo dllInfo = config.GetSection("DllInfo") as DllInfo;
Console.WriteLine(dllInfo);
再次运行,呵呵,仍是不行,提示错误:『加载配置文件时出错: 参数“exePath”无效。参数名: exePath』。显然咱们有被耍了,这里要传入应用程序路径(exePath)没错,可是由于咱们并无在xxx.exe.config文件同目录下,加入xxx.exe文件,所以咱们传入的exePath其实是无效的,可见为了可以加载xxx.exe.config,咱们弄一个xxx.exe文件放在一块儿。
ok,运行,成功。
小结1:第一个问题的解决方案找到:
step3:扩展step2的战果,找到加载xxx.config的方法
step2已经找到了加载xxx.exe.config的方法,观察xxx.exe.config的名称,发现,若把xxx.exe当作YYY,显然xxx.exe.config = YYY.config,也就是说:xxx.exe.config是xxx.config中比较特殊的一种,他要求config文件的文件名最后4个字母必须是“.exe”。
此时,大胆推测,使用ConfigurationManager.OpenExeConfiguration(string exePath),应该能够解决问题。
Configuration config = ConfigurationManager.OpenExeConfiguration("C:\\xxx"); // 记得要有xxx文件,不然这个路径就是无效的了。
DllInfo dllInfo = config.GetSection("DllInfo") as DllInfo;
Console.WriteLine(dllInfo);
运行,HOHO,成功了。
小结2:第二个问题和第一个问题的解决方案同样。
step4:扩展xxx.config解决问题3
继续扩大战果,仍是从文件名上来找思路,咱们要加载的xxx.dll.config,其实也是xxx.config中稍微特殊的一种,显然也能够和step3那样处理。
使用OpenExeConfiguration(string exePath)来解决问题三,在dll内,碰到须要读取config文件信息的时候,放弃使用ConfigurationManager的函数或属性直接获取,而改用OpenExeConfiguration(string exePath)加载config文件为一个Configuration对象的对应函数或属性便可。
小结3:第三个问题一样能够按照第一个问题的方案来作。
4、额外的思考
在应用程序yyy.exe中经过ConfigurationManager能够很方便的读取到yyy.exe.config文件中的信息,但在类库中使用ConfigruationManager读取的却不是自动编译生成的xxx.dll.cofig文件,而是引用类库的应用程序yyy.exe的yyy.exe.config文件。
有没有什么办法,让类库中的ConfigurationManager读取的也是他默认的xxx.dll.config文件呢?
其实,是能够的,不过这里涉及到了应用程序域(AppDomain)的概念, .Net上的应用程序都是运行在一个应用程序域(AppDomain)内的,在程序启动之初,都会默认启动一个AppDomain,查看MSDN能够看到AppDomain有一个属性:SetupInformation,这个属性保存的就是当前域的config文件路径;惋惜,这个属性是只读的,因此咱们默认AppDomain的config文件路径。
所以,若想让类库可以直接使用ConfigurationManager来读取本身默认的config文件,就只能把类库放在一个新的AppDomain中执行,而且在建立AppDomain的时候指定他的SetupInformation为类库默认的config文件路径;AppDomain有一个用来建立新AppDomain的方法:CreateDomain(String, Evidence, AppDomainSetup);只要把第三个参数的属性ConfigurationFile只想类库默认的config文件路径便可。
5、附录:实例代码: 代码下载
对于config文件,通常状况下都是使用ConfigurationManager加载,而后经过读取相应节点的值来获取想要的数据,可是,有时候须要修改config文件的值,这时候就用到了OpenExeConfiguration()方法。
MSDN上面对该方法的解释:ConfigurationManager.OpenExeConfiguration方法用来把指定的客户端配置文件做为Configuration对象打开,该方法具备两个重载:
名称 |
说明 |
ConfigurationManager.OpenExeConfiguration (ConfigurationUserLevel) |
将当前应用程序的配置文件做为 Configuration 对象打开。 |
ConfigurationManager.OpenExeConfiguration (String) |
将指定的客户端配置文件做为 Configuration 对象打开。 |
1、使用OpenExeConfiguration(ConfigurationUserLevel)重载设置当前应用程序的配置文件
客户端应用程序使用应用于全部用户的全局配置、应用于单个用户的单独配置以及应用于漫游用户的配置。userLevel 参数经过指示该配置文件是不具备用户级别(配置文件与应用程序位于同一目录中),仍是具备一个依每一个用户而定的用户级别(配置文件位于用户级别所肯定的应用程序设置路径中),从而肯定所打开的配置文件的位置。
经过向 userLevel 传递下列值之一来指定要获取的配置:
-
若要获取应用于全部用户的 Configuration 对象,请将 userLevel 设置为 None。
-
若要获取应用于当前用户的本地 Configuration 对象,请将 userLevel 设置为 PerUserRoamingAndLocal。
-
若要获取应用于当前用户的漫游 Configuration 对象,请将 userLevel 设置为 PerUserRoaming。
注意:若要获取资源的 Configuration 对象,您的代码必须对它从中继承设置的全部配置文件具备“读取”特权。若要更新配置文件,您的代码还必须对该配置文件及其所在目录具备“写入”特权。
示例程序:
一、配置文件结构以下:
1 <?xml version="1.0" encoding="utf-8" ?>
2 <configuration>
3 <appSettings>
4 <add key="ApServer1" value="ApServer1"/>
5 <add key="ApServer2" value="ApServer2"/>
6 <add key="LocalHost1" value="LocalHost1"/>
7 <add key="LocalHost2" value="LocalHost2"/>
8 <add key="addr" value="11111"/>
9 </appSettings>
10 <startup>
11 <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
12 </startup>
13 </configuration>
二、经过程序修改LocalHost1节点的值
string strLocalHost1Value1 = ConfigurationManager.AppSettings["LocalHost1"].ToString(); //strLocalHost1Value1="LocalHost1";
//Configuration对象
Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
config.AppSettings.Settings["LocalHost1"].Value = "http://127.0.0.1";
//保存配置文件
config.AppSettings.SectionInformation.ForceSave = true;
config.Save(ConfigurationSaveMode.Modified);
//从新加载改变的节点
ConfigurationManager.RefreshSection("appSettings");
//读取配置文件的值
string strLocalHost1Value2 = ConfigurationManager.AppSettings["LocalHost1"].ToString();//strLocalHost1Value2="http://127.0.0.1"
2、使用OpenExeConfiguration(String)重载设置指定的客户端配置文件
重载指定的客户端config文件主要包括下面3种状况:
一、加载非当前应用程序yyy.exe默认的config文件的xxx.exe.config文件(yyy.exe是当前应用程序,xxx.exe.config与yyy.exe.config文件不在同一目录下)。
二、加载非应用程序的xxx.config文件。
三、让类库xxx.dll内的函数读取默认config文件的时候,读取的是xxx.dll同级目录下的xxx.dll.config文件,而不是加载xxx.dll的应用程序yyy.exe的默认应用程序配置文件:yyy.exe.config。
注意:在类库中使用ConfigruationManager读取的不是自动编译生成的xxx.dll.config文件,而是引用类库的应用程序yyy.exe的yyy.exe.config文件。
解决方法:
按照MSDN上的说明,咱们把要打开的xxx.exe.config的路径做为参数传入,代码以下:
1 Configuration con = ConfigurationManager.OpenExeConfiguration("C:\\Modify.exe.config");
2 con.AppSettings.Settings["LocalHost2"].Value = "测试";
可是程序运行的时候报错,通过调试,发现con对象的FilePath属性的值为:C:\Modify.exe.config.config,程序本身在传入的参数后增长了“.config”做为要打开的config文件的路径,由于没有这个文件,因此程序报错。这里要传入的参数,不该该是要打开的config文件的路径,而是这个config文件对应的应用程序的路径,上面的代码应修改成:
1 //参数传的是应用程序的路径
2 Configuration con = ConfigurationManager.OpenExeConfiguration("C:\\Modify.exe.");
3 con.AppSettings.Settings["LocalHost2"].Value = "测试";
再次运行程序,仍是报错,提示“加载配置文件时出错:参数exePath”无效。这里要传入应用程序的路径(exePath)没错,可是由于在xxx.exe.config文件的同一目录下,没有xxx.exe文件,所以咱们传入的exePath其实是无效的,为了可以加载xxx.exe.config文件,须要在同一目录下增长一个xxx.exe文件。(能够在同一目录下新建一个txt文件,修更名称为xxx,扩展名为.exe,这样就能够加载xxx.exe.config配置文件了)
完整的代码以下:
//参数传的是应用程序的路径
Configuration con = ConfigurationManager.OpenExeConfiguration("C:\\Modify.exe");
con.AppSettings.Settings["LocalHost2"].Value = "测试";
//保存配置文件
con.AppSettings.SectionInformation.ForceSave = true;
con.Save(ConfigurationSaveMode.Modified);
//从新加载改变的节点
ConfigurationManager.RefreshSection("appSettings");
//读取修改后的配置文件节点值
string str = con.AppSettings.Settings["LocalHost2"].Value;//str="测试"
注意:
使用ConfigurationManager.OpenExeConfiguration(string exePath)便可,同时注意2个小细节:
A:改方法需传入的是exePath,而不是configPath;
B:exePath必须是有效的,所以xxx.exe和xxx.exe.config应该成对出现,缺一不可。
加载非应用程序的xxx.config文件
在上面的例子中,观察xxx.exe.config文件的名称,发现,若把xxx.exe当作YYY,则xxx.exe.config=YYY.config,也就是说:xxx.exe.config是xxx.config文件的一种特殊形式,因此,可使用以下的代码加载xx.config文件:
//参数传的是应用程序的路径
Configuration con = ConfigurationManager.OpenExeConfiguration("C:\\Modify");
con.AppSettings.Settings["LocalHost2"].Value = "测试";
//保存配置文件
con.AppSettings.SectionInformation.ForceSave = true;
con.Save(ConfigurationSaveMode.Modified);
//从新加载改变的节点
ConfigurationManager.RefreshSection("appSettings");
//读取修改后的配置文件节点值
string str = con.AppSettings.Settings["LocalHost2"].Value;//str="测试"
注意:C:\Modify这个文件必需要有。
加载xxx.dll.config文件:
仍是从文件名上来找思路,咱们要加载xxx.dll.config文件,能够和加载xxx.config文件同样。在dll内,碰到须要读取config文件信息的时候,放弃使用ConfigurationManager读取节点的值,而是使用OpenExeConfiguration(string exePath)方法加载config文件为一个Configuration对象来使用。
注意:经过程序修改配置文件中节点的值,不会修改.config文件里面的值,更改只是发生在内存中。
==============================================================================================
经过从网上的了解,和学习,咱们看到ConfigurationManager.OpenMappedExeConfiguration这个方法能够用于打开指定的配置文件,那么看看咱们用它来作一些事情吧,下面看代码:
using System;
using System.Collections.Generic;
using System.Configuration;//必须引用:System.Configuration
using System.Linq;
using System.Text;
namespace PVG.Lib.Configs
{
public class AppConfigHelper
{
private Configuration config = null;
public Configuration Configuration
{
get { return config; }
set { config = value; }
}
/// <summary>
/// 是否加密链接字符串
/// </summary>
public bool IsEncryptionConnection { get; set; }
/// <summary>
/// 默认读取当前应用程序的配置信息
/// </summary>
public AppConfigHelper()
{
string startPath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase + AppDomain.CurrentDomain.SetupInformation.ApplicationName;
config = ConfigurationManager.OpenExeConfiguration(startPath);
IO.DebuggerHelper.OutputLine(startPath);//输出
}
/// <summary>
/// 指定的Config文件的路径
/// </summary>
/// <param name="configPath"></param>
public AppConfigHelper(string configPath)
{
string configFilePath = System.AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;
configFilePath = string.IsNullOrEmpty(configPath) ? configFilePath : configPath;
//new ExeConfigurationFileMap();
config = ConfigurationManager.OpenMappedExeConfiguration(new ExeConfigurationFileMap() { ExeConfigFilename = configFilePath }, ConfigurationUserLevel.None, true);
IO.DebuggerHelper.OutputLine(configFilePath);//输出
}
public string GetConnectionStrings(string ConnName)
{
//return ConfigurationManager.ConnectionStrings[ConnName].ToString();
string res = "";
if (config != null && config.ConnectionStrings.ConnectionStrings[ConnName] != null)
res = config.ConnectionStrings.ConnectionStrings[ConnName].ConnectionString;
return res;
}
public string SetConnectionStrings(string ConnName, string ConnValue)
{
return SetConnectionStrings(ConnName, ConnValue, "");
}
public string SetConnectionStrings(string ConnName, string ConnValue, string providerName)
{
if (config != null)
{
if (config.ConnectionStrings.ConnectionStrings[ConnName] != null)
config.ConnectionStrings.ConnectionStrings[ConnName].ConnectionString = ConnValue;
else
config.ConnectionStrings.ConnectionStrings.Add(new ConnectionStringSettings(ConnName, ConnValue, providerName));
config.Save(ConfigurationSaveMode.Modified);
}
if (IsEncryptionConnection)
encryptionConn();
return GetConnectionStrings(ConnName);
}
public string GetAppSettings(string keyName)
{
string res = "";
if (config != null && config.AppSettings.Settings[keyName] != null)
{
res = config.AppSettings.Settings[keyName].Value;
}
return res;
}
public string SetAppSettings(string keyName, string keyValue)
{
if (config != null)
{
if (config.AppSettings.Settings[keyName] != null)
config.AppSettings.Settings[keyName].Value = keyValue;
else
config.AppSettings.Settings.Add(keyName, keyValue);
config.Save(ConfigurationSaveMode.Modified);
}
return GetAppSettings(keyName);
}
private void encryptionConn()
{
ConfigurationSection connectionSection = config.GetSection("connectionStrings");
if (connectionSection != null)
{
connectionSection.SectionInformation.ProtectSection("RSAProtectedConfigurationProvider");
config.Save();
}
}
}
}
View Code