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>

 

posted on 2015-04-07 16:05  chulin0217  阅读(1240)  评论(2编辑  收藏  举报