如何从数据库中的XML读取配置节?

我有一个像这样的Config类:

public class MyConfig : ConfigurationSection { [ConfigurationProperty("MyProperty", IsRequired = true)] public string MyProperty { get { return (string)this["MyProperty"]; } set { this["MyProperty"] = value; } } } 

它正在由另一个类实例化

 (MyConfig)ConfigurationManager.GetSection("myConfig") 

我们正在进行一些更改,现在将配置文件作为xml存储在数据库中,就像它当前在配置文件中一样。

我想将MyConfig维护为ConfigurationSection以实现向后兼容,但仍然可以使用从DB检索的XML字符串来实例化它。

可能吗? 如果是这样,怎么样? (请记住,它仍然可以像上面的实例一样工作)

我的建议是保留当前的MyConfig类,但是在构造函数中从数据库加载XML,然后在MyConfig的每个属性中,您可以输入逻辑来确定从何处获取值(数据库或.config文件)如果你需要从任一位置拉配置,或者如果值为空则让它回退。

 public class MyConfig : ConfigurationSection { public MyConfig() { // throw some code in here to retrieve your XML from your database // deserialize your XML and store it _myProperty = ""; } private string _myProperty = string.Empty; [ConfigurationProperty("MyProperty", IsRequired = true)] public string MyProperty { get { if (_myProperty != null && _myProperty.Length > 0) return _myProperty; else return (string)this["MyProperty"]; } set { this["MyProperty"] = value; } } } 

以下是我通常的做法:只需将这些成员添加到MyConfig类:

  public class MyConfig : ConfigurationSection { private static MyConfig _current; public static MyConfig Current { get { if (_current == null) { switch(ConfigurationStorageType) // where do you want read config from? { case ConfigFile: // from .config file _current = ConfigurationManager.GetSection("MySectionName") as MyConfig; break; case ConfigDb: // from database default: using (Stream stream = GetMyStreamFromDb()) { using (XmlTextReader reader = new XmlTextReader(stream)) { _current = Get(reader); } } break; } } return _current; } } public static MyConfig Get(XmlReader reader) { if (reader == null) throw new ArgumentNullException("reader"); MyConfig section = new MyConfig(); section.DeserializeSection(reader); return section; } } 

这样,您在MyConfig类中无需更改,但您仍需要使用此类代码更改客户访问它的方式:

 string myProp = MyConfig.Current.MyProperty; 

如果需要将任何System.Configuration.ConfigurationSection存储在数据库中,您可以考虑编写通用的部分阅读器,如下所示:

    public class ConfigurationSectionReader where T : ConfigurationSection, new() { public T GetSection( string sectionXml ) { T section = new T(); using ( StringReader stringReader = new StringReader( sectionXml ) ) using ( XmlReader reader = XmlReader.Create( stringReader, new XmlReaderSettings() { CloseInput = true } ) ) { reader.Read(); section.GetType().GetMethod( "DeserializeElement", BindingFlags.NonPublic | BindingFlags.Instance ).Invoke( section, new object[] { reader, true } ); } return section; } }
public class ConfigurationSectionReader where T : ConfigurationSection, new() { public T GetSection( string sectionXml ) { T section = new T(); using ( StringReader stringReader = new StringReader( sectionXml ) ) using ( XmlReader reader = XmlReader.Create( stringReader, new XmlReaderSettings() { CloseInput = true } ) ) { reader.Read(); section.GetType().GetMethod( "DeserializeElement", BindingFlags.NonPublic | BindingFlags.Instance ).Invoke( section, new object[] { reader, true } ); } return section; } } 

这适用于覆盖DeserializeElement方法的所有类。 例如

    protected override void DeserializeElement( XmlReader reader, bool serializeCollectionKey ) { XmlDocument document = new XmlDocument(); document.LoadXml( reader.ReadOuterXml() ); MyProperty = document.DocumentElement.HasAttribute( "MyProperty" ) ? document.DocumentElement.Attributes[ "MyProperty" ].Value : string.Empty; }
protected override void DeserializeElement( XmlReader reader, bool serializeCollectionKey ) { XmlDocument document = new XmlDocument(); document.LoadXml( reader.ReadOuterXml() ); MyProperty = document.DocumentElement.HasAttribute( "MyProperty" ) ? document.DocumentElement.Attributes[ "MyProperty" ].Value : string.Empty; } 

你可以得到这样一个部分:

    var reader = new ConfigurationSectionReader(); var section = reader.GetSection( sectionXml ); // where sectionXml is the XML string retrieved from the DB
var reader = new ConfigurationSectionReader(); var section = reader.GetSection( sectionXml ); // where sectionXml is the XML string retrieved from the DB 

这是一个难题。 您可能有一个配置文件,其中包含一些故意不正确的XML,在ConfigurationSection中覆盖OnDeserializeUnrecognizedElement,然后有效地绕过该文件到ConfigurationSection映射(实际上是手动设置您的属性) – 需要进行一些重构,但您仍然可以公开相同的属性等。这有点WTF,但可能是可行的。

我基本上在本博文中描述了如何使用LINQ to XML执行此操作 。 在我的所有代码中,我现在没有依赖ConfigurationSection的类,我使用我的博客文章中描述的技术绕过它并通过接口返回POCO。 这使我的代码更易于unit testing,因为我可以轻松地使用存根作为接口。

如果我愿意,我也可以轻松地将配置移动到数据库中 – 我只需创建一个新类来实现我的配置界面并在IoC配置中切换它。 Microsoft没有将配置系统设计为灵活,因此在您自己的代码中使用它时必须考虑到这一点。

我能想到的唯一另一种方法是将DB配置写入文件,然后将其读入,但这也很奇怪!

相当古老的问题,但只是在解决这个问题。 它类似于Simon Mourier的方法(我喜欢在某些方面更好 – 更少hacky),但它的意思是任何调用System.Configuration.ConfigurationManager.GetSection()代码将继续工作而不必更改它们以使用静态方法,所以可能导致整体代码更改更少。

第一个基本的警告是我不知道这是否适用于嵌套部分,但我几乎可以肯定它不会。 几乎需要注意的是,它需要更改config部分类,因此您只能将它与您拥有源的自定义部分一起使用(并且允许更改!)

第二个和MAIN CAVEAT是我正在玩这个,我不是在开发中使用它而且绝对不是生产,并且简单地将我自己的代码涂抹在这样的基本function上可能会产生连锁反应不要出现在我的例子中。 使用风险由您自己承担

(话虽如此,我正在Umbraco网站上进行测试,所以有很多其他配置部分正在进行,但它们仍然可以工作,所以我认为它没有立即可怕的效果)

编辑:这是.NET 4,而不是原始问题的3.5。 不知道这是否会产生影响。

所以,这里的代码非常简单,只需覆盖DeserializeSection即可使用从数据库加载的XML阅读器。

 public class TestSettings : ConfigurationSection { protected override void DeserializeSection(System.Xml.XmlReader reader) { using (DbConnection conn = /* Get an open database connection from whatever provider you are using */) { DbCommand cmd = conn.CreateCommand(); cmd.CommandText = "select ConfigFileContent from Configuration where ConfigFileName = @ConfigFileName"; DbParameter p = cmd.CreateParameter(); p.ParameterName = "@ConfigFileName"; p.Value = "TestSettings.config"; cmd.Parameters.Add(p); String xml = (String)cmd.ExecuteScalar(); using(System.IO.StringReader sr = new System.IO.StringReader(xml)) using (System.Xml.XmlReader xr = System.Xml.XmlReader.Create(sr)) { base.DeserializeSection(xr); } } } // Below is all your normal existing section code [ConfigurationProperty("General")] public GeneralElement General { get { return (GeneralElement)base["General"]; } } [ConfigurationProperty("UI")] public UIElement UI { get { return (UIElement)base["UI"]; } } ... ... } 

我正在使用ASP.Net,所以为了使它工作,你需要一个web.config ,但是嘿,无论如何我需要连接字符串的某个地方,或者我根本不会连接到数据库。

您的自定义部分应在定义为正常; 做这项工作的关键是然后用一个空元素代替你的正常设置; 即代替或者你的内联设置,只需输入

然后,配置管理器将加载所有部分,查看现有的元素,并对其进行反序列化,此时它将命中您的覆盖并从数据库加载XML。

注意 :反序列化需要一个文档片段(它希望在读取器已经位于节点时调用),而不是整个文档,因此如果您的节存储在单独的文件中,则必须删除声明首先,或者你得到Expected to find an element