简单灵活地访问和存储配置设置






4.59/5 (9投票s)
一种管理应用程序配置的简便方法,具有广泛的存储选项。
引言
最近,我一直在处理一项任务,该任务需要极大的灵活性来存储和访问配置设置。我在网上搜索了大量解决方案和示例,但没有一个真正满足我的要求。这迫使我放弃了搜索,并考虑自己编写一个版本。一旦我编写的版本似乎可行,我就决定分享它,以防有人觉得它比其他现有选项更有用。
背景
为了完成这项任务,我必须确保以下条件得以满足,以便使其有用:
- 用户应能够轻松地更改或选择配置数据的存储方式和位置,而不会影响可配置对象。
- 对用户而言,访问和存储配置应非常简单且透明,但同时要可靠。
- 负责发起加载配置的进程不知道任何子对象的类型,并且子对象的创建取决于特定配置。
当我处理一个 Web 项目并使用 View State 在页面回发之间存储一些值时,一个潜在的解决方案浮现在我的脑海中。这让我想起字符串可以表示任何数据,包括二进制数据。它们相对容易使用,并且几乎可以存储在任何地方——XML 文件、数据库,甚至二进制文件。就像我们在 XML 中操作字符串一样,我们可以将数据作为字符串进行访问和存储,并在加载时将其转换为适当的数据类型。
为了能够管理复杂的配置,我必须想出两个我们将称之为上下文和值的概念。上下文只是一个容器,它包含其他上下文和值的集合,而值只是简单的字符串值。加载和保存是通过两个执行读取和写入配置到相应上下文的方法完成的,这类似于 `IXmlSerializable` 接口。
使用代码
您需要实现 `IConfigurationElement` 接口,以便可以保存和加载类配置。该接口类似于 `IXmlSerializable` 接口,并包含两个方法:`ReadConfiguration` 和 `WriteConfiguration`。顾名思义,一个用于读取配置,另一个用于写入。
public interface IConfigurationElement
{
void ReadConfiguration(IConfigurationContext context);
void WriteConfiguration(IConfigurationContext context);
}
由于我们只处理字符串,我们可以使用索引器来加载和保存值。用户在读取时负责将字符串转换为适当的数据类型,在写入时负责将适当的数据类型转换回字符串。例如:
// writing
context["DateOfBirth"] = DateOfBirth.ToShortDateString();
// reading
DateTime dt;
DateTime.Parse(context["DateOfBirth"], out dt);
现在的问题是,如果我们想保存一部分配置值怎么办?在这种情况下,您需要从当前上下文中获取一个上下文对象,方法是创建一个新的上下文对象或获取一个现有的上下文对象。为此,`IConfigurationContext` 提供了三个方法:`Get`、`GetAll` 和 `Create`。`Get` 方法接受一个指定参数名称的字符串参数,并返回一个上下文列表。`GetAll` 将返回与当前上下文关联的所有上下文,您可以通过使用 `Name` 属性来访问上下文名称。相应地,`Create` 方法将创建一个具有提供名称的新上下文。
这是完整的接口定义
public interface IConfigurationContext
{
IEnumerable<IConfigurationContext> Get(string contextName);
IEnumerable<IConfigurationContext> GetAll();
string this[string name] { get; set; }
string Name { get; }
IConfigurationContext Create(string contextName);
}
下面是一个访问和存储个人姓名(`FirstName` 和 `LastName`)和邮寄地址(`MailingAddress`)的示例。下面的示例不包含 `PostalAddress` 类的实现,但它与此处未显示的任何其他验证逻辑一起包含在附件的源代码中。
public class Person : IConfigurationElement
{
public string FirstName { get; set; }
public string LastName { get; set; }
public PostalAddress MailingAddress { get; set; }
public void WriteConfiguration(IConfigurationContext context)
{
context["FirstName"] = FirstName;
context["LastName"] = LastName;
var ctx = context.CreateContext(“MailingAddress”);
MailingAddress.WriteConfiguration(ctx);
}
public void ReadConfiguration(IConfigurationContext context)
{
FirstName = context["FirstName"];
LastName = context["LastName"];
var ctx = context.GetContext(“MailingAddress”);
MailingAddress.ReadConfiguration(ctx);
}
}
现在让我们看看如何使用它。为了演示,我创建了一个 `IConfigurationContext` 的工作实现,它使用 `XmlNode` 来存储配置。所以下面是一个应该很容易理解的例子。
// loading
static void Main()
{
var xmlDoc = new XmlDocument();
xmlDoc.Load(“FileName“);
var context = new XmlConfigurationContext(xmlDoc.DocumentElement);
var person = new Person();
person.LoadConfiguration(context);
}
// saving
static void Main()
{
var xmlDoc = new XmlDocument();
var context = new XmlConfigurationContext(xmlDoc, "Person");
person.WriteConfiguration(context);
xmlDoc.Save(“FileName“);
}
下面是创建的配置文件示例
<Context Name="Person">
<Value Name="FirstName">FirstName</Value>
<Value Name="LastName">LastName</Value>
<Context Name="MailingAddress">
<Value Name="Street1">Street1</Value>
<Value Name="Street2">Street2</Value>
<Value Name="City">City</Value>
<Value Name="State">State</Value>
<Value Name="Zip">Zip</Value>
</Context>
</Context>
那么,让我们回顾一下我的要求。
- 用户应能够轻松地更改或选择配置数据的存储方式和位置,而不会影响可配置对象。我们通过提供 `IConfigurationContext` 的不同实现来实现了这一点。因此,除了 `XmlConfigurationContext`,我们还可以轻松创建 `DatabaseConfigurationContext` 或 `WebServiceContext` 或其他变体。
- 对用户而言,访问和存储配置应非常简单且透明,但同时要可靠。使用简单的索引器可以轻松直接地访问设置。
- 负责发起加载配置的进程不知道任何子对象的类型。在加载 `Person` 时,我们不知道它将保存哪些设置,也不关心,我们只需要提供保存或查找所需值的上下文。