开发基于接口的.NET Web服务 [转]
象我们在传统的面向组件的编程中所学到的那样,接口和具体实现的分离使得同一种服务可以具有多种实现方法。大家公认的是,在任何应用中重用的基本部分是接口,而不是对象,这种面向组件的编程的核心原理同样也适用于互联网服务的开发。
接口是一组方法的逻辑组合,是客户端应用和互联网服务之间的媒介。由于客户端应用是针对一种抽象的服务描述而不是一个特定的服务实现编写的,用户在转换服务商时就可以只对客户端进行很少的变化,甚至不用对客户端进行任何改变。
互联网服务标准支持接口的概念,但在缺省情况下,.NET中的互联网服务是基于方法的,而不是基于接口的。因此,在目前的情况下,.NET并非“与生俱来”地允许我们开发基于接口的互联网服务。在本篇文章中,我们将通过一些简单的步骤(包括服务器端和客户机端)来使得VS.NET支持基于接口的互联网服务,使读者可以开发和使用基于接口的互联网服务。
假设有一个名称为SimpleCalculator的互联网服务,该服务能够提供加、减、乘、除四种基本的算术运算,还有一个使用这个服务的客户端应用。我们可以在.NET中使用C#来实现SimpleCalculator互联网服务,见下面表1:
| [WebService( Namespace="http://CalculationServices.com", Description = "The SimpleCalculator Web Service provides the four basic arithmetic operations for integers.")] public class SimpleCalculator: WebService { public SimpleCalculator(){} [WebMethod] public int Add(int num1,int num2) { return num1 + num2; } [WebMethod] public int Subtract(int num1,int num2) { return num1 - num2; } [WebMethod] public int Divide(int num1,int num2) { return num1 / num2; } [WebMethod] public int Multiply(int num1,int num2) { return num1 * num2; } } |
表1通过简单地添加[WebMethod]属性将一个类的方法作为互联网服务。注意从WebService中继承是可选的,[WebService]属性也是可选的,但我们在这里用它来提供服务描述和包含名字空间。
我们只要给作为互联网服务的Add()、Subtract()、Divide()和Multiply()这四种方法添加[WebMethod]属性即可,剩下的工作就全由.NET来替我们完成。注意,使WebService作为基本类是可选的,从WebService中进行继承生成类将使用户可以更方便地使用公共的ASP.NET对象,但我们在这里不会用到这些对象。尽管WebService属性是可选的,但我建议用户使用它。
WebService属性可以让我们指定一个包含服务的Web名字空间,用户可以用它来作为一个普通的.NET名字空间来减少名字冲突。如果不指定名字空间,VS.NET将缺少地使用http://tempuri.org/作为名字空间。一个发布的服务通常使用一个URL作为其名字空间,一般情况下就是服务商的公司名字。WebService属性还使我们能够提供一个有关服务的文字描述,这些文字描述将出现在互联网服务用户使用的自动生成的网页中。
编写客户端代码不需要什么特别的技巧。在VS.NET中的用户工程中选择Add Web Reference,并把向导指向包含有互联网服务ASPX文件的站点,这将使VS.NET生成一个被称作SimpleCalculator的包装类,客户端程序会用到这一包装类(见表2)。SimpleCalculator类拥有互联网服务开发商用[WebMethod]标识为公共方法的方法。包装类(有时也被称作“互联网服务代理”类)完全封装了与远程对象之间复杂的交互活动,它也是唯一的与服务的位置相关的实体,它的基本类SoapHttpClientProtocol有一个被称为Url的的属性,Url属性指向这一对象的位置。
表2:
| public class SimpleCalculator : SoapHttpClientProtocol { public SimpleCalculator() { Url ="http://www.CalculationServices.com /SimpleCalculator.asmx"; } [SoapDocumentMethod("http://CalculationServices.com/Add")] public int Add(int num1,int num2) { object[] results = Invoke("Add", new object[]{num1,num2}); return (int)(results[0]); } // 其他方法的包装 } |
专门为表1中的互联网服务生成的SimpleCalculator互联网服务包装类完全封装了与互联网服务之间的交互活动,使得客户端不会受到这些细节问题的影响,它在其公共的Url属性中包含有服务的位置。
客户端的代码在使用SimpleCalculator对象时,把它看作与本地对象是一样的:
| SimpleCalculator calculator; calculator = new SimpleCalculator(); int result = calculator.Add(2,3); Debug.Assert(result == 5); |
很显然,在VS.NET中调用Web方法是非常简单的事。
我们发现了一个新问题:客户端软件不再直接针对提供服务的对象(在这里是SimpleCalculator)进行编程,而是针对这一服务的抽象进行编程。我们希望SimpleCalculator互联网服务通过服务抽象━━接口,具有多态性。
例如,假设客户端应用希望从SimpleCalculator转向另一种被称作ScientificCalculator的计算器服务,ScientificCalculator支持与SimpleCalculator相同的接口,但它的速度更快、价格更低,而且计算更准确。因此,我们希望定义一个通用的计算器接口━━ICalculatorlw接口,并将它作为互联网服务:
| [WebInterface] interface ICalculator { int Add(int num1,int num2); int Subtract(int num1,int num2); int Divide(int num1,int num2); int Multiply(int num1,int num2); } |
假定我们可以完成这一工作(很快我会向你说明如何作的。),我们可以只针对接口定义而不是它的一个特定的具体实现来进行编程。(见表3)
表3
| ICalculator calculator = (ICalculator) new ScientificCalculator(); file://或 ICalculator calculator = (ICalculator) new SimpleCalculator(); // 这部分的客户端应用代码对于任何服务提供商都具有多态性 int result = calculator.Add(2,3); Debug.Assert(result == 5); |
我们希望计算器互联网服务通过定义服务抽象━━接口实现多态性,可以在不修改或尽量少修改客户端代码的情况下改变服务提供商。
在服务提供商之间进行转换时客户端应用代码中唯一需要改变的是决定使用哪个接口实现的部分。我们可以根据不同的集合而不仅是主要客户的逻辑来作出决策,只在二者之间传递接口。我们可以从基于接口的互联网服务中得到的另一个好处是:客户可以发布接口的定义,使不同的服务开发商更方便地实现客户的要求。
现在我们就可以开始使用不支持基于接口的互联网服务的VS.NET来编写服务器和客户机端的代码了。
Web接口的定义和实现
为了实现一个基于接口的互联网服务,首先需要解决互联网服务接口的定义这个问题。为了简单起见,我们在这里假定服务提供商同时负责接口的定义和实现。(客户端应用软件和第三方都可以知道接口定义,任何人都可以实现它,但这还需要其他一些工作。)
创见一个被称作CalculationServices的新的互联网项目。右击该项目,并选择Add Web Service,在Add New Item对话框,输入ICalculator作为接口名字,并点击Open。
![]() |
| 图:开发基于接口的.NET互联网服务1 接口的定义:要定义一个互联网服务的接口,需要添加一个互联网服务条目,并给它命名。点击OK按钮,使VS.NET创建一个互联网服务,并在向导生成的代码中删除所有具体实现互联网服务的代码。 |
VS.NET将创建一个被称作ICalculator的互联网服务。打开ICalculator.asmx.cs文件,并把ICalculator的类型定义由class改为interface,从System.Web.Services.WebService中删除派生类。这样,该接口就仅仅剩下了定义,而没有了实现代码━━删除了构建器、InitializeComponent()和Dispose()方法。最后,删除有注释的HelloWorld()方法。
然后,添加接口的方法━━Add()、Subtract()、Divide()和Multiply()。尽管从理论上可以将[WebMethod]属性用在每个接口方法上,把接口当作是一个互联网服务定义,但由于[WebService]的原因,实际上你无需这么作。这一属性只能用在类中,而且它是密封的,不能对它作任何改变,因此不能给接口指定一个名字空间和描述。为了解决这一难题,我们必须给接口提供一个缓冲━━一个象纯接口定义的抽象类。在ICalculator.asmx.cs文件中添加ICalculatorShim纯抽象类定义:
| [WebService( Name = "ICalculator",Namespace= "http://CalculationServices.com", Description = "This Web Service is only the definition of the interface. You cannot invoke method calls on it.")] abstract class ICalculatorShim : ICalculator { abstract public int Add(int num1,int num2); abstract public int Subtract(int num1,int num2); abstract public int Divide(int num1,int num2); abstract public int Multiply(int num1,int num2); } |
需要注意的是,由于ICalculatorShim是一个类,我们可以使用[WebService]属性来提供一个名字空间和描述。此外,可以将[WebService]属性的Name属性设置为ICalculator,以使服务的描述成为ICalculator而不是ICalculatorShim。
我们感兴趣的只是知道该服务的标志,因此无需知道具体的实现代码。由于我们使用了抽象的类和方法,VS.NET也不会生成ICalculatorShim互联网服务的实现代码,而只生成一个服务说明。
为了确认所有这一切都已经正确地完成,把ICalculator.asmx文件设置为起始页,并运行该工程,自动生成的浏览器页面将显示ICalculator的接口定义。如果试图调用任何一个方法,都会得到一个错误提示,因为我们并没有完成实现该服务所需要的代码。
然后,在互联网服务类上实现ICalculator接口。庥朐?NET中实现其他类完全一样,互联网类应该由接口继承而得到,并实现它的方法。在本例中,需要完成二个类的实现:SimpleCalculator和ScientificCalculator互联网服务类。(见表4)
| [WebService(amespace="http://CalculationServices.com", Description = "The SimpleCalculator web service implements ICalculator. It provides the four basic arithmetic operations for integers.")] public class SimpleCalculator : ICalculator { public SimpleCalculator() {} [WebMethod] public int Add(int num1,int num2) { return num1 + num2; } file://ICalculator的其他方法 } [WebService(Namespace="http://CalculationServices.com", Description = "The ScientificCalculator web service implements ICalculator. It provides the four basic arithmetic operations for integers.")] public class ScientificCalculator : ICalculator { public ScientificCalculator () {} [WebMethod] public int Add(int num1,int num2) { return num1 + num2; } file://ICalculator的其他方法 } |
实现ICalculator与实现其他的.NET接口非常相似,被当作互联网服务的该类继承自接口,并实现接口的方法。注意:必须在接口方法的实现中提供[WebMethod]属性。
再次使用Add Web Service菜单项添加二种互联网服务,添加ICalculator接口的一个继承类(可以删除WebService基础类中的继承类,它不适合基于接口的互联网服务。),添加Add()、Subtract()、Divide()和Multiply()接口类方法的具体实现。必须提供希望将之作为互联网服务的接口方法的[WebMethod]属性,如果类的方法没有[WebMethod]属性,.NET就不会将之作为互联网服务的一部分。
编写客户端代码 客户端软件需要添加一个对接口类型描述的引用和实现它的类,可以用下面二种方法之一添加接口引用,第一种方法是使用WSDL.exe命令行工具。通过使用/server开关,可以让WSDL.exe生成一个符合互联网服务描述的纯抽象类。假设接口描述在http://www.CalculationServices.com/ICalculator.asmx,用下面的格式运行WSDL.exe工具:
| WSDL.exe /server /out: ICalculatorDef.cs http://www.CalculationServices.com/ICalculator.asmx |
然后在客户端工程中添加ICalculatorDef.cs源文件。
即使.NET知道该接口,/server开关生成一个带有抽象方法的纯抽象类:
| public abstract class ICalculator : WebService { [WebMethod] [SoapDocumentMethodAttribute("http://CalculationServices.com/Add"] public abstract int Add(int num1, int num2); // ICalculator方法的其余部分 } |
我们需要的是接口定义。打开ICalculatorDef.cs文件,删除WebService基本类,并把ICalculator的描述由抽象类改为接口,删除ICalculator类和其方法的所有属性以及所有方法中的public和abstract修饰符。经过这样的处理后,我们就得到了最原始的接口定义。
第二种方法是,客户端应用导入接口定义:在ICalculator服务中添加一个Web引用,然后从包装类中解析出接口的方法,将Add Web Reference向导指向含有接口定义的站点,就可以实现这一目的。此操作会生成一个被称为ICalculator的包装类,该类能够利用ICalculator原有的方法和互联网服务包装类的其他方法。用户只需要接口的方法定义即可,因此可以删除掉接口方法的实体和包括构造器在内的其他方法。删除接口方法中的SoapClientProtocol基本类和public修饰符,删除所有方法和类的属性。最后,将ICalculator的定义由类改为接口。这样,客户端应用就有了最原始的接口描述。
第二步,客户端应用必须包含一个对实现接口的互联网服务的Web引用。再次使用 Add Web Reference向导将向导指向这些实现驻留的站点。在本例中,VS.NET生成了这些实现的包装类━━SimpleCalculator和ScientificCalculator。这些机器生成的包装类与与ICalculator无关,为了使ICalculator具有多态性,可以添加一个它的导出类。SimpleCalculator和ScientificCalculator如表5所示:
| public class SimpleCalculator : SoapHttpClientProtocol, ICalculator { public SimpleCalculator() { Url ="http://www.CalculationServices.com /SimpleCalculator.asmx"; } [SoapDocumentMethod("http://CalculationServices.com/Add")] public int Add(int num1,int num2) { object[] results = Invoke("Add", new object[]{num1,num2}); return (int)(results[0]); } file://Other method wrappers } public class ScientificCalculator : SoapHttpClientProtocol, ICalculator { public ScientificCalculator() { Url = "http://www.CalculationServices.com/ ScientificCalculator.asmx"; } [SoapDocumentMethod("http://CalculationServices.com/Add")] public int Add(int num1,int num2) { object[] results = Invoke("Add", new object[]{num1,num2}); return (int)(results[0]); } file://Other method wrappers } |
在使用Add Web Reference向导生成包装类后,将它们修改成基于接口的类。
下面是一个客户端设计模板:ICalculator提供了服务定义。互联网服务的位置是由通过继承得来的SimpleCalculator或ScientificCalculator生成的,包装类知道如何转发对互联网应用的调用,但并不清楚它在服务器端的具体实现,SimpleCalculator和ScientificCalculator之间的所有区别只不过是它们封装的服务地址或者提供商不同而已。
在有了上面的基础之后,我们就可以编写基于接口的、多态性的互联网服务应用。如果仔细观察,我们还会发现一个有趣的现象:站在客户端应用的角度来看,服务的位置━━URL是一个对象。
在未来数年内,互联网服务将成为几乎每个编程人员职业生涯中不可或缺的一部分,然而,与我们在intranet上已经熟悉的面向组件的编程和现有的设计方法学相比,互联网服务的支持工具还是相当不成熟的,目前最大的挑战是如何把二者结合起来。我希望这篇文章使你能够不要因为VS.NET不支持就放弃一些成熟和很好的理念,通过把二者结合起来或者能够对URL与对象相同有一个正确的认识,我们完全可以把一些成熟的理念应用在崭新的领域。

浙公网安备 33010602011771号