WCF示例及Per-Call和Per-Session配置
这篇文章是几年前自己学习WCF时的读书笔记,整理文档的时候发现了顺便放到博客里面。
示例是网上流传很广的一个订票系统的示例,由于在学习过程中出现了一些错误,在此特地把为什么出错的原因记录下来。
新建一个WCF服务应用程序
在IService1.cs定义服务契约
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.ServiceModel.Web; using System.Text; namespace WcfDemo { // 注意: 使用“重构”菜单上的“重命名”命令,可以同时更改代码和配置文件中的接口名“IService1”。 [ServiceContract] public interface IService1 { [OperationContract] void AddTicket(int count); [OperationContract] int BuyTickets(int num); [OperationContract] int GetRemainingNum(); // TODO: 在此添加您的服务操作 } // 使用下面示例中说明的数据约定将复合类型添加到服务操作。 [DataContract] public class Ticket { bool boolCount = true; int howMany = 10; [DataMember] /* 判断是否还有票 */ public bool BoolCalue { get { return boolCount; } set { if (howMany > 0) { boolCount = false; } else { boolCount = true; } } } [DataMember] public int HowMany { get { return howMany; } set { howMany = value; } } } }
在Service1.svc.cs中实现契约服务
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.ServiceModel.Web; using System.Text; namespace WcfDemo { // 注意: 使用“重构”菜单上的“重命名”命令,可以同时更改代码、svc 和配置文件中的类名“Service1”。 //[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)] public class Service1 : IService1 { Ticket T = new Ticket(); public void AddTicket(int count) { T.HowMany = T.HowMany + count; } public int GetRemainingNum() { return T.HowMany; } public int BuyTickets(int Num) { if (T.BoolCalue) { T.HowMany = T.HowMany - Num; return 1; } else { return 0; } } } }
现在添加宿主程序用于监测服务,添加WinForm项目加入解决方案
WinForm宿主程序界面如下图:
添加WCF服务应用程序生成的dll文件
配置如下图,在解决方案的属性里面需要把启动项目设置成宿主程序,否则每次F5启动都会失败
源代码
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.ServiceModel; namespace WindowsFormsApplication1 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } ServiceHost host = null; private void button1_Click(object sender, EventArgs e) { host = new ServiceHost(typeof(WcfDemo.Service1)); host.Open(); this.label1.Text = "服务已启动"; } private void button2_Click(object sender, EventArgs e) { if (host.State != CommunicationState.Closed) { host.Close(); } this.label1.Text = "服务已关闭"; } } }
接下来配置宿主程序的app.config
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <services><!--添加服务--> <service name="WcfDemo.Service1" behaviorConfiguration="CalculatorServiceBehavior"> <!--name 必须与代码中的host实例初始化的服务一样 behaviorConfiguration 行为配置 --> <host> <baseAddresses> <!--添加调用服务地址--> <add baseAddress="http://localhost:8000/"/> </baseAddresses> </host> <!--添加契约接口 contract="WcfDemo.IService1" WcfDemo.IService1为契约接口 binding="wsHttpBinding" wsHttpBinding为通过Http调用.如果下面的节点不存在,那么默认的绑定方式是basicHttpBinding。--> <endpoint address="" binding="wsHttpBinding" contract="WcfDemo.IService1"></endpoint> </service> </services> <!--定义CalculatorServiceBehavior的行为--> <behaviors> <serviceBehaviors> <behavior name="CalculatorServiceBehavior"> <serviceMetadata httpGetEnabled="true"/> <serviceDebug includeExceptionDetailInFaults="false"/> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel> </configuration>
程序运行结果:
服务启动后可以通过app.config中baseAddress节点中的baseAddress地址查看WCF服务
到此为止服务以及宿主程序都已经创建好了,下面该创建测试客户机了。
新建个WinForm程序作为我们的测试客户机
界面如下:
购买车票:调用WCF服务的BuyTickets()方法
查询车票:调用WCF服务的GetRemainingNum()方法
Label用于显示调用结果
为项目添加服务引用,地址输入宿主程序app.config中baseAddress地址
后台代码为
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; namespace WCFClient { public partial class Form1 : Form { ServiceReference2.Service1Client TClient = new ServiceReference2.Service1Client(); public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { int i = TClient.BuyTickets(2); if (i == 1) { this.label1.Text = "购买成功"; } this.label1.Text += "剩余车票还有:" + TClient.GetRemainingNum().ToString(); } private void button2_Click(object sender, EventArgs e) { this.label1.Text = ""; this.label1.Text = TClient.GetRemainingNum().ToString(); } } }
点击购买车票时的结果如下,和网上的示例得到的结果不一样。
使用debug调试发现,每次调用一个方法,Ticket都要重新生成,查询资料发现,这和服务实例的生命周期有关。在宿主程序的app.config配置里面,我少写了
<endpoint address="" binding="wsHttpBinding" contract="WcfDemo.IService1"></endpoint>
导致默认的绑定方式是basicHttpBinding,并且服务实例的生命周期是Per-Call的,也即每次调用服务方法,服务实例都要重新生成。在宿主程序里面把app.config修改,加上
<endpoint address="" binding="wsHttpBinding" contract="WcfDemo.IService1"></endpoint>
重新生成服务端,然后运行客户端,报错如下
这是由于宿主程序的binding方式改变了,但是客户端的代理类的binding方式没有做相应的修改导致的,重新把代理类生成一次,如前面的图所示我重新生成了代理类ServiceReference2。
再次运行客户端程序然后打击购买车票,得到了预期的结果还剩8张车票。使用wsHttpBinding的时候,默认的服务实例生命周期是Per-Session,这样在一个会话里面调用服务方法,服务实例不会每次都重新生成。
客户端app.config如下
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <bindings> <basicHttpBinding> <binding name="BasicHttpBinding_IService1" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00" allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard" maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536" messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered" useDefaultWebProxy="true"> <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384" maxBytesPerRead="4096" maxNameTableCharCount="16384" /> <security mode="None"> <transport clientCredentialType="None" proxyCredentialType="None" realm="" /> <message clientCredentialType="UserName" algorithmSuite="Default" /> </security> </binding> </basicHttpBinding> <wsHttpBinding> <binding name="WSHttpBinding_IService1" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00" bypassProxyOnLocal="false" transactionFlow="false" hostNameComparisonMode="StrongWildcard" maxBufferPoolSize="524288" maxReceivedMessageSize="65536" messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true" allowCookies="false"> <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384" maxBytesPerRead="4096" maxNameTableCharCount="16384" /> <reliableSession ordered="true" inactivityTimeout="00:10:00" enabled="false" /> <security mode="Message"> <transport clientCredentialType="Windows" proxyCredentialType="None" realm="" /> <message clientCredentialType="Windows" negotiateServiceCredential="true" algorithmSuite="Default" /> </security> </binding> </wsHttpBinding> </bindings> <client> <endpoint address="http://localhost:8000/" binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IService1" contract="ServiceReference1.IService1" name="BasicHttpBinding_IService1" /> <endpoint address="http://localhost:8000/" binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IService1" contract="ServiceReference2.IService1" name="WSHttpBinding_IService1"> <identity> <userPrincipalName value="Administrator@wmq.kxu.com" /> </identity> </endpoint> </client> </system.serviceModel> </configuration>