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

时间:2010-12-02 19:49:20

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

我有一个像这样的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字符串来实例化它。

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

5 个答案:

答案 0 :(得分:14)

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

答案 1 :(得分:6)

如果您需要将任何 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方法的所有类。 e.g。

    
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
    

答案 2 :(得分:3)

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

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

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

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

答案 3 :(得分:3)

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

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 :(得分:3)

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

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

第二个和 MAIN CAVEAT 是我正在玩这个,我不是在开发中使用它,绝对不是生产,只是涂抹我自己的代码像这样的基本功能可能会产生在我的例子中没有出现的连锁效应。 使用风险自负

(话虽如此,我在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,但是嘿,我还是需要一个连接字符串的地方,或者我不打算连接到了数据库。

您的自定义栏目应在<configSections/>中定义为正常;做这项工作的关键是然后用一个空元素代替你的正常设置;即代替<TestSettings configSource="..."/>或您的内联设置,只需添加<TestSettings/>

即可

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

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