使用 XAML 进行自定义应用程序配置






3.74/5 (15投票s)
描述了为什么以及如何使用 XAML 作为纯 XML 的替代方案来指定配置文件
引言
应用程序设计和开发中的一个流行模式是使用离线文件来持久化配置信息,这样下游用户就可以在不要求重新生成活动(如SDLC)的情况下修改与应用程序相关的某些属性。当我担任Sun MicroSystems的Java架构师时,Properties文件是实现此目的的规定方法。虽然它提供了一些灵活性,但对于许多情况来说,尤其是一些需要分层结构的情况,它肯定不是理想的。随着.NET的出现,微软选择了更加强大和强大的可扩展标记语言作为存储离线配置参数的底层机制。这效果很好。XML是此类场景的完美解决方案。它的实现相对简单,速度快,但却非常强大、可扩展且灵活。
然而,XML再好,也有一些不足之处。如果配置文件是Microsoft的标准配置文件(如app.config或web.config)之外自定义的,那么模式验证和同步问题可能会带来额外的麻烦,尤其是因为它们通常是不必要的。我的意思是,这些任务与应用程序的实际配置无关,但人们会花很多时间考虑它们。对于标准的config文件,对于config文件格式的直观理解存在普遍的缺乏。例如,只需知道如何配置WCF服务即可。没有办法在不知道每个单词/字符的拼写和大小写的情况下,从头开始直观地配置一个。我必须承认,intellisense引擎在很多方面都得到了帮助,它似乎是构建在模式规范之上的,但我发现对于标准的config文件来说,它仍然不够,而且对于自定义文件,我不想为我的配置编写模式。
背景
现在,随着.NET 3.0的出现,出现了XAML的概念。XAML不仅仅是一个演示性DSL,它实际上是一种使用XML描述对象运行时状态的强大符号。在某些情况下,它等同于使用C#、VB或您选择的任何语言来描述这种运行时行为。由于其描述对象的非常通用的方法,XAML不仅用于演示描述(WPF),还用于描述工作流(WF)。赋予XAML惊人力量的最后一件事是,微软通过它引入了一个动态加载框架!这对普通人意味着什么?这意味着纯XAML不必编译即可工作;它可以在运行时读取,仍然可以生成适当的对象图。想想JavaScript如何能够像json一样产生强大而动态的运行时行为,这得益于eval
函数。(当然,JavaScript提供了一个更健壮的实现,因为整个语言都是后期绑定的。C#团队,请给我们动态编程!)
从这一切中应该清楚的是,XAML是(并且我认为是)离线配置的完美选择,也是其下一次的演进(以及其他一些本文范围之外的酷炫功能)。本系列文章详细介绍并提供示例,说明如何设置和使用XAML作为配置文件,或作为标准配置文件的补充。它还包括一个XAML配置节库,用于将XAML附加到您现有的app.config或web.config文件中。
流程
抛开WPF或WF的云雾不谈,大多数人都会同意XAML本身是一种非常简单的表示对象的方法。事实上,任何可访问的类都可以表示为XAML中的对象形式。下面是XAML中.NET string
的表示形式
<String xmlns=""clr-namespace:System;assembly=mscorlib"" >hello world</String>
将此值读入您的应用程序需要三个简单的步骤。
首先,应用程序必须引用presentationcore和presentationframework。这是一个小的挫折,我相信有人会在某个时候解决这个问题。
其次,现在我们有了XamlReader
和XamlWriter
(位于namespace system.windows.markup
中),我们可以使用Load
和Save
方法分别将XAML转换为它表示的实例,或将实例转换为其XAML表示。最后,剩下的就是设计一个新类来支持我们所有的配置需求(尝试使其可序列化),并将该类的实例表示为XAML符号。所以,让我们开始创建一个简单的项目来使用这个string
配置。首先,创建一个简单的控制台应用程序,并添加对presentationcore.dll和presentationframework.dll的引用。完成之后,将以下代码行添加到Main
方法中
string xaml = @"<string assembly=""mscorlib"" >
<String xmlns=""clr-namespace:System;assembly=mscorlib"" >
hello world</String></string >";
byte[] xaml_data = System.Text.Encoding.ASCII.GetBytes(xaml);
System.IO.MemoryStream xaml_stream = new System.IO.MemoryStream(xaml_data);
string xaml_object = (string)System.Windows.Markup.XamlReader.Load(xaml_stream);
Console.WriteLine(xaml_object);
如果您要使用文字string
,请务必使用双引号表示法,就像我在这里所做的那样。运行此代码将在控制台窗口中生成string hello world
。如果您不熟悉XAML,这里有一件重要的事情需要注意。就像在C#或任何其他.NET语言中,您必须声明您计划使用的namespace
,以便编译器知道如何解析您在列表中使用的类型一样,XAML,更具体地说,xamlreader
和xamlwriter
类,要求您指定一个namespace
来帮助解析您在文档中指定的元素。在这种情况下,属性xmlns="clr-namespace:System;assembly=mscorlib"
指定文档的默认namespace
将是程序集mscorlib
中的System namespace
。因此,该namespace
(在该程序集中)中的任何类型都可以使用。尝试使用namespace
中的不同类型,更改namespace
和/或程序集,并更改您指定的类型的值。
您首先应该从您所做的修改中注意到的是,每次都需要重新编译代码才能看到更改反映出来。显然,对于配置文件来说,这并不是理想的情况,所以我们将把XAML移出代码库,放到文件系统中的一个平面文件中。正如您可能从XamlReader
的Load
函数的各种重写中注意到的那样,它也接受一个XmlReader
或一个Stream
。这两种类型都可以在这里用作读取XAML文件的机制。下面的代码显示了一个实现,它读取与可执行文件位于同一目录中的示例文件config.xaml。
注意:目前没有办法将XAML配置文件添加到Visual Studio项目中,所以您必须自己想办法。您可以添加一个简单的文本文件,然后将其文件扩展名更改为XAML,或者您可以添加一个标准的XAML文件(对于WinForm应用程序,目前是一个UserControl
),然后根据需要进行修改。如果您选择第二种方法,请务必删除与XAML文件关联的代码隐藏文件;另外,将文件的生成操作更改为None(在属性窗口中选中文件时),并删除Custom Tool属性项中的文本。由于从空白文件开始没有缺点,我建议使用它而不是后一种方法。
生成的代码如下所示
using (FileStream xaml_stream = File.OpenRead("config.xaml"))
{
string xaml_object = (string)System.Windows.Markup.XamlReader.Load(xaml_stream);
Console.WriteLine(xaml_object);
}
现在我们已经成功地演示了我们可以从外部XAML文件读取类型并在宿主应用程序中对其进行处理,下一步是创建一个实际的配置文件,而不是使用内置的.NET类型。在简单的XMLconfig文件的时代,您设计了您的模式(或只是开始编写XML),然后构建了一个专门的类型来读取该模式,就这样。我认为它已经发展到使用XML序列化器本质上以XML形式表示预定义的类型。这种方法与之相似,但更侧重于XML(XAML)而不是它们背后的实际类。更重要的是,XAML提供了一种比XML序列化器生成的XML更灵活、更强大的描述对象的方法。
第一步是创建一个类来表示配置。下面的列表代表了一个名为LaunchPoint
的开源项目的配置,该项目在Codeplex和Sourceforge上,旨在为构建安全智能客户端应用程序(基于WPF)创建一个统一且简化的框架。如果您处理过WCF,这看起来会有点像数据契约的构成方式。
namespace MindFactorial.LaunchPoint.Runtime
{
[Serializable]
public class LaunchPointConfiguration
{
public bool LaunchInDefaultContext { get; set; }
public LaunchPointApp Application { get; set; }
public PluginConfigList Plugins { get; set; }
public override string ToString()
{
string xml = XamlWriter.Save(this);
return xml;
}
}
[Serializable]
public class LaunchPointApp
{
public string Path { get; set; }
public string Name { get; set; }
public string RunnableTypeName { get; set; }
}
[Serializable]
public class PluginConfig
{
public string Name { get; set; }
public string Path { get; set; }
public string Label { get; set; }
}
[Serializable]
public class PluginConfigList : List { }
}
这里包含了namespace
名称,因为正如我们之前所见,它对于XamlReader
在将XAML加载到适当的对象中至关重要。应该注意的是,任何无效的XAML或无法发现的类型都将导致解析器异常。这实际上是一件好事,因为它为我们提供了类似模式的验证,而无需创建模式。使用上述模式和实践,这个类可以用XAML表示如下
<LaunchPointConfiguration
xmlns="clr-namespace:MindFactorial.LaunchPoint.Runtime;
assembly=MindFactorial.LaunchPoint.Runtime"
>
<LaunchPointConfiguration.Application>
<LaunchPointApp>
<LaunchPointApp.Name>Console</LaunchPointApp.Name>
<LaunchPointApp.Path>MindFactorial.LaunchPoint.Console.dll</LaunchPointApp.Path>
<LaunchPointApp.RunnableTypeName>MindFactorial</LaunchPointApp.RunnableTypeName>
</LaunchPointApp>
</LaunchPointConfiguration.Application>
<LaunchPointConfiguration.Plugins>
<PluginConfigList>
<PluginConfig>
<PluginConfig.Name>one</PluginConfig.Name>
<PluginConfig.Label>first plugin</PluginConfig.Label>
<PluginConfig.Path>debug\generalsettings.plugin.dll</PluginConfig.Path>
</PluginConfig>
</PluginConfigList>
</LaunchPointConfiguration.Plugins>
</LaunchPointConfiguration>
从namespace
声明中可以看出,处理此文件需要处理器引用程序集MindFactorial.LaunchPoint.Runtime
。这是指定LaunchPointConfiguration
类型的程序集。如果此文件与XAML加载应用程序位于同一程序集中,则可以忽略namespace
声明的程序集部分。
历史
- 2008年4月16日:初稿