使用 MyXaml 消费天气 Web 服务






4.77/5 (21投票s)
2004 年 5 月 9 日
9分钟阅读

147014

2
演示如何使用 MyXaml 消耗 Web 服务。
引言
我想用 MyXaml 尝试 Web 服务,了解它们可能如何实现,我认为一个有趣的服务是报告当前天气情况和预报。
要找到一个可用的天气 Web 服务本身就很有趣。我尝试的大多数服务都已失效或无法正常工作。我偶然发现了 Code Project 的文章《使用 Borland C# Builder 编写 Web 服务客户端》,作者是 Sagar Regmi,在研究了 EJSE 网站后,我发现那里有我需要的一切。
- 当前状况
- 预报
- 漂亮的位图
后者,漂亮的位图,我偶然发现一个博客指出 EJSE 也提供图标(我想 这个 是我找到信息的博客)。
对于我们的国际社区,抱歉,此 Web 服务仅在美国境内有效。
如何创建 Web 服务客户端
创建 Web 服务客户端非常简单。
- 创建一个新的项目,选择“类库”。
- 右键单击“引用”,然后选择“添加 Web 引用”。
- 将 Web 服务的 WSDL 链接复制到 URL 文本框中。
- 单击“转到”。
- 单击“添加引用”。
Visual Studio 会创建一套漂亮的包装类来调用 Web 服务,这些类封装了返回数据,以及枚举。
创建 MyXaml 应用程序
MyXaml 应用程序包含几个部分:
- 应用程序中将要引用的程序集的命名空间。
- 窗体声明。
- 服务调用。
- 当前状况控件。
- 预报控件。
以下各节将详细介绍其中每一项。
在 MyXaml 中创建命名空间
需要创建四个 XML 命名空间:
- 默认命名空间,引用
System.Windows.Forms
。 - XWorkflow 插件的命名空间。
- 新创建的 Web 服务客户端的命名空间。
- 对象定义的命名空间。
结果如下:
<?xml version="1.0" encoding="utf-8"?>
<!-- (c) 2004 Marc Clifton All Rights Reserved -->
<MyXaml
xmlns="System.Windows.Forms"
xmlns:def="Definition"
xmlns:wf="XWorkflow"
xmlns:ws="WeatherForecast.com.ejse.www, WeatherForecast">
命名空间声明的形式是:
- 命名空间
-或-
- 命名空间, 程序集
-或-
- 命名空间, 完全限定的程序集名称
当命名空间和程序集相同时,第一种形式就足够了。当命名空间与程序集名称不同时,必须使用第二种形式。第三种形式在您想引用特定程序集版本或区域性时非常有用。
窗体声明
窗体声明很简单。
<Form def:Name='AppMainForm'
ClientSize='525, 460'
StartPosition='CenterScreen'
FormBorderStyle="FixedSingle"
Text='Weather Forecast'>
这些都是 .NET Form 类的属性。“def:Name
”属性指示解析器在键值对列表中跟踪实例。键始终是属性值,值是类实例。
服务调用
首先,必须实例化服务。MyXaml 不仅仅是一个 UI 标记解析器。它可以实例化任何无参数类。在这种情况下:
<ws:Service def:Name="Service"/>
MyXaml 正在实例化服务。“ws:
”前缀告诉解析器该类位于先前命名空间声明引用的程序集中。同样,使用了“def:
”前缀,它告诉解析器保存对刚刚创建的实例的引用。即使 Service
类没有 Name
属性,上述语句仍会被处理。
接下来,调用服务的两个方法:
<wf:Invoke DefList="{MyXamlDefs}" Target="{Service}"
Method="GetWeatherInfo" Args="[int]02852" RetVal="Info"/>
<wf:Invoke DefList="{MyXamlDefs}" Target="{Service}"
Method="GetExtendedWeatherInfo" Args="[int]02852" RetVal="ExtInfo"/>
要获取您当地的天气,请编辑该文件并将“02852”替换为您所在的邮政编码。
虽然 MyXaml 支持内联和代码隐藏,但有时这有点过度。我们真正想做的是调用一个方法,向其传递一些参数,并可能对返回值做些什么。
上面的两行实例化了 XWorkflow
程序集中的 Invoke
类(由“wf:
”前缀确定)。对于每个实例化的类,解析器都会通过反射检查它是否有一个“Finisher
”方法。如果有,则调用该方法。对于 Invoke
类,Finisher
方法定义为通过反射调用 XML 属性中初始化的方法。
DefList
- 这是用于解析引用的定义列表。在这种情况下,我们正在传递 MyXaml 自己的定义列表,它会将其添加到自己的键值对象列表中,以便在标记中引用它。{}
大括号告诉解析器在对象列表中查找对象。Target
- 这是正在调用方法的对象。{}
大括号告诉解析器在对象列表中查找对象,这就是为什么我们在命名Service
实例时使用了“def:
”格式。Method
- 这是要调用的方法。Args
- 这些是逗号分隔的参数。请注意将string
转换为“int
”的临时解决方案。RetVal
- 如果定义了,返回值(一个对象,我们不关心类型)将被添加到定义列表中。属性值是键值对中的键。
在这种特定情况下,WeatherInfo
和 ExtendedWeatherInfo
返回的实例被存储在 MyXaml 定义列表中,键名为“Info
”和“ExtInfo
”。
Invoke
代码看起来是怎样的?有几个有趣的部分:
处理 Args 属性
public Type[] ProcessParams(object[] parms)
{
ArrayList types=new ArrayList();
for (int i=0; i<parms.Length; i++)
{
string parm=parms[i].ToString();
if (parm.StartsWith("*"))
{
parm=Lib.StringHelpers.RightOf(parm, '*');
if (defList.Contains(parm))
{
object obj=defList[parm];
parms[i]=obj;
}
}
else if (parm.StartsWith("["))
{
string parmType=Lib.StringHelpers.Between(parm, '[', ']');
if (parmType=="int")
{
parms[i]=Convert.ToInt32(
Lib.StringHelpers.RightOf(parm, ']'));
}
}
types.Add(parms[i].GetType());
}
return (Type[])types.ToArray(typeof(Type));
}
是的,这段代码有一个严重的临时解决方案,关于测试“[int]
” 子字符串
。我将在 MyXaml 的下一版本中修复它。但这段代码有趣之处在于它如何构建类型数组,这对于查找接受特定参数类型的特定方法是必需的(通常,.NET 可以自行解决。但在某些情况下,它无法解决,这会导致异常被抛出,例如参数歧义导致两个或多个匹配的方法,因此我费尽周折自行获取类型信息)。
通过反射调用方法
以下代码调用方法并处理返回值:
public void Finisher()
{
Type t=target.GetType();
object[] parms=ArgList.ToArray();
Type[] types=ProcessParams(parms);
MethodInfo mi=t.GetMethod(method, types);
object ret=null;
if (mi != null)
{
try
{
ret=mi.Invoke(target, parms);
if (RetVal != null)
{
defList[RetVal]=ret;
}
}
catch(Exception e)
{
...
}
}
else
{
...
}
}
当前状况控件
UI 当前状况部分的标记很简单。它是一堆标签:
<Controls>
<GroupBox Location="10, 10" Size="500, 230" Text="Current Conditions"
FlatStyle="System" Font="MS Sans Serif, 10pt">
<Controls>
<Label Location="10, 20" Size="100, 20" Text="Location:"/>
<Label Location="10, 40" Size="100, 20" Text="Last Updated:"/>
<Label Location="10, 80" Size="100, 20" Text="Temperature:"/>
<Label Location="10, 100" Size="100, 20" Text="Feel like:"/>
<Label Location="10, 120" Size="100, 20" Text="Humidity:"/>
<Label Location="10, 140" Size="100, 20" Text="Pressure:"/>
<Label Location="10, 160" Size="100, 20" Text="UV Index:"/>
<Label Location="10, 180" Size="100, 20" Text="Wind:"/>
<Label Location="10, 200" Size="100, 20" Text="Forecast:"/>
<Label Location="110, 20" Size="200, 20" Text="{Info.Location}"/>
<Label Location="110, 40" Size="380, 40" Text="{Info.LastUpdated}"/>
<Label Location="110, 80" Size="200, 20" Text="{Info.Temprature}"/>
<Label Location="110, 100" Size="200, 20" Text="{Info.FeelsLike}"/>
<Label Location="110, 120" Size="200, 20" Text="{Info.Humidity}"/>
<Label Location="110, 140" Size="200, 20" Text="{Info.Pressure}"/>
<Label Location="110, 160" Size="200, 20" Text="{Info.UVIndex}"/>
<Label Location="110, 180" Size="200, 20" Text="{Info.Wind}"/>
<Label Location="110, 200" Size="200, 20" Text="{Info.Forecast}"/>
</Controls>
</GroupBox>
这里有趣的是 Text="{Info.Location}"
和类似的属性。此语法指示解析器返回 {}
大括号内引用的对象。但是,它不仅仅返回对象,还使用点表示法告诉解析器返回对象属性的值,该属性的名称位于“.
”的右侧。此语法可用于深入到任何深度的属性,是一种非常方便的访问对象属性值的方式。
预报控件
五日预报运用了 MyXaml 的一个非常巧妙的功能,即包含重复 XML 块的能力。此功能用于显示 ExtendedWeatherInfo
实例的 Day1
、Day2
、Day3
、Day4
和 Day5
成员,所有这些都是 DayForecastInfo
对象。由于 Web 服务以这种方式得到了很好的结构化,因此我们可以利用 Include
处理器的前缀/后缀功能来适应我们感兴趣的特定成员实例。
请记住,解析器实例化任何类型的类。同样,Include
标签是用一个名为 Include
的类实现的。核心解析器不知道也不关心包含项,或者它正在实例化的类有什么特定之处。这一点非常重要。任何“自定义”解析都由核心解析器实例化的类来处理。本质上,MyXaml 是一个插件框架。
以下是其余的标记:
<GroupBox Location="10, 250" Size="100, 190"
Text="{ExtInfo.Day1.Day}" FlatStyle="System">
<Controls>
<Include Src="ExtForecast.mx"
ElementName="ExtendedForecast" Postfix="1"/>
</Controls>
</GroupBox>
<GroupBox Location="110, 250" Size="100, 190"
Text="{ExtInfo.Day2.Day}" FlatStyle="System">
<Controls>
<Include Src="ExtForecast.mx" ElementName="
ExtendedForecast" Postfix="2"/>
</Controls>
</GroupBox>
<GroupBox Location="210, 250" Size="100, 190"
Text="{ExtInfo.Day3.Day}" FlatStyle="System">
<Controls>
<Include Src="ExtForecast.mx"
ElementName="ExtendedForecast" Postfix="3"/>
</Controls>
</GroupBox>
<GroupBox Location="310, 250" Size="100, 190"
Text="{ExtInfo.Day4.Day}" FlatStyle="System">
<Controls>
<Include Src="ExtForecast.mx"
ElementName="ExtendedForecast" Postfix="4"/>
</Controls>
</GroupBox>
<GroupBox Location="410, 250" Size="100, 190"
Text="{ExtInfo.Day5.Day}" FlatStyle="System">
<Controls>
<Include Src="ExtForecast.mx"
ElementName="ExtendedForecast" Postfix="5"/>
</Controls>
</GroupBox>
</Controls>
</Form>
</MyXaml>
对于每个 GroupBox
,请注意文本是如何设置为“{ExtInfo.Day<n>.Day}
”的,其中 <n>
用于第 1-5 天。第二点要注意的是“Postfix
”属性,它指定了一个值。Include
类允许您定义一个前缀和一个后缀值,它们可以使用 #prefix#
和 #postfix#
表示法应用于包含标记中的任何位置。
好吧,这种表示法有点疯狂,不是吗?嗯,不管怎样,在我多年的应用程序开发中,我使用过这个功能,使用应用程序自动化层,我可以证明它无与伦比的灵活性和强大功能。
预报包含文件
那么,包含文件是什么样的?这是标记:
<?xml version="1.0" encoding="utf-8"?>
<!-- (c) 2004 Marc Clifton All Rights Reserved -->
<MyXaml
xmlns="System.Windows.Forms"
xmlns:def="Definition"
xmlns:wf="XWorkflow"
xmlns:ws="WeatherForecast.com.ejse.www, WeatherForecast">
<Element Name="ExtendedForecast">
<PictureBox Location="22, 20" Size="56, 48">
<Image>
<Bitmap URL="http://www.ejse.com/WeatherService/images/52/
ExtInfo.Day#postfix#.IconIndex}.gif"/>
</Image>
</PictureBox>
<Label Location="10, 80" Size="80, 25"
Text="{ExtInfo.Day#postfix#.Forecast}" TextAlign="MiddleCenter"/>
<Label Location="10, 105" Size="80, 20"
Text="Precip. Prob.:" TextAlign="MiddleCenter"/>
<Label Location="10, 125" Size="80, 20"
Text="{ExtInfo.Day#postfix#.PrecipChance}" TextAlign="MiddleCenter"/>
<Label Location="10, 145" Size="80, 20"
Text="{ExtInfo.Day#postfix#.High}" TextAlign="MiddleCenter" ForeColor="Red"
Font="MS Sans Serif, 10pt, style=Bold"/>
<Label Location="10, 165" Size="80, 20"
Text="{ExtInfo.Day#postfix#.Low}" TextAlign="MiddleCenter" ForeColor="Blue"
Font="MS Sans Serif, 10pt, style=Bold"/>
</Element>
</MyXaml>
在这里,我们看到了一些东西。首先是 PictureBox
。MyXaml 符合标签是“类-属性-类”层次结构的理念(尽管在属性名称和类实现的属性值相同时,您可以使用“类-类”格式)。
PictureBox
是一个类。Image
是该类的属性,它恰好由一个Image
类实现,但由于其实现方式,我们无法直接使用它。- MyXaml 中的
Bitmap
类返回一个Image
,并且有一些智能功能——图像可以从 URL、文件或资源中获取。
解析器遵循“类-属性-类”规则,将 Bitmap
类构造的对象分配给 PictureBox
实例的 Image
属性。
注意 URL。使用 #postfix#
语法,我们可以提取特定天引用的 IconIndex
。类似地,提取由 #postfix#
内容确定的实例的预报、降水概率、最高和最低温度文本。
当包含标记时,Include
类中实现的预处理器会遍历属性值,并将所有 #prefix#
和 #postfix#
的出现替换为 Include
标记中指定的值。是的,这很耗时,而且可以进行很多优化。无论如何,此功能提供的功能非常强大。它使我们不必一遍又一遍地编写相同的标记(在这种情况下,写了 5 次!)
结论
除了演示一个简单的 Web 服务之外,我希望这篇文章主要能给您一些关于如何使用 XML 来声明性地实例化类的有趣想法。其中一些技术,例如 Include
功能,非常巧妙——例如,您可以创建一个 UI 构建块的库。请记住,这不仅仅限于 UI。在我的 博客 中,我演示了如何使用 Include
标签加载包含州及其缩写的 DataTable
。
使用 MyXaml,或任何通用声明性实例化引擎,确实改变了您对编程的思考方式。除了帮助解耦对象之外,这种编程方法还可以很好地将 UI 与控件逻辑分离。如果您将这个想法泛化,您会发现它在程序的“被动”部分和“主动”部分之间产生了良好的分离。我发现,很多时候,被动部分会随着时间而改变,而主动部分则保持不变。能够轻松修改 UI、数据表或其他声明性初始化的信息,最终使我的应用程序更加健壮。我不需要重新编译代码,应用程序对新功能和现有功能的更改更具弹性,并且我可以更快地完成工作(是的,即使没有设计器!)。
许可证
本文没有明确的许可证,但可能包含文章文本或下载文件中的使用条款。如有疑问,请通过下方的讨论区联系作者。作者可能使用的许可证列表可以在 此处 找到。