WCF4.0进阶系列—第十一章 编写代码控制配置和通信(下)

前言

WCF客户端通过代理对象可连接至服务。如果服务管理员禁用了服务元数据公布或者出于性能原因,那么在客户端可以通过代码创建ChannelFactory对象连接至服务并与服务通讯;甚至还可以通过扩展ClientBase抽样类与服务交互。使用上述两种方式时有一个前提那就是服务开发人员可以提供包含服务合约的组件。如果该前提也不存在,只要你拥有描述服务接受的SOAP消息和服务发送的响应消息的文档时,那么你也可以使用WCF客户端访问该服务;你可以直接通过通道向服务发送消息。

通过编程方式连接到一个服务

当一个客户端程序运行并连接到服务时,客户端的WCF运行时创建一个与服务端WCF运行时相似的基本架构。如果你通过一个代理对象连接到服务,在这背后实际是
1. 客户端的WCF运行时使用配置文件中指定的绑定元素创建一个绑定对象、使用端点定义创建一个端点。
2. 客户端WCF运行时然后使用这些对象创建一个ChannelFactory对象。
3. 客户端的WCF运行时使用该对象实例化通道堆栈并连接到端点指定的URI。
4. 当客户端程序通过代理对象调用服务的操作时,服务端的WCF运行时通过通道堆栈分发这些请求,然后传输请求至服务。
5. 当响应消息离开服务后,响应消息经过通道堆栈回传至客户端的WCF运行时、
6. 然后代理对象传递这些响应消息至客户端程序。
通过svcutil实用工具或者添加服务引用向导,可以为服务创建一个客户端代理类。由于安全因素,管理员可以选择禁用WCF服务对外公布服务的元数据。但是,WCF服务开发人员可以分发包含服务合约的组件;你可以使用这些组件动态地创建通道堆栈。这种实现方式还有性能方面的优势。比如,你可能已经发现使用svcutil实用工具或者添加服务向导生成的代理类没经过必要的优化;自动生成的代理类比较冗长,因为它们不能假设实现服务所使用的技术,而且这种冗长带来了影响性能的代码。如果你知道服务是使用WCF生成的,那么你可以省掉创建代理对象,而直接从包含服务合约的组件中为客户端构造一个ChannelFactory对象。这就是下面练习我们要做的事情。

使用ChannelFactory对象连接至ProductsService服务
1. 在Visual Studio中,关闭ShoppingCartService方案,然后打开位于*\Solutions\WCF\Step.by.Step\Chapter11\ProductsService文件夹下的ProductsService解决方案
2. 在解决方案中,使用下列步骤为ProductsServiceLibrary项目下的IProductService.cs文件添加一个链接
  • 在ProductsClient项目上点击右键,指向添加,然后点击现有的条目
  • 在添加现有的条目对话框中,浏览ProductsService\ProductsServiceLibrary文件夹,并选择IProductsService.cs文件
  • 在添加按钮上点击下拉箭头,然后点击"添加为链接"
该特性允许你从多个项目文件中引用相同的源代码文件,而不是创建一个副本;这样可以避免版本问题。你可以在任何引用源文件的项目中编辑该源文件。
IProductService.cs文件包含ProductsService服务的服务合约定义。如果你不希望对服务合约共享源代码,你可以编辑该文件为单个的组件然后分发该组件至构建客户端程序的开发人员。
3. 在解决方案浏览器窗口中,打开ProductsClient项目下的Program.cs文件。该文件的Main方法中包含了客户端程序的基本框架,但是当前的代码不含有连接至服务的功能。添加下面的引用至该文件
Using System.Servicemodel.Security;
4. 在main方法中的try片段中,添加下面的声明
WS2007HttpBinding httpBinding = new WS2007HttpBinding(SecurityMode.Message);
WSHttpSecurity httpSec = httpBinding.Security;
httpSec.Message.AlgorithmSuite = SecurityAlgorithmSuite.Basic128;
httpSec.Message.ClientCredentialType = MessageCredentialType.Windows;
WS2007HttpBinding类实现标准的WS2007HttpBinding绑定。ProductsServiceHost对外公布HTTP端点,其URI为http://localhost:8010/ProductsService/Service.svc。如果你检查ProductsServiceHost的app.config文件,你将看到当前服务的绑定配置使用消息安全,并配置了 128位的消息加密和Windows集成验证。上述代码的设置客户端程序使用的端点对应的WS2007HttpBinding绑定的安全属性。
5. 添加下面声明至main方法的try片段中
EndpointAddress address = new EndpointAddress("http://localhost:8010/ProductsService/Service.svc");
EndpointAddress对象封装了客户端程序用以和服务通讯的地址。
6. 添加下面声明至main方法的try片段中
Products.IProductsServiceV2 channel =ChannelFactory<Products.IProductsServiceV2>.CreateChannel(httpBinding, address);
通过访问ChannelFactory工厂类的静态方法CreateChannel创建一个通道。该方法的第一个参数指定通道的绑定,第二个参数指定通道将连接的地址。该方法的返回所创建通道的引用。通道有一个服务合约对应的类型,并且该类型决定了该通道暴露给客户端程序的方法。在本例中,通道被赋值到一个可变的类型Products.IProductsServiceV2。是否还记得IProductsServiceV2是一个接口,该接口在ProductsServiceContract.cs实现。你可以基于任何标注为ServiceContract特性的接口创建通道。
7. 现在你可以通过通道调用服务的方法。在try片段中,添加下面的代码
Console.WriteLine("Test 1: List all bicycle frames");
List<string> productNumbers = channel.ListMatchingProducts("Frame");
foreach (string productNumber in productNumbers)
{
Console.WriteLine("Number: {0}", productNumber);
}
上面的代码与第六章调用服务的代码有稍微的不同。在上面代码中,调用服务方法的返回值是List<String>对象而不是第六章使用代理对象返回的字符数组。
8. 关闭与服务之间的连接,然后设置channel对象为null以释放与之相关的资源。
channel = null;
9. 完整的Main方法的代码如下
static void Main(string[] args)
{
    Console.WriteLine("Please ENTER when the service is running.");
    Console.ReadLine();

    try
    {
        WS2007HttpBinding ws2007httpBinding = new WS2007HttpBinding(SecurityMode.Message);
        ws2007httpBinding.Security.Mode = SecurityMode.Message;
        ws2007httpBinding.Security.Message.ClientCredentialType = MessageCredentialType.Windows;

        EndpointAddress address = new EndpointAddress("http://localhost:8010/ProductsService/Service.svc");

        Products.IProductsServiceV2 channel = ChannelFactory<Products.IProductsServiceV2>.CreateChannel(ws2007httpBinding, address);

        Console.WriteLine("Test 1: List all bycycle frames");
        List<string> productNumebrs = channel.ListMatchedProducts("Frame");
        foreach (string productNumber in productNumebrs)
            Console.WriteLine("Number : {0}", productNumber);
        Console.WriteLine();

        channel = null;
    }
   
    catch (Exception ex)
    {
        Console.WriteLine(ex.InnerException.Message);
    }


    Console.WriteLine("Please ENTER to finish.");
    Console.ReadLine();
}
10. 生成项目并退出visual studio
在你运行客户端程序之前,你需更改你计算机的一个安全配置:你需要添加你当前的用户账户到WarehouseStaff组。这是因为ProductsService服务只允许该组的成员访问ListMatchingProducts操作。

配置安全并测试客户端程序
1. 打开Windows开始菜单,然后在计算机点击右键然后选择管理。
2. 在计算机管理控制台中,在系统工具节点下,展开本地用户和组节点,然后点击用户文件夹
3. 在右边面板,在你当前用户的用户名上点击右键,然后点击属性
4. 在属性对话框中,点击成员标签,然后点击添加
5. 在选择组对话框中,输入WarehouseStaff,然后点击确认按钮
6. 在属性对话框上,点击确认按钮
7. 关闭计算机管理控制台
8. 注销Windows并再次登陆系统
9. 启动Visual Studio,再次打开ProductsService解决方案
10. 在非调适模式下启动解决方案。在产品服务宿主程序窗口中,点击启动按钮。然后在客户端控制台窗口中,按ENTER键。客户端程序连接到服务,请求自行车车架列表,然后显示如下图的结果
11. 在客户端控制台窗口中按ENTER键关闭客户端程序。在产品宿主程序窗口中,点击停止按钮,然后关闭窗口。
请注意产品服务宿主窗口将停顿一定的时间才响应停止按钮。这是因为宿主程序必须等待以确保在关闭连接并释放各种网络资源前客户端程序已经关闭连接。客户端程序在channel对象被设置为null后一段时间后,.NET Framework的垃圾回收器释放channel对象所使用的资源后,客户端程序才断开连接。

使用ClientBase抽象类

在先前的章节中,你使用代理类的ClientCredentials属性以指定向服务发送的凭据。如果你需要提供凭据不是你当前的Windows身份,那么上一个练习中的实现方式不能满足需求,此时,你可以定义一个类,该类扩展System.ServiceModel.ClientBase抽象类,然后使用该类连接至服务。ClientBase类通过一系列的构造器与客户端的ChannelFactory基础架构协作。该类还需要实现定义服务合约的接口。你可使用基类的Channel属性分发方法调用通过通道连接至实现服务接口的对于方法上。下面的代码显示了一个具体的例子:
class ProductsServiceProxy : ClientBase<Products.IProductsServiceV2>,Products.IProductsServiceV2
{
public ProductsServiceProxy(Binding binding, EndpointAddress address) :
base(binding, address)
{
}
public List<string> ListMatchingProducts(string match)
{
return base.Channel.ListMatchingProducts(match);
}
public Products.ProductData GetProduct(string productNumber)
{
return base.Channel.GetProduct(productNumber);
}
public int CurrentStockLevel(string productNumber)
{
return base.Channel.CurrentStockLevel(productNumber);
}
public bool ChangeStockLevel(string productNumber, short newStockLevel,string shelf, int bin)
{
return base.Channel.ChangeStockLevel(productNumber, newStockLevel, shelf,bin);
}
}
你可以实例化该类,然后和使用代理对象一样调用它的方法。ClientBase类提供ClientCredentials属性,你可以为该属性指定一个特定的凭据,并使用下面的代码传输凭据至服务:
ProductsServiceProxy channel = new ProductsServiceProxy(httpBinding, address);
channel .ClientCredentials.Windows.ClientCredential.UserName = "Fred";
channel .ClientCredentials.Windows.ClientCredential.Password = "Pa$$w0rd";
channel .ClientCredentials.Windows.ClientCredential.Domain = "Your PC";
你可以检查使用svcutil实用工具生成的代码,你可以看到使用svcutil工具生成的代码也是按照上述方式传输凭据至服务。
ClientBase类还有一个优点;他提供了Close方法以显示地终止通讯通道并在客户端立即释放相应的资源,而不用等待.NET Framework垃圾回收器执行这些任务。

通过编程方式向服务发送消息

WCF的一个主要目的是提供一个互操作的平台。你可以构建WCF客户端程序与其他的非WCF技术,比如Java实现的服务进行通讯。在这种情况下,如果服务宿主程序的管理员禁用了对外公布服务元数据,服务开发人员也不可能为你提供一个包含服务合约的C#代码或者.NET Framework组件。然而,如果你有描述服务可以接受的SOAP消息和服务发送的响应消息的文档,那么你也可以使用WCF客户端访问该服务;你可以直接通过通道向服务发送消息。通道是非常底层但相当灵活的实现方式;这种方式可以使你更进一步的了解客户端的WCF运行时是如何将对通道的方法调用转化为SOAP消息。这就是下面练习要做的事情。

在客户端中发送消息和处理响应消息
1. 在Visual Studio中,关闭ProductsService解决方案。然后打开位于*\Solutions\WCF\Step.by.Step\Chapter11\SimpleProductsService文件夹下的SimpleProductsService解决方案。
2. 打开ProductsServiceLibrary项目下的ISimpleProductsService.cs文件。然后找到ISimpleProductsService接口,该接口应该如下所示:
[ServiceContract(Namespace="http://adventure-works.com/2011/07/19",
Name="SimpleProductsService")]
public interface ISimpleProductsService
{
[OperationContract(Name = "ListProducts")]
List<string> ListProducts();
}
服务合约仅仅定义了一个操作:ListProducts。请注意该服务的命名空间和名字。WCF使用服务合约命名空间和名字结合操作的名字以定义一个对外公布的SOAP消息,或者活动。在本例中,服务将接受和处理的使用后面活动SOAP消息:http://adventure-works.com/2011/05/20/ProductsService/ListProducts。此外,注意该方法的返回值为List<string>,因此服务将返回一个包含序列化的字符串列表的SOAP消息。
3. 打开ProductsClient项目下的programm.cs文件。Main方法创建一个默认的BasicHttpBinding对象和一个EndpointAddress对象。端点的URI为http://localhost:8010/SimpleProductsService/Service.svc。在该文件的头部,添加下面的using语句
Using System.ServierModel.Channles;
4. 在try片段中,添加下面的语句
EndpointAddress address = new EndpointAddress("http://localhost:8010/SimpleProductsService/Service.svc");
IChannelFactory<IRequestChannel> factory =httpBinding.BuildChannelFactory<IRequestChannel>();
factory.Open();
第一行语句创建一个客户端的ChannelFactory对象,客户端使用该对象创建一个可以发送和接受消息的通道,这个通道使用指定的绑定。Open方法实例化通道工厂,然后该通道工厂创建通道堆栈。
一个通道实现其指定支持消息模式的接口。一个通道可以是一个输入通道或者一个输出通道;输入和输出通道构成双工通道。输出通道的一个特定形式为请求通道,对应地输入通道为响应通道。这些接口一起都被当作各种通道具体表现形式。这些表现形式是否可当作传输通道依赖于服务因素。服务因素包括传输通道的类型以及其属性的值。如果一个TCP传输通道使用缓存传输模式,那么该TCP通道不能作为一个响应通道;在这种情况下,它只能作为双向双工通道。而HTTP协议默认使用发送/接受模式,HTTP传输通道在客户端形成了请求通道的表现形式,在服务端形成了相应通道表现形式。
下一步就是创建一个客户端通道堆栈,该堆栈使用通道工厂并连接至服务侦听的地址。
5. 添加下面的语句创建通道
IRequestChannel channel = factory.CreateChannel(address);
channel.Open();
6. 现在你可以通过通道堆栈发送消息和接受响应。你通过Message类的静态方法CreateMessage方法创建一条消息。当你创建消息时,你必须制定小心的版本以及请求动作。
Message request = Message.CreateMessage(MessageVersion.Soap11,"http://adventure-works.com/2010/06/29/SimpleProductsService/ListProducts");
SOAP消息传输规范在其发布后经过了多次变化;WCF中不同的绑定支持不同各种版本规范。BasicHttpBinding绑定支持SOAP1.1消息传输规范。CreateMessage方法的第一个参数MessageVersion.Soap11表明该方法将根据Soap1.1规范格式化消息。如果你使用WS2007HttpBinding绑定,那么你将使用Soap1.2规范格式化消息。CreateMessage的第二个参数由服务的命名空间,服务的名字和服务对应的操作组成。
CreateMessage方法是一个重载方法。你还可以使用其他的重载方法制定消息body的数据,如果服务的操作需要参数或者服务配置了生成SOAP错误消息。
使用请求/响应模式时,真正发送消息的方法是通道的Request方法,在下一步中你将使用该方法发送消息。
7. 添加下面的语句发送消息
Message reply = channel.Request(request);
Console.WriteLine(reply);
Request方法将阻塞当前线程,直到接收到服务的响应消息。响应消息作为Request方法的返回值回传至客户端。当应用程序接收到响应消息后,客户端在控制台中直接显示消息的内容—并不作任何转化。
接收到响应消息后,你可以继续向服务发送其他的请求,但是在我们的练习中,客户端将从服务端开连接。
8. 添加下面的语句
request.Close();
reply.Close();
channel.Close();
factory.Close();
消息、通道和通道工厂都会使用资源,因此当你结束使用后,需要关闭它们以释放其使用的资源。
9. 生成解决方案并在为调适模式下运行方案。在产品服务寄宿窗口,点击开始按钮启动服务。在客户端程序控制台窗口中按ENTER键,客户端程序将向服务发送ListMatchingProduct请求,服务的响应消息将包含一个产品列表。然后客户端控制台将显示包含该产品列表的SOAP消息:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header />
<s:Body>
<ListProductsResponse xmlns="http://adventure-works.com/2010/06/29">
<ListProductsResult
xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays"
xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<a:string>AR-5381</a:string>
<a:string>BA-8327</a:string>
...
<a:string>VE-C304-S</a:string>
<a:string>WB-H098</a:string>
</ListProductsResult>
</ListProductsResponse>
</s:Body>
</s:Envelope>
10. 在客户端控制台窗口中按ENTER键关闭客户端程序。在产品宿主程序窗口中,点击停止按钮,然后关闭窗口
posted @ 2011-08-01 12:33  On the road....  阅读(1934)  评论(1编辑  收藏  举报