本文是终结点与服务寻址系列的第三篇文章。
3.使用ChannelFactory<TChannel>取代服务代理对象
在前面两个实验中,从客户端程序代码可以看到,客户端使用服务代理对象来调用远程服务,那么有没有别的选择呢?答案是肯定的。
在这个实验中,所使用的依然是《[WCF]终结点与服务寻址(二)》中的服务端代码、服务端配置文件以及客户端配置文件。因此这个实验研究的地方是客户端程序的代码。首先给出一个方法的定义:
![ContractedBlock.gif](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
{
ChannelFactory < WCF_Study1.Client.Services.ISystemInfoServiceContract > factory
= new ChannelFactory < WCF_Study1.Client.Services.ISystemInfoServiceContract > ();
WCF_Study1.Client.Services.ISystemInfoServiceContract proxy = factory.CreateChannel();
/*
* 调用上述创建proxy的方法时,会抛出异常,因为没有为ChannelFactory指定一个明确的终结点,
*正确的使用方式应该在ChannelFactory的构造方法中传入一个配置的终结点的名称,或手动构造
*一个终结点对象并传入。
*/
Console.WriteLine( " 调用背景:没有指定使用哪个终结点调用服务。 " );
Console.WriteLine( " 代理类别:使用ChannelFactory直接创建的Channel。 " );
Console.WriteLine( " 调用结果:Remote Server Name:{0} " , proxy.GetServerMachineName());
Console.WriteLine();
}
在这段代码中,一个有趣的地方是,我没有像之前的实验一样,使用WCF_Study1.Client.Services.SystemInfoServiceContractClient这个服务代理,而是改用了一个叫做ChannelFactory<WCF_Study1.Client.Services.ISystemInfoServiceContract>的类型。这个类型从名字就可以知道,就是信道工厂。它是一个泛型类型,那么类型参数中所指定的类型是用于控制什么的呢?答案就是:控制创建一个信道,该信道服务于一个特定的终结点,而这个终结点就是公开类型参数中指定的服务契约。那么到底信道跟终结点之间有什么关系呢?我把这个问题的解答留在往后的信道专题文章中,因为现在我所关注的地方,不是必须要知道这个关系。
上述方法的定义中,另一个亮点就是CreateChannel这个方法的调用。顾名思义,这个方法就是用于构造一个信道对象,而该信道实现了类型参数中所定义的接口。由于在这里,我把类型参数直接定义为服务契约,所以返回的信道完全可以当成服务契约使用,从而调用远程服务。不过当我们调用这个方法时,会出现以下异常:
这个异常的描述信息似曾相似,其实就是因为客户端具有两个终结点的配置,之前已经讲述过,必须为服务代理明确所使用的客户端终结点(尽管现在使用的不是服务代理,但就类似与创建服务代理)。在这里再次引入这个话题是因为这次造成异常出现的位置不是服务代理的创建,而是这个CreateChannel方法的调用。这就说明服务代理的直接构造,与利用CreateChannel方法进行构造有异曲同工之妙。以下是修改后的方法定义:
![ContractedBlock.gif](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
{
ChannelFactory < WCF_Study1.Client.Services.ISystemInfoServiceContract > factory
= new ChannelFactory < WCF_Study1.Client.Services.ISystemInfoServiceContract > ( " BasicHttpBinding_ISystemInfoServiceContract " );
WCF_Study1.Client.Services.ISystemInfoServiceContract proxy = factory.CreateChannel();
Console.WriteLine( " 调用背景:使用基于HTTP协议作为绑定的终结点来调用服务。 " );
Console.WriteLine( " 代理类别:使用ChannelFactory直接创建的Channel。 " );
Console.WriteLine( " 调用的终结点逻辑地址:{0} " , factory.Endpoint.Address.ToString());
Console.WriteLine( " Channel类型:{0} " , proxy.GetType());
// 注意:这里查看代理使用哪个终结点的信息,是由ChannelFactory提供的。
Console.WriteLine( " 调用结果:Remote Server Name:{0} " , proxy.GetServerMachineName());
Console.WriteLine();
}
读者或许已经有这样的一个疑问:是怎样的一种机制确保ChannelFactory<TChannel>中的类型参数所指定的类型必定能够被构造出正确的服务代理(信道)呢?不可能随便传一个NormalClass类型(这就是我后面会做的事)也可以构造出来吧?在通过实验解答这个疑问之前,再来看一个方法的定义:
![ContractedBlock.gif](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
{
ChannelFactory < WCF_Study1.Client.Services.ISystemInfoServiceContractChannel > factory
= new ChannelFactory < WCF_Study1.Client.Services.ISystemInfoServiceContractChannel > ( " BasicHttpBinding_ISystemInfoServiceContract " );
WCF_Study1.Client.Services.ISystemInfoServiceContractChannel proxy = factory.CreateChannel();
Console.WriteLine( " 调用背景:使用基于HTTP协议作为绑定的终结点来调用服务。 " );
Console.WriteLine( " 代理类别:使用ChannelFactory直接创建的Channel。 " );
Console.WriteLine( " 调用的终结点逻辑地址:{0} " , factory.Endpoint.Address.ToString());
Console.WriteLine( " Channel类型:{0} " , proxy.GetType());
// 注意:这里查看代理使用哪个终结点的信息,是由ChannelFactory提供的。
Console.WriteLine( " 调用结果:Remote Server Name:{0} " , proxy.GetServerMachineName());
Console.WriteLine();
}
代码中又出现了一个新类型,那就是WCF_Study1.Client.Services.ISystemInfoServiceContractChannel,这个就是真正的服务代理信道类型(它是一个接口),之前使用的是服务契约类型(也是一个接口),现在使用的是信道类型。我在上面的方法中使用了同样的手段,让ChannelFactory<TChannel>通过CreateChannel方法为我构造一个可供使用的对象(通过它调用远程服务),唯一不一样的就是,这次构造出来的是服务代理信道类型对象。这段代码依然可以正常的工作,同时也加深了疑问。
为了用实验解答刚才的疑问,我建立了三个类型,分别是:
public interface InterfaceWithServiceContractAttr { }
public interface NormalInterface { }
public class NomalClass { }
然后我分别用这三个类型作为ChannelFactory<TChannel>中的类型参数。然后构造信道工厂,再调用CreateChannel方法,得到下面三个异常,
使用NormalClass作为类型参数:
使用NormalInterface作为类型参数:
使用InterfaceWithServiceContractAttr作为类型参数:
从上面三个异常可以总结出来,要使得ChannelFactory<TChannel>成功构造出类型参数中的类型对象,该类型至少是一个:已经存在客户端终结点配置的服务契约类型。而刚刚演示的两个类型,它们都是继承自这样的一个服务契约类型的,观看以下代码:
![ContractedBlock.gif](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
[System.CodeDom.Compiler.GeneratedCodeAttribute( " System.ServiceModel " , " 4.0.0.0 " )]
[System.ServiceModel.ServiceContractAttribute(Namespace = " http://www.dreamofflea.net " , ConfigurationName = " Services.ISystemInfoServiceContract " )]
public interface ISystemInfoServiceContract {
[System.ServiceModel.OperationContractAttribute(Action = " http://www.dreamofflea.net/ISystemInfoServiceContract/GetServerMachineName " , ReplyAction = " http://www.dreamofflea.net/ISystemInfoServiceContract/GetServerMachineNameRespons " +
" e " )]
string GetServerMachineName();
}
[System.CodeDom.Compiler.GeneratedCodeAttribute( " System.ServiceModel " , " 4.0.0.0 " )]
public interface ISystemInfoServiceContractChannel : WCF_Study1.Client.Services.ISystemInfoServiceContract, System.ServiceModel.IClientChannel {
}
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute( " System.ServiceModel " , " 4.0.0.0 " )]
public partial class SystemInfoServiceContractClient : System.ServiceModel.ClientBase < WCF_Study1.Client.Services.ISystemInfoServiceContract > , WCF_Study1.Client.Services.ISystemInfoServiceContract {
那有没有其他途径,尽管没有在客户端中配置终结点,也能使用ChannelFactory<TChannel>的CreateChannel方法来创建代理呢?经过网友留言提醒后,我补充了这样的方法(感谢该网友)。
![ContractedBlock.gif](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
{
Console.WriteLine( " 使用被标记为服务契约的接口类型作为类型参数构造ChannelFactory,
EndpointAddress ea = new EndpointAddress( @" http://localhost:8000/SystemInfoService " );
ChannelFactory < ISystemInfoServiceContract > factory = new ChannelFactory < ISystemInfoServiceContract > ( new BasicHttpBinding(),ea);
ISystemInfoServiceContract proxy = factory.CreateChannel();
Console.WriteLine( " 调用背景:直接指定服务端终结点地址来使用ChannelFactory<TChannel>创建信道对象,并进行服务调用。 " );
Console.WriteLine( " 代理类别:使用ChannelFactory直接创建的Channel。 " );
Console.WriteLine( " 调用的终结点逻辑地址:{0} " , factory.Endpoint.Address.ToString());
Console.WriteLine( " Channel类型:{0} " , proxy.GetType());
// 注意:这里查看代理使用哪个终结点的信息,是由ChannelFactory提供的。
Console.WriteLine( " 调用结果:Remote Server Name:{0} " , proxy.GetServerMachineName());
Console.WriteLine();
}
在这个方法的代码中,我没有在任何地方指定客户端终结点的配置,而是在构造ChannelFactory<TChannel>对象的时候就明确的以参数方式指定了服务端终结点的访问地址,还有使用的绑定类型。同时在类型参数中使用了服务契约类型(其实就是通过编程而不是配置的方式指定终结点三要素——服务契约、绑定、地址)。通过这样的方式,可在毫不配置的情况下,进行远程服务调用。其实通过配置可以做到的事情,都可以通过编程来实现,在前面我提过本系列文章只着重介绍配置方式,因为比较简洁而且灵活,