调用服务最简单的方法就是,直接在VS里面添加服务引用,输入服务的地址即可,无论是普通Web服务,还是WCF服务均可。VS会根据获取到的元数据,自动生成客户端代码。
如果服务的调用量很大,应用广泛,可以放在IIS上作为一种Web资源使用。但WCF不限于此,它可以在一个进程中运行,或者可以放到Windows服务进程上运行,实则是一种Windows平台的万能通信技术。
为了装逼,今天老周将演示如何手动调用WCF,仅通过Channel(通道)就可以调用。其实,在某些时候,手动也有手动的好处,手动调用的话,代码量不多,也比较灵活。
当然,这个演示仅供参考。
为了顺利完成装逼演示,首先得弄个服务示例,以往常用的是做一个执行加减乘除的服务来测试,今天既然要装逼,就装得有创意一点,就来一个计算N次方的吧,比如2的3次方为8。
不知各位是否记得建立WCF的步骤,这么小的程序,就不用IIS来运行了,直接用一个控制台应用程序可以了,简单大方美观有层次。
首先要声明服务协定。
[ServiceContract(Name = "pow_service", Namespace = "http://my")] interface IService { [OperationContract(Name = "pow", Action = "PowAction", ReplyAction = "PowReply")] double Pow(double x, double y); }
协定只是个接口,在服务器端要实现它,但在客户端不需要知道实现这接口的代码,只要在客户端也定义一个这样的接口,就可以了。当然,如果你不想重复定义协定,你可以把这个接口定义到一个共享的类库中,最好用可移植的库,这样保证平台万能性。
在服务器和客户端中分别定义协定接口有一个好处就是可以两边不必保持一致,接口名、接口的方法都可以不同,参数名也可以不同,只要参数 的顺序、类型、数量,以及返回值的类型相同就可以。
重点是附加在接口上的ServiceContractAttribute,和附加到方法上的OperationContractAttribute特性。服务器和客户端的接口名字可以不同,只要附加的这些特性的属性值相同即可。
所以,在Win10 App客户端,我可以自己声明这样的接口:
[ServiceContract(Name = "pow_service", Namespace = "http://my")] interface IPowService { [OperationContract(Name = "pow", Action = "PowAction", ReplyAction = "PowReply")] Task<double> PowAsync(double x, double y); }
你不妨看一下,接口名字和接口成员名字不同,但标注的协定特性是相同的。所谓的协定者,就是服务器和客户端之间必须有一点“默契”,协定接口就是一种规范,不然,客户端不知道服务有哪些操作方法,就无法调用了。
协定完成后,在服务器端要实现协定接口,进行具体的操作。
class PowService : IService { public double Pow(double x, double y) { double res = Math.Pow(x, y); return res; } }
承载在进程上的WCF比较好处理,不用太复杂的配置,几行代码就可以启动服务主机。
using (ServiceHost host = new ServiceHost(typeof(PowService))) { NetTcpBinding binding = new NetTcpBinding(SecurityMode.None); // 直接添加终结点 host.AddServiceEndpoint(typeof(IService), binding, "net.tcp://localhost:1700/pow"); host.Opened += (h, ea) => Console.WriteLine("服务已打开。"); // 打开服务 try { host.Open(); } catch (Exception ex) { Console.WriteLine(ex.Message); } Console.ReadKey(); }
其实很Easy,ServiceHost负责运行服务,可解释为服务主机。因为是手动访问服务,基址可以省略。然后你直接将服务协定相应地作为终结点添加到服务主机中即可。在添加终结点时,需要指明协定的Type,一个Binding,当然还要包含地址。
这里我选用TCP协议来通信,就用NetTcpBinding类,NET TCP的地址要以“net.tcp:”开头。
之后,直接Open就可以运行服务了。
注意这个服务只能手动调用,不能用服务引用生成代码,因为没有公开元数据,生成代码时会找不到WSDL。
=========================================================
然后在Windows App客户端中就可以直接调用了。
// 终结点地址 EndpointAddress ep = new EndpointAddress("net.tcp://localhost:1700/pow"); // TCP绑定 NetTcpBinding binding = new NetTcpBinding(SecurityMode.None); // 创建通道 ChannelFactory<WCFSVContracts.IPowService> factory = new ChannelFactory<WCFSVContracts.IPowService>(binding, ep); WCFSVContracts.IPowService channel = factory.CreateChannel(); // 调用服务 double x = double.Parse(txt1.Text); double y = double.Parse(txt2.Text); double r = await channel.PowAsync(x, y); tbres.Text = $"计算结果:{r:G}";
WCFSVContracts.IPowService就是在客户端上重新定义的服务协定,但协定的特性要与服务相同,接口名字可以不同。这个上面贴过代码,下面我再贴一段完整的。
using System; using System.ServiceModel; using System.Threading.Tasks; namespace WCFSVContracts { [ServiceContract(Name = "pow_service", Namespace = "http://my")] interface IPowService { [OperationContract(Name = "pow", Action = "PowAction", ReplyAction = "PowReply")] Task<double> PowAsync(double x, double y); } }
与服务器上的定义有些不同的是,在客户端中,我将操作方法声明为支持异步等待,即返回Task<TResult>,这个是允许的,大家不必怀疑。
你会看到,在客户端上调用也是挺简单的,首先用一个EndpointAddress表示终结点的地址,这个地址必须和服务器上添加的终结点的地址一致,否则找不到服务。
然后实例化一个NetTcpBinding对象,Binding的类型与属性值必须与服务器上的Binding一致。服务器上的NetTcpBinding的安全模式设置为None,客户端上的也要设置为None。因为也不是什么见不得人的数据,就禁用安全模式。
最后,用一个ChannelFactory<TChannel>就可以完成服务调用。TChannel的类型就是协定接口,随着当调用CreateChannel方法时,它所创建的通道就以协定接口为基础返回,它返回的实际类型是一个动态类型,但它可以以协定接口为基础来调用。
运行结果如下图。