Как прочитать раздел конфигурации из XML в базе данных? - PullRequest
25 голосов
/ 02 декабря 2010

У меня есть класс 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, полученной из БД.

Возможно ли это? Если так, то как? (Имейте в виду, что он все еще должен работать, как описано выше)

Ответы [ 5 ]

14 голосов
/ 29 января 2011

Вот как я обычно это делаю: просто добавьте этих членов в класс 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;
6 голосов
/ 30 января 2011

Если вам нужно получить любую 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
    
3 голосов
/ 01 мая 2014

Довольно старый вопрос, но просто игра с решением этой проблемы. Это похоже на подход Саймона Мурье (который мне нравится больше в некотором смысле - менее хакерский), но означает, что любой код, который вызывает System.Configuration.ConfigurationManager.GetSection(), продолжит работать без необходимости изменять их для использования статического метода, поэтому может привести к меньшему количеству кода изменить в целом.

Первое основное замечание: я понятия не имею, работает ли это с вложенными разделами, но я почти уверен, что это не так. Практически основным предупреждением является то, что для него требуются изменения в классе раздела конфигурации, поэтому вы можете использовать его только с пользовательскими разделами, исходный код которых у вас есть (и разрешено изменять!)

Второй и 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.

3 голосов
/ 25 января 2011

Мое предложение было бы сохранить текущий класс 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; }
    }
}
3 голосов
/ 02 декабря 2010

Это сложная проблема. У вас может быть файл конфигурации с некоторым намеренно неправильным XML, переопределить OnDeserializeUnrecognizedElement в вашем ConfigurationSection и затем эффективно обойти файл для сопоставления ConfigurationSection (по сути, установить ваши свойства вручную) - потребуется некоторый рефакторинг, но вы все равно можете выставить те же свойства и т. Д. Это немного WTF, но возможно работоспособный.

Я в сущности опишу , как это сделать с помощью LINQ to XML в этом сообщении в блоге . Во всем моем коде сейчас у меня нет классов, которые полагаются на ConfigurationSection, я использую технику, описанную в моем блоге, чтобы обойти это и вернуть POCO через интерфейс. Это сделало мой код более модульно тестируемым, так как я могу легко использовать заглушку для интерфейса.

Я также могу легко переместить свою конфигурацию в БД, если я захочу это сделать - я просто создаю новый класс, который реализует мой интерфейс конфигурации, и переключаю его в своей конфигурации IoC. Microsoft не спроектировала систему конфигурации как гибкую, поэтому вы должны учитывать это при использовании ее в своем собственном коде.

Единственный другой способ, который я могу придумать, - это записать конфигурацию БД в файл, а затем прочитать ее, но это тоже странно!

...