65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 MyXaml 消费天气 Web 服务

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.77/5 (21投票s)

2004 年 5 月 9 日

9分钟阅读

viewsIcon

147014

downloadIcon

2

演示如何使用 MyXaml 消耗 Web 服务。

引言

我想用 MyXaml 尝试 Web 服务,了解它们可能如何实现,我认为一个有趣的服务是报告当前天气情况和预报。

要找到一个可用的天气 Web 服务本身就很有趣。我尝试的大多数服务都已失效或无法正常工作。我偶然发现了 Code Project 的文章《使用 Borland C# Builder 编写 Web 服务客户端》,作者是 Sagar Regmi,在研究了 EJSE 网站后,我发现那里有我需要的一切。

  • 当前状况
  • 预报
  • 漂亮的位图

后者,漂亮的位图,我偶然发现一个博客指出 EJSE 也提供图标(我想 这个 是我找到信息的博客)。

对于我们的国际社区,抱歉,此 Web 服务仅在美国境内有效。

如何创建 Web 服务客户端

创建 Web 服务客户端非常简单。

  1. 创建一个新的项目,选择“类库”。
  2. 右键单击“引用”,然后选择“添加 Web 引用”。
  3. 将 Web 服务的 WSDL 链接复制到 URL 文本框中。
  4. 单击“转到”。
  5. 单击“添加引用”。

Visual Studio 会创建一套漂亮的包装类来调用 Web 服务,这些类封装了返回数据,以及枚举。

创建 MyXaml 应用程序

MyXaml 应用程序包含几个部分:

  1. 应用程序中将要引用的程序集的命名空间。
  2. 窗体声明。
  3. 服务调用。
  4. 当前状况控件。
  5. 预报控件。

以下各节将详细介绍其中每一项。

在 MyXaml 中创建命名空间

需要创建四个 XML 命名空间:

  1. 默认命名空间,引用 System.Windows.Forms
  2. XWorkflow 插件的命名空间。
  3. 新创建的 Web 服务客户端的命名空间。
  4. 对象定义的命名空间。

结果如下:

<?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 - 如果定义了,返回值(一个对象,我们不关心类型)将被添加到定义列表中。属性值是键值对中的键。

在这种特定情况下,WeatherInfoExtendedWeatherInfo 返回的实例被存储在 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 实例的 Day1Day2Day3Day4Day5 成员,所有这些都是 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 符合标签是“类-属性-类”层次结构的理念(尽管在属性名称和类实现的属性值相同时,您可以使用“类-类”格式)。

  1. PictureBox 是一个类。
  2. Image 是该类的属性,它恰好由一个 Image 类实现,但由于其实现方式,我们无法直接使用它。
  3. 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、数据表或其他声明性初始化的信息,最终使我的应用程序更加健壮。我不需要重新编译代码,应用程序对新功能和现有功能的更改更具弹性,并且我可以更快地完成工作(是的,即使没有设计器!)。

许可证

本文没有明确的许可证,但可能包含文章文本或下载文件中的使用条款。如有疑问,请通过下方的讨论区联系作者。作者可能使用的许可证列表可以在 此处 找到。

© . All rights reserved.