Подтвердить что ты не робот

Как прочитать раздел конфигурации из 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 для обратной совместимости, но все же иметь возможность создавать его, используя XML-строку, полученную из БД.

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

4b9b3361

Ответ 1

Мое предложение состояло в том, чтобы сохранить ваш текущий класс MyConfig, но загрузите XML из своей базы данных в конструкторе, а затем в каждое свойство вашего MyConfig вы можете поместить логику, чтобы определить, откуда вы получаете значение (либо в базе данных, либо в. config file), если вам нужно вытащить конфигурацию из любого местоположения или вернуть ее, если значение пусто.

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; }
    }
}

Ответ 2

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

Ответ 3

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

Ответ 4

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

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

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

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

Ответ 5

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

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

Второй и MAIN CAVEAT - это то, что я просто играю с этим, я не использую его ни в разработке, ни определенно не в производстве, а просто размалываю свой собственный код по базовой функциональности например, это может иметь эффект постукивания, которые не отображаются в моем примере. Использовать на свой страх и риск.

(Сказав это, я тестирую его на сайте Umbraco, так что с нагрузками других разделов конфигурации происходит, и они все еще работают, поэтому я думаю, что он не имеет сразу ужасных эффектов)

EDIT: это .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 из базы данных.

ПРИМЕЧАНИЕ: десериализатор ожидает фрагмента документа (он будет вызван, когда читатель уже находится в node), а не весь документ, поэтому, если ваши разделы хранятся в отдельном файлов, вы должны сначала удалить объявление <?xml ?>, или вы получите Expected to find an element.