WCF学习笔记(五)契约双工通信、多契约绑定及对数据库增删改查等综合实例

来源:互联网 发布:免费手机扫描软件 编辑:程序博客网 时间:2024/06/04 18:20

1.服务层代码

1.1接口

namespace Services
{
    [ServiceContract(Name = "GetProducts", Namespace = "
http://www.yoyo.com")]
    public interface IGetProducts
    {
        //Action,ReplyAction用于维持接口层级之间的关系,主要作用体现在有接口继承的情况下
        [OperationContract(Name = "GetProduct", Action = "
http://www.yoyo.com/IGetProducts/GetProduct", ReplyAction = "http://www.yoyo.com/IGetProducts/GetProductResponse")]
        Products GetProduct(int products);
    }

    /// <summary>
    /// 回调契约:CallbackContract指定回调契约;使用wsDualHttpBinding通信协议
    /// </summary>
    [ServiceContract(Name = "AddProducts", Namespace = "
http://www.yoyoyo.com", CallbackContract = typeof(IAddProductsCallback))]
    public interface IAddProducts
    {
        //, IsOneWay = true:是一个异步调用标记,不标记它就会形成同步阻塞
        //此操作将死锁,因为在当前邮件完成处理以前无法收到答复。
        //这个异常是在AddProduct和GetResult都没有标记IsOneWay = true时出现的
        //如果要允许无序的邮件处理,则在 ServiceBehaviorAttribute
        //上指定可重输入的或多个 ConcurrencyMode。
        [OperationContract(Name = "AddProduct")]
        void AddProduct(Products products);
    }

    public interface IAddProductsCallback
    {
        //因为有IsOneWay标记,此时就不能有返回值,即不能有输出参数
        //(IsOneWay = true)
        [OperationContract]
        void GetResult(string msg);
    }

    /// <summary>
    /// 回调契约:CallbackContract指定回调契约;使用netTcpBinding通信协议
    /// </summary>
    [ServiceContract(Name = "DelProducts", Namespace = "
http://www.yoyoyoyo.com", CallbackContract = typeof(IDelProductsCallback))]
    public interface IDelProducts
    {
        /*注意:此接口和对应的回调接口中的方法名可以相同,序列化到
         *SOAP包中就会报如下错误:
         *
         * 元数据包含无法解析的引用:“http://localhost:8110/”。
         * 没有终结点在侦听可以接受消息的
http://localhost:8110/。这通常是由于不正确的地址或者
         * SOAP 操作导致的。如果存在此情况,请参阅 InnerException 以了解详细信息。远程服务器返
         * 回错误: (404) 未找到。
         *
         * 因此,需要指定别名
         */

        [OperationContract(Name = "DelProduct", IsOneWay = true)]
        void DelProduct(int productsID);
    }

    public interface IDelProductsCallback
    {
        [OperationContract(Name = "DelProductsCallback", IsOneWay = true)]
        void DelProduct(string msg);
    }

    /// <summary>
    /// 用于测试异常
    /// </summary>
    [ServiceContract(Name = "UpdateProducts", Namespace = "
http://www.yoyoyoyoyo.com")]
    public interface IUpdateProducts
    {
        /*注意:此处可能会报以下错误:
         *
         * 由于  ContractFilter 在 EndpointDispatcher 不匹配,因此 Action 为
         * “http://www.yoyoyoyoyoyo.com/UpdateProducts/UpdateProduct”的消息
         * 无法在接收方处理。这可能是由于协定不匹配(发送方和接收方 Action 不
         * 匹配)或发送方和接收方绑定/安全不匹配。请检查发送方和接收方是否具有
         * 相同的协定和绑定(包括安全要求,如 Message、Transport、None)。[MessageParameter(Name = "Products")]
         *
         * 当在契约上加了[FaultContract(typeof(Exception))]标记后,服务端的异常并不影响客户端的执行
         *
         */
        [OperationContract]
        void UpdateProduct( Products products);
    }

    /// <summary>
    /// 用于测试异常
    /// </summary>
    [ServiceContract(Name = "GetProductssList", Namespace = "
http://www.yoyoyoyoyoyo.com")]
    public interface IGetProductssList
    {
        /*注意:此处不使用[ServiceKnownType(typeof(NotebookComputer))]
         * 基类数据契约上不使用[KnownType(typeof(NotebookComputer))]
         * 在客户端是带不出其派生类的
         */

        [OperationContract]
        List<Products> GetProductsList();

        [OperationContract]
        [ServiceKnownType(typeof(NotebookComputer))]
        void AddNotebookComputer(Products products);

        [OperationContract]
        [ServiceKnownType(typeof(NotebookComputer))]
        List<Products> GetNotebookComputer();
    }
}

1.2接口实现

namespace Services
{
    public class GetProducts : IGetProducts
    {
        DBHelper dbHelp = new DBHelper();
        #region IGetProducts 成员

        public Products GetProduct(int products)
        {
            return dbHelp.GetProducts(products);
        }

        #endregion
    }
   
    [ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession)]         
    public class AddProducts : IAddProducts
    {
        #region IAddProducts 成员

        public void AddProduct(Products products)
        {
            DBHelper dbHelp = new DBHelper();
            string msg = null;
            try
            {
                dbHelp.AddProducts(products);
                msg = "添加完成";
            }
            catch
            {
                msg = "添加失败!";
            }
            IAddProductsCallback callback = OperationContext.Current.GetCallbackChannel<IAddProductsCallback>();
            callback.GetResult(msg);

        }

        #endregion
    }

    public class DelProducts : IDelProducts
    {
        #region IDelProducts 成员

        public void DelProduct(int productsID)
        {
            DBHelper dbHelp = new DBHelper();
            string msg = null;
            try
            {
                dbHelp.DelProducts(productsID);
                msg = "删除成功";
            }
            catch
            {
                msg = "删除失败!";
            }
            IDelProductsCallback callback = OperationContext.Current.GetCallbackChannel<IDelProductsCallback>();
            callback.DelProduct(msg);
        }

        #endregion
    }

    public class UpdateProducts : IUpdateProducts
    {
        #region IUpdateProducts 成员

        public void UpdateProduct(Products products)
        {
            DBHelper dbHelp = new DBHelper();
            try
            {
                dbHelp.UpdateProducts(products);
            }
            catch
            {
                InvalidOperationException ex = new InvalidOperationException("传入参数缺少主键");
                ExceptionDetail detail = new ExceptionDetail(ex);

                //ExceptionDetail用于调试错误代码
                //FaultException就是SOAP错误;此时服务端的错误和客户端的错误信息一致
                throw new FaultException<ExceptionDetail>(detail, ex.Message.ToString());
            }
        }

        #endregion
    }

    public class GetProductssList : IGetProductssList
    {
        DBHelper dbHelp = new DBHelper();

        #region IGetProductssList 成员

        public List<Products> GetProductsList()
        {
            return dbHelp.GetProductsList();
        }

        public List<Products> GetNotebookComputer()
        {
            return dbHelp.GetNotebookComputer();
        }

        public void AddNotebookComputer(Products products)
        {
            dbHelp.AddNotebookComputer(products);
        }

        #endregion
    }

}

1.3数据对象

namespace DBModel
{
    /// <summary>
    /// Name:设置Products在SOAP包中的名称,同时反映到客户端
    ///
http://schemas:数据契约命名空间需要使用此字符串开头
    /// </summary>
    [DataContract(Name = "Products", Namespace = "
http://schemas.Products")]
    //[KnownType(typeof(NotebookComputer))]
    public class Products : IExtensibleDataObject
    {
        private Int32 m_ProductID;
        /// <summary>
        /// DataMember:使用在属性上,而不是使用在成员上
        /// IsRequired:表示此字段在序列化之前是否需要赋值
        /// Order:指该对象在序列化时的顺序,即在SOAP包中的顺序,主要作用体现在等效数据契约上
        /// </summary>
        [DataMember(Name = "ProductID", IsRequired = true, Order = 1)]
        public Int32 ProductID
        {
            set { m_ProductID = value; }
            get { return m_ProductID; }
        }

        [DataMember(Name = "ProductName", IsRequired = true, Order = 2)]
        public String ProductName { get; set; }

        [DataMember(Name = "ProductOrigin", IsRequired = true, Order = 3)]
        public String ProductOrigin { get; set; }

        [DataMember(Name = "ProductCategory", IsRequired = false, Order = 4)]
        public String ProductCategory { get; set; }

        #region IExtensibleDataObject 成员
        private ExtensionDataObject m_extensionDataObject;
        public ExtensionDataObject ExtensionData
        {
            get
            {
                return m_extensionDataObject;
            }
            set
            {
                m_extensionDataObject = value;
            }
        }

        #endregion
    }

    public class NotebookComputer : Products
    {
        public string Bluetooth { get; set; }
    }
}

2.数据访问层(略)

3.宿主

3.1

        static void Main(string[] args)
        {
            //GetProducts
            ServiceHost host1 = new ServiceHost(typeof(GetProducts));
            host1.Open();
            Console.WriteLine("basicHttpBinding服务已启动!");

            //AddProducts
            ServiceHost host2 = new ServiceHost(typeof(AddProducts));
            host2.Open();
            Console.WriteLine("wsDualHttpBinding双工服务已启动!");

            //DelProducts
            ServiceHost host3 = new ServiceHost(typeof(DelProducts));
            host3.Open();
            Console.WriteLine("netTcpBinding双工服务已启动!");

            //UpdateProducts
            ServiceHost host4 = new ServiceHost(typeof(UpdateProducts));
            host4.Open();
            Console.WriteLine("测试异常服务已启动!");

            //GetProductssList
            ServiceHost host5 = new ServiceHost(typeof(GetProductssList));
            host5.Open();
            Console.WriteLine("获取数据列表服务已启动!");
            Console.ReadKey();
        }
    }

3.2服务配置:

<system.serviceModel>
    <bindings>
      <wsDualHttpBinding>
        <binding name="dualHttpBinding" transactionFlow="true" maxReceivedMessageSize="100000"
          messageEncoding="Text" clientBaseAddress="
http://localhost:8850/Services/" />
      </wsDualHttpBinding>
    </bindings>
   
    <services>
      <!--这一部分使用wsDualHttpBinding进行双通道通信-->
      <service name="Services.AddProducts" behaviorConfiguration="serviceBehavior">
        <endpoint address="AddProducts" binding="wsDualHttpBinding" bindingConfiguration="dualHttpBinding"
          contract="Services.IAddProducts" />
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
        <host>
          <baseAddresses>
            <add baseAddress="
http://localhost:8020" />
          </baseAddresses>
        </host>
      </service>

      <!--这一部分使用netTcpBinding进行双通道通信-->
      <service name="Services.DelProducts" behaviorConfiguration="serviceBehavior">
        <!--使用http协议绑定;绑定具体的服务协议;设置服务地址,这个地址是个相对baseAddress的地址-->
        <endpoint binding="netTcpBinding" contract="Services.IDelProducts" address="DelProducts" ></endpoint>
        <!--这是元数据交换特有的绑定协议;IMetadataExchange元数据交换协定-->
        <endpoint binding="mexTcpBinding" contract="IMetadataExchange" address="mex"></endpoint>
        <!--这个节配置宿主信息-->
        <host>
          <baseAddresses>
            <add baseAddress="
http://localhost:8030"/>
            <add baseAddress="net.tcp://localhost:8031"/>
          </baseAddresses>
        </host>
      </service>

      <!--处理服务异常-->
      <service name="Services.UpdateProducts" behaviorConfiguration="serviceBehavior">
        <endpoint address="UpdateProducts" binding="basicHttpBinding" contract="Services.IUpdateProducts" />
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
        <host>
          <baseAddresses>
            <add baseAddress="
http://localhost:8040" />
          </baseAddresses>
        </host>
      </service>

      <!--获取数据列表-->
      <service name="Services.GetProductssList" behaviorConfiguration="serviceBehavior">
        <endpoint address="GetProductssList" binding="basicHttpBinding" contract="Services.IGetProductssList" />
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
        <host>
          <baseAddresses>
            <add baseAddress="
http://localhost:8050" />
          </baseAddresses>
        </host>
      </service>
     
        <!--这个Name是必须的,它指定了具体提供服务的类;behaviorConfiguration设置行为配置策略-->
        <service name="Services.GetProducts" behaviorConfiguration="serviceBehavior">
          <!--使用http协议绑定;绑定具体的服务协议;设置服务地址,这个地址是个相对baseAddress的地址-->
          <endpoint binding="basicHttpBinding" contract="Services.IGetProducts" address="GetProducts" ></endpoint>
          <!--这是元数据交换特有的绑定协议;IMetadataExchange元数据交换协定-->
          <endpoint binding="mexHttpBinding" contract="IMetadataExchange" address="mex"></endpoint>
          <!--这个节配置宿主信息-->
          <host>
            <baseAddresses>
              <add baseAddress="
http://localhost:8010"/>
            </baseAddresses>
          </host>

      </service>
    </services>

    <!--这个节使用来配置服务的行为-->
    <behaviors>
      <serviceBehaviors>
        <behavior name="serviceBehavior">
          <!--指定元数据使用http的get方式获取-->
          <serviceMetadata httpGetEnabled="true"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>

  <!--<system.runtime.serialization>
    <dataContractSerializer>
      <declaredTypes>
        <add type="DBModel.Products, DBModel, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
          <knownType type="DBModel.NotebookComputer, DBModel, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
        </add>
      </declaredTypes>
    </dataContractSerializer>
  </system.runtime.serialization>-->

4.客户端:

客户端代理生成参见学习笔记(二),单独说明回调的使用:

public class AddCallBack : AddProductsCallback
    {
        private static string message;
        #region AddProductsCallback 成员

        /*
         * 注意:此回调方法是在本次服务调用完并且在页面完全显示后才执行
         * GetMsg()方法最初的用意是返回到界面上弹出提示信息,由于此时页
         * 面已完全显示,所以此方法是没必要的
         *
         * 注意:
http://localhost:8788/Services/其中的端口号,每
         * 启动一次客户端进程,这个端口号就需要改变,否则报错:
         * HTTP 无法注册 URL
http://+:8788/Services/。另一应用程序已使用 HTTP.SYS 注册
         * 解决方案:使用编程的方式创建客户端代理
         */
        public void GetResult(string msg)
        {
            message = msg;
        }

        #endregion

        public string GetMsg()
        {
            return message;
        }
    }

    public class DelCallBack : DelProductsCallback
    {
        private static string message;

        #region DelProductsCallback 成员

        public void DelProductsCallback(string msg)
        {
            message = msg;
        }

        #endregion

        public string GetMsg()
        {
            return message;
        }
    }
}

5.动态设置客户端监听基址,此代码在双工通信方式下调用:

namespace WebClient
{
    /*
     动态设置客户端Endpoint地址
     IPAddress ipAddress;int port;
     client.Endpoint.Address = new EndpointAddress("http://" + ipAddress.ToString() + ":"
     + port.ToString());
    
     */


    /// <summary>
    /// 动态指定客户端终结点地址,避免出现以下错误:
    ///
    /// </summary>
    public class AddProductClientBaseAddressEx
    {
        public static void GetAddProductsClientBaseAddress(AddProductsClient client)
        {
            string clientBaseAddress = null;
            IPAddress address = Dns.GetHostAddresses("127.0.0.1")[0];
            clientBaseAddress = "http://" + address.ToString() + ":";
            WSDualHttpBinding binding = client.Endpoint.Binding as WSDualHttpBinding;
            int port = AddProductClientBaseAddressEx.FindFreePort(address);
            binding.ClientBaseAddress = new Uri(clientBaseAddress + port.ToString());
        }

        public static int FindFreePort(IPAddress hostAddress)
        {
            //hostAddress:这个服务的对应的主机地址;
            IPEndPoint endPoint = new IPEndPoint(hostAddress, 0);
            //InterNetwork:IPV4地址协议
            using (Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
            {
                socket.Bind(endPoint);
                IPEndPoint local = (IPEndPoint)socket.LocalEndPoint;
                return local.Port;
            }
        }
    }
}

 

 

原创粉丝点击