【问题标题】:How to Read a Configuration Section from XML in a Database?如何从数据库中的 XML 读取配置部分?
【发布时间】:2011-05-19 08:36:00
【问题描述】:

我有一个这样的 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 以实现向后兼容性,但仍然能够使用从数据库中检索到的 XML 字符串来实例化它。

有可能吗?如果是这样,怎么做? (请记住,它仍然应该像上面实例化的那样工作)

【问题讨论】:

  • 我希望乐于看到这个问题的解决方案 - 我已经研究了一段时间,但没有取得太大的成功,不幸的是......
  • 我很高兴看到我并不孤单。
  • 我不知道这个问题的答案,因为似乎应用程序代码无法更改配置文件的位置。但我不会在这种情况下重载配置文件,而是创建自己的配置阅读器,从数据库中获取数据。
  • @akonsu,我一直在认真考虑按照你的建议去做。我以为我在进入那个方向之前问过
  • @akonsu:嗯,主要问题是我真的找不到插入我自己的基于数据库的配置阅读器的方法。不幸的是,整个 .NET 配置系统不是基于提供程序的(就像 .NET 中的许多其他东西一样),并且许多相关的类是密封的并使用内部和私有方法:-(

标签: c# .net .net-3.5 configuration


【解决方案1】:

这是我通常的做法:只需将这些成员添加到 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;

【讨论】:

    【解决方案2】:

    如果您需要将任何 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; } }

    这适用于所有重写 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; }

    你可以得到这样的部分:

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

    【讨论】:

    • 这很糟糕,因为它依赖于私有成员,因此可能无法在 Mono 或下一个版本的 .NET 中工作,否决
    • 如果将其用作永久解决方案,这可能不太理想,但如果将其用作重构而不使用 System.Configuration 的一种手段,那么它完全适合 IMO。我在一些非常讨厌的遗留代码上使用了类似的技术来做到这一点 - 重构不再需要物理文件和环境转换。下一步当然也将重构它并消除对 System.Configuration 的依赖。给我+1,考虑到警告。
    • 优秀的解决方案。虽然该解决方案确实依赖于私有成员,但它实现了能够解耦和重构完全依赖于 app.config 的组件的总体目标。就我而言,它是 Unity 容器。我现在能够仅使用 XML 来实例化完整的类层次结构。在 Azure Function 中运行良好。谢谢
    【解决方案3】:

    我的建议是保留当前的 ​​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 = "<deserialized value from db>";
        }
    
        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; }
        }
    }
    

    【讨论】:

    • 这是我最终选择的路线
    【解决方案4】:

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

    我基本上描述了how to do this with LINQ to XML in this blog post。现在在我的所有代码中,我没有依赖于 ConfigurationSection 的类,我使用我的博客文章中描述的技术来绕过它并通过接口返回 POCO。这使我的代码更易于单元测试,因为我可以轻松地为接口使用存根。

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

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

    【讨论】:

      【解决方案5】:

      相当老的问题,但只是想解决这个问题。它类似于 Simon Mourier 的方法(我在某些方面更喜欢它 - 不那么 hacky),但确实意味着任何调用 System.Configuration.ConfigurationManager.GetSection() 的代码都将继续工作,而无需更改它们以使用静态方法,因此可能会导致更少的代码整体改变。

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

      第二个和主要警告是我只是在玩这个,我没有在任何开发中使用它,绝对不是在生产中使用它,只是将我自己的代码涂抹在基本功能上像这样很可能会产生我的示例中没有出现的连锁反应。 使用风险自负

      (话虽如此,我正在 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,但是,嘿,无论如何我都需要连接字符串的地方,否则我根本不打算连接到数据库。

      您的自定义部分应在&lt;configSections/&gt; 中正常定义;完成这项工作的关键是然后放置一个空元素来代替您的正常设置;即代替 &lt;TestSettings configSource="..."/&gt; 或内联设置,只需输入 &lt;TestSettings/&gt;

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

      注意:反序列化需要一个文档片段(它希望在阅读器已经位于某个节点时被调用),而不是整个文档,因此如果您的部分存储在单独的文件中,您必须先删除 &lt;?xml ?&gt; 声明,否则您会得到 Expected to find an element

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-09-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多