Как прочитать раздел конфигурации из 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-строку, полученную из БД.
Возможно ли это? Если да, то как? (Имейте в виду, что он все равно должен работать как экземпляр выше)
Ответы
Ответ 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
.