首页 新闻 论坛 群组 Blog 文档 下载 读书 Tag 网摘 搜索 开源 FAQ 第二书店 博文视点 程序员
频道: 研发 数据库 中间件 信息化 视频 .NET Java 游戏 移动 服务: 人才 外包 培训
    图书品种:235680
       
热门搜索: ASP.NET Ajax Spring Hibernate Java

Web服务是B2B交互的一种非常好的机制,也可用于各种不同领域的交叉,如技术的、信任和安全的、部署布局的、组织的、地域的各种领域。Web服务的核心是能够非常方便地开发服务并调用它们的方法,就如同常规组件那样容易。Web服务标准甚至提供接口定义和逻辑抽象服务。第1章中已解释过,整本书中也有不少实例,它们都说明了“接口从实现分离”是面向组件编程的核心原则,也是应用程序可扩展性和复用的关键。本附录描述了在Visual Studio 2005中开发基于接口的Web服务的一组简单的步骤。本书附带的源代码包含下面列出的代码,以及使用这些代码的CalculationServices解决方案。

.NET中的Web服务支持

.NET Web Services Support

仔细研究示例A-1中的SimpleCalculator Web服务,它提供了四种基本的算术运算。

示例A-1:SimpleCalculator Web服务

using System.Web.Services;

   

[WebService(Namespace="http://CalculationServices",

            Description = "The SimpleCalculator Web Service provides the

                           four basic arithmetic operations for integers.")]

public class SimpleCalculator

{

   [WebMethod]

   public int Add(int argument1,int argument2)

   {

      return argument1 + argument2;

   }

   [WebMethod]

示例A-1:SimpleCalculator Web服务(续例)

   public int Subtract(int argument1,int argument2)

   {

      return argument1 - argument2;

   }

   [WebMethod]

   public int Divide(int argument1,int argument2)

   {

      return argument1 / argument2;

   }

   [WebMethod]

   public int Multiply(int argument1,int argument2)

   {

      return argument1 * argument2;

   }

}

使用.NET开发一个Web服务,你所要做的全部事情就是为你希望暴露成Web服务的方法添加WebMethod属性,然后将剩下的事情交给.NET去做。虽然WebServiceAttribute属性是可选的,但你应该使用它。其属性定义如下:

[AttributeUsage(AttributeTargets.Class|AttributeTargets.Interface)]

public sealed class WebServiceAttribute : Attribute

{

   public WebServiceAttribute();

   public string Description{get; set;}

   public string Name{get; set;}

   public string Namespace{get; set;}

}

WebServiceAttribute让你可以指定一个包含了你的服务的Web服务命名空间,就像使用普通的.NET命名空间一样,这可减少冲突。如果不指定一个命名空间,Visual Studio 2005 将把http://tempuri.org/作为默认命名方式。每种发布的服务使用一种具体的URI作为它的命名空间。WebServiceAttribute也允许你为服务提供一个自由文本的描述。这个描述,服务的使用者和开发过程中的测试人员能够在自动生成的浏览器页面中看到。

为Web服务编写客户端代码也一样简单。可以在Visual Studio 2005中从客户端项目中选择“添加Web引用”,并将向导指向包含Web服务的.asmx文件(也可从你的解决方案或机器中选择一个Web服务)。一旦向导给出了可用的Web服务,选择需要的Web服务,并点击“添加引用”。 这将产生一个代理类,而客户端可以使用该类来调用Web服务。示例A-2展示了示例A-1中提到的服务的代理类。为了简单明了,已删除了其中一些代码。

示例A-2:SimpleCalculator Web Service代理类

public partial class SimpleCalculator : SoapHttpClientProtocol

{

示例A-2:SimpleCalculator Web Service代理类(续例)

   public SimpleCalculator()

   {

      Url = Settings.Default.<App name>_<Reference name>_SimpleCalculator;

   }

   [SoapDocumentMethod("http://CalculationServices/Add")]

   public int Add(int argument1,int argument2)

   {

      object[] results = Invoke("Add",new object[]{argument1, argument2});

      return (int)(results[0]);

   }

   //其他方法和属性

}

针对原始Web服务暴露的每个Web方法,SimpleCalculator代理类也包含一个公共方法。通过使用远程服务,代理类(有时被称做Web服务包装类)完全封装了与远程服务复杂的交互过程。

这种代理类提供Url属性,用来设置和获得服务地址。当在Visual Studio 2005中添加一个Web引用时,项目的Settings类也被添加了一个属性,命名依照如下格式:

<应用名>_<引用名>_<服务名>(<App name>_<reference name>_<Service name>)

并把它绑定到应用程序配置文件的对应项上,该项包含服务地址。此外,Visual Studio 2005会用DefaultSettingValue属性(atrribute)来修饰属性。DefaultSettingValue属性包含Web服务的默认地址,以防应用程序配置文件中的对应项缺失。默认地址将是原始引用的Web地址。

客户代码可使用代理类,就像把SimpleCalculator对象当作本地对象一样:

SimpleCalculator calculator;

calculator = new SimpleCalculator();

int result = calculator.Add(2,3);

Debug.Assert(result == 5);

问题陈述

Problem Statement

使用刚刚介绍的编程模型,客户端是对服务提供程序直接编程(这里指SimpleCal- culator),而不是对该服务的一般抽象。更好的解决方法是,使SimpleCalculator web服务以服务抽象的形式呈现多态性——即接口。对一个接口而不是一个特定的服务实现编程,有利于客户端在不同的服务提供者之间切换,而只须作很小的改动,甚至基本不需要改动。这样,客户端无需理会服务提供程序的改动。例如:假设客户端想从SimpleCalcul-

ator切换到另一个称为ScientificCalculator的calculator Web服务,这个服务有与SimpleCalculator相同的接口,但可能比SimpleCalculator更准确、更快,或更经济。最好要么是客户端要么是服务提供端,来定义一个抽象的计算器接口——ICalculator接口:

[WebInterface]//假想属性,在.NET中实际不存在

public interface ICalculator    

{

   int Add(int argument1,int argument2);

   int Subtract(int argument1,int argument2);

   int Divide(int argument1,int argument2);

   int Multiply(int argument1,int argument2);

}

如果有这样一个Web接口的话,客户端就能根据接口定义编写代码,而不是一个特定的实现。如示例A-3所示。

示例A-3:Web服务客户端基于接口的编程模型

ICalculator calculator = new ScientificCalculator();

// 或者

ICalculator calculator = new SimpleCalculator();

//客户端的这部分代码是任何服务提供程序的多态:

int result = calculator.Add(2,3);

Debug.Assert(result == 5);

提示:微软的下一代分布式应用程序技术(代号名为Indigo),可使用包括Web服务在内的多种传输协议连接逻辑服务。Indigo通过使用一个表现为.NET接口的抽象契约定义,来实现接口和实现分离。

当客户端切换服务提供者时,客户端代码中唯一需要更改的只是选择使用哪个接口实现的那一行代码。你甚至可以考虑把这个选择的决定不放在客户端主逻辑的程序集中而放在另一个程序集中,在这两个程序集之间仅传递接口。客户端也可以使用类工厂来创建对象和获取一个接口。基于接口的Web服务还有另外的好处,例如:客户端可以公布接口定义,这使得不同的服务提供商更容易实现该客户端的需要。

解决方案

Solution

下面将要介绍的技巧需要Web服务提供程序和客户端在编写代码时使用略微不同的方式,来使用基于接口的Web服务。在创建一个基于接口的Web服务时,首先公布Web服务的

接口定义。为了简单起见,假定服务提供程序既要负责定义接口,又要实现接口。客户端或第三方可以通过很方便的方式,公开Web服务接口定义并让任何人去实现它,但这需要一两个额外的步骤(以后再详细说明)。

服务端的步骤

Service-Side Steps

在Visual Studio 2005中,选择“文件 > 新建 > 网站”,打开“新建网站”对话框,选择“ASP.NET Web”服务并命名为CalculationServices。Visual Studio 2005 中创建了一种名为Service的Web服务骨架。你根本用不上它,所以从项目文件的根目录中删除文件Service.asmx,并从App_Code文件夹中删除Service.cs。为了添加Web服务接口,右键单击“项目”,然后选择“添加新项…”——从“文件”菜单中弹出“添加新项”对话框。在对话框中选择“Web服务”,并命名为ICalculator。确认“将代码放在单独的文件中”复选框处于选中状态,然后点击“添加”。

打开App_Code文件夹中的ICalculator.cs文件,然后把ICalculator的类型定义从类改成接口。删除构造函数、HelloWorld()方法以及从WebService的派生。你也可以删除公共接口访问修饰符。更改WebServiceBinding属性为:

[WebServiceBinding (Name = "ICalculator")]

下一步,添加Add()、Subtract()、Divide()和Multiply()接口方法,在接口上的每个方法应用WebMethod属性。接口看起来应该如下所示:

[WebServiceBinding (Name = ICalculator)]

interface ICalculator    

{

   [WebMethod(Description = "将两个整形相加并返回其和")]

   int Add(int argument1,int argument2);

        

   [WebMethod(Description = "两数相减并返回结果")]

   int Subtract(int argument1,int argument2);

        

   [WebMethod(Description = "两数相除并返回结果")]

   int Divide(int argument1,int argument2);

        

   [WebMethod(Description = "两数相乘并返回结果")]

   int Multiply(int argument1,int argument2);

}

WebServiceBinding属性表明ICalculator接口定义了一个Web服务契约的接口,但本身不作为一个Web服务暴露。其他有权访问ICalculator元数据定义的类现在能实现(或绑定)该服务契约定义。

下一步,在Web服务类中实现ICalculator接口。这类似于在.NET中实现其他任何接口:类必须派生于该接口,并为它的方法提供实现。例如,在“添加新项”对话框中添加两个Web服务,SimpleCalculator和ScientificCalculator。清除向导产生的代码,如前所述。把它们添加到ICalculator的派生类中,然后实现它。

添加WebService属性以具体指定命名空间和描述。示例A-4是ICalculator的两种实现方式。需要注意的是,编译器要求服务提供程序实现接口定义的所有方法。

示例A-4:ICalculator接口的两种不同的Web服务实现

[WebService(Namespace="http://CalculationServices",

            Description = "The SimpleCalculator web service implements ICalculator.

                           It provides the four basic arithmetic operations.")]

class SimpleCalculator : ICalculator

{

   public int Add(int argument1,int argument2)

   {

      return argument1 + argument2;

   }

   //其他ICalculator方法

}

[WebService(Namespace="http://CalculationServices",

            Description = "The ScientificCalculator web service implements 

            ICalculator. It provides the four basic arithmetic operations.")]

class ScientificCalculator : ICalculator

{

   public int Add(int argument1,int argument2)

   {

      return argument1 + argument2;

   }

   //其他ICalculator方法

}

提示:一个类能实现多个以WebServiceBinding属性修饰的接口,每个接口对应不同的服务契约。

现在,你要将接口暴露为Web接口。但是,你无法将接口暴露为Web服务。为了克服这种障碍,你需要提供接口垫片(interface shim)——从外部看上去像是一个纯粹的接口定义的一个抽象类。为Web服务项目添加名为ICalculatorShim的类,把它放置在Icalculator- Shim.cs文件中,把该类定义为抽象类,然后使其派生于ICalculator。只提供抽象方法作为接口实现。

[WebService(Name = "ICalculator",Namespace="http://CalculationServices",

            Description = "This web service is only the definition of the

                           interface. You cannot invoke method calls on it.")]

abstract class ICalculatorShim : ICalculator

{

   public abstract int Add(int argument1,int argument2);

   public abstract int Subtract(int argument1,int argument2);

   public abstract int Divide(int argument1,int argument2);

   public abstract int Multiply(int argument1,int argument2);

由于它派生于ICalculator,编译器强迫垫片类暴露接口定义的所有方法。使用WebSer- viceAttribute属性提供命名空间和说明。最重要的是,设置WebServiceAttribute的Name属性为ICalculator。这样做,就暴露了ICalculator的服务定义,而不是Icalcul- atorShim。

最后,修改ICalculator.asmx文件,以便编译器从正确的文件中加载ICalculatorShim类。

<%@ WebService Language="C#" CodeBehind="~/Code/ICalculatorShim.cs"

    Class="ICalculatorShim"%>

为了验证所有这些,设置ICalculator.asmx文件为起始页,然后运行该项目。自动生成的浏览器测试页面将展示ICalculator的接口定义(见图A-1)。

图A-1:ICalculator自动生成的测试页面

如果你试图调用ICalculator的任何方法,都会返回一个错误,因为该服务没有任何实现。

客户端步骤

Client-Side Steps

客户端需要有接口定义以便能依据该定义编程。客户端能通过两种方法获取该定义。第一种方法是使用WSDL.exe命令行工具。使用/serverInterface开关,你可以指示WSDL.exe生成一个与Web服务契约相对应的接口定义。假定接口定义在http://localhost/ CalculationServices.com/ICalculator.asmx,使用命令行工具:

WSDL.exe /serverInterface http://localhost/CalculationServices.com/ICalculator.asmx

/out:ICalculator.cs

WSDL.exe将会生成一个名为ICalculator.cs的文件,此文件包含一个名为IICalculator的接口定义:

[WebServiceBinding (Name = "ICalculator",

                   Namespace="http://CalculationServices/")]

public partial interface IICalculator

{

   [WebMethod]

   [SoapDocumentMethod("http://CalculationServices/Add",...]  

   int Add(int argument1,int argument2);

   //其他IICalculatorSoap方法

}

ICalculator.cs源文件添加到客户端项目中。ICalculator.cs的IICalculator定义是面向接口实现方,而不是面向服务使用方的。因此,它包含有服务器端属性,如WebService和WebMethod。你可以安全的删除这些属性,也可以保留它们,但它们在客户端不起任何作用。将IICalculatorSoap更名为ICalculator。

提示:使用带/serverInterface开关的WSDL.exe命令行工具,是服务提供者导入第三方定义的接口的方法。

第二种客户端可以导入接口定义的方法就是添加对ICalculator Web服务的Web引用。添加引用后,客户端可从代理类中手工提取接口方法。首先,将“添加Web引用向导”指向包含接口定义的网址。这样就产生一个名为ICalculator的代理类,它暴露了原始ICalculator的以及作为异步调用的各种方法(第8章已阐述过)。每种方法的实现都是将调用转发给Web服务。当然,对于一个接口定义,你只需要方法的定义。删除所有的接口方法的内容和其他所有方法,包括构造函数。删除SoapHttpClientProtocol基类和方法上的公共修饰符,然后删除所有的类和方法属性。最后将ICalculator的定义从类换成接口。现在客户端就拥有原始接口定义了。

不管客户端是如何导入接口定义的,它都需要用到实现该接口的Web服务。打开“添加Web引用向导”,使向导指向实现所在的地方。Visual Studio 2005将会为接口实现生成代理类(在此指的是SimpleCalculator和ScientificCalculator)。这些机器生成的代理类是默认的代理类;它们看起来和示例A-2类似,不涉及ICalculator。下面就轮到客户端提供一个ICalculator的多态了,这可通过添加一个接口的派生来做到。

public partial class ScientificCalculator : ICalculator

{}

public partial class SimpleCalculator : ICalculator

{}

由于代理类被标记为局部类,你甚至可以把接口派生放到一个独立的文件中去,并在需要时更新这个代理类文件。

最后,客户端就能编写基于接口的多态Web服务代码了,如示例A-3所示。注意:两种代理类唯一的区别在于,实现ICalculator接口的Web服务的两个URL是不同的。这产生了一个有趣的现象:在Web服务中,从客户端的角度看,服务的位置(也就是URL)就是对象的类型。

还要注意的是:除了SOAP以外,本附录中说明的各种技巧也对其他的Web服务协议有效——比如HTTP-POST。使用WSDL.exe工具,你就能生成使用这些协议的代理类,并向Web服务接口添加派生了。

查看所有评论(0)条】

最近评论



正在载入评论列表...
热点评论