zoukankan      html  css  js  c++  java
  • wcf基础教程之 契约(合同)Contract

    wcf基础教程之 契约(合同)Contract

    在前几篇博客中我有说到服务的寄宿,就是服务要运行起来必须采取的几种方式,相当于我们可以照葫芦画瓜的效果运行一个wcf服务,但是那只是实践,我们知其然更要知其所以然,所以从今天开始我们开始介绍wcf的三大部分:契约、绑定以及服务。当然这其中会有很多的细节问题,比如说终结点等等,我们穿插着进行。今天就先来预习一下契约,也称为合同(Contract).

    wcf的契约一共包括几种:数据契约、服务契约、消息契约、错误契约。既然是预习,那么我们就一起来学习一下这几种契约的作用。

    数据契约:DataContract 是用来对wcf的数据传输进行串行化。说到串行化,.net中也有串行化,但是.Net的串行化是把所有数据:私有成员、公有成员以及私有属性、公有属性等全部串行化,但是wcf的数据串行化是需要显式指定的,只有我们标记了DataContract属性的类上的DataMember属性之后才会串行化。数据契约涉及到版本问题,也就是数据字段的添加或删除?这个问题留给亲爱的你来解决吧。

    服务契约:ServiceContract 只有标记了ServiceContract的类或接口才可以作为契约对外发布,并且契约中的方法只有标记了OperationContract,才会被客户端访问到。也就是说服务契约对外发布的接口方法也是需要显式指定的。其中有双向绑定、单工模式、请求响应模式。在服务契约的开发中,我们如果采用wcf的并发模式,那么我们一定要注意死锁的问题?ServiceBehavior 特性的ConcurrencyMode我们可以通过制定Multiple来启动多线程访问。

    错误契约:FaultContract 是用来对客户端显式错误信息。可以自定义要显式的错误信息。

    消息契约:MessageContract 可以对Soap消息进行完全的控制。但是一般用不到,所以我们有机会再深入研究这个契约。

    简洁的介绍了一下这几种契约,下面我们通过一个简单的示例综合上面的除了消息契约以外的几种契约,让我们有一个大概的认识。项目还是沿用我们原来的那个项目版本,我在那个上面更改,具体的项目结构我就不再赘述。

    首先,我们创建一个契约IPerson 表示Person的契约,

    复制代码
     1 using System;
     2 using System.Collections.Generic;
     3 using System.Linq;
     4 using System.Text;
     5 using System.ServiceModel;
     6 namespace Chinaer.WcfDemo.Contracts
     7 {
     8     [ServiceContract]
     9     public interface IPerson
    10     {
    11         [OperationContract]
    12         [FaultContract(typeof(StateStatus))]
    13         PersonInfo GetPerson();
    14 
    15         /// <summary>
    16         /// 请注意我们没有制定Check方法的OperationContract属性 它不会被客户端调用到
    17         /// </summary>
    18         /// <param name="perInfo"></param>
    19         /// <returns></returns>
    20         bool Check(PersonInfo perInfo);
    21     }
    22 }
    复制代码

    其中在服务契约方法可能会使用到自定义异常信息的方法,我们添加了泛型FaultContract

    添加PersonInfo实体类以及错误契约的声明

    复制代码
     1 using System;
     2 using System.Collections.Generic;
     3 using System.Linq;
     4 using System.Text;
     5 using System.ServiceModel;
     6 using System.Runtime.Serialization;
     7 namespace Chinaer.WcfDemo.Contracts
     8 {
     9     [DataContract]
    10     public class PersonInfo
    11     {
    12         [DataMember]
    13         public string Name { get; set; }
    14 
    15         [DataMember]
    16         public string Address { get; set; }
    17         /// <summary>
    18         /// 请注意Age属性我们没有添加DataMember属性 就表示它不会被序列化
    19         /// </summary>
    20         public int Age { get; set; }
    21     }
    22 }
    复制代码
    复制代码
     1 using System;
     2 using System.Collections.Generic;
     3 using System.Linq;
     4 using System.Text;
     5 using System.ServiceModel;
     6 using System.Runtime.Serialization;
     7 namespace Chinaer.WcfDemo.Contracts
     8 {
     9     [DataContract]
    10     public class PersonInfo
    11     {
    12         [DataMember]
    13         public string Name { get; set; }
    14 
    15         [DataMember]
    16         public string Address { get; set; }
    17         /// <summary>
    18         /// 请注意Age属性我们没有添加DataMember属性 就表示它不会被序列化
    19         /// </summary>
    20         public int Age { get; set; }
    21     }
    22 }
    复制代码

    上面是PersonInfo实体类说明

    错误信息实体类声明:

    复制代码
     1 using System;
     2 using System.Collections.Generic;
     3 using System.Linq;
     4 using System.Text;
     5 using System.ServiceModel;
     6 using System.Runtime.Serialization;
     7 namespace Chinaer.WcfDemo.Contracts
     8 {
     9     [DataContract]
    10     /// <summary>
    11     /// 错误契约
    12     /// </summary>
    13    public class StateStatus
    14     {
    15         [DataMember]
    16         public int BadState { get; set; }
    17 
    18     }
    19 }
    复制代码

    我们添加服务类,我们在服务类方法中故意抛出一个自定义异常信息。

    复制代码
     1 using System;
     2 using System.Collections.Generic;
     3 using System.Linq;
     4 using System.Text;
     5 using Chinaer.WcfDemo.Contracts;
     6 using System.ServiceModel;
     7 namespace Chinaer.WcfDemo.Services
     8 {
     9     /// <summary>
    10     /// 这是服务实现类
    11     /// </summary>
    12     public class Person : IPerson
    13     {
    14 
    15         public PersonInfo GetPerson()
    16         {
    17 
    18             PersonInfo pInfo = new PersonInfo() { Address = "济南", Age = 25, Name = "郭志奇" };
    19             ///我们故意抛出一个自定义异常信息
    20             if (pInfo != null)
    21             {
    22                 //创建两个表示错误信息的数组
    23                 FaultReasonText[] text = new FaultReasonText[2];
    24                 text[0] = new FaultReasonText("simple message", new System.Globalization.CultureInfo("en-us"));
    25                 text[1] = new FaultReasonText("中文表示 简单的错误信息", new System.Globalization.CultureInfo("zh-cn"));
    26                 FaultReason faultReason = new FaultReason(text);
    27                 throw new FaultException<StateStatus>(new StateStatus() { BadState = int32.MaxValue }, faultReason);
    28 
    29             }
    30 
    31             return pInfo;
    32         }
    33 
    34         public bool Check(PersonInfo perInfo)
    35         {
    36             bool flag = false;
    37             if (perInfo.Name.ToLower() == "guozhiqi")
    38             {
    39                 flag = true;
    40             }
    41             return flag;
    42         }
    43     }
    44 }
    复制代码

    其中错误异常信息FaultReasonText 表示错误信息说明。

    上面就实现了服务契约、错误契约、以及数据契约的示例。大家可以看到,其实错误契约是在一个数据契约定义的基础上在服务契约的方法中添加FaultContract 来实现的自定义异常信息。并且在操作方法中还要包装要抛出的异常信息。

    我们在客户端调用来查看是否可以正常的返回异常信息。其中我们要创建宿主。

    ConsoleHosting就是控制台宿主类,我们通过它类运行服务。

    配置文件:

    复制代码
     1 <?xml version="1.0" encoding="utf-8" ?>
     2 <configuration>
     3   <system.serviceModel>
     4     <behaviors>
     5       <serviceBehaviors >
     6         <behavior name="metaDataBehavior">
     7           <serviceMetadata httpGetEnabled="true" httpGetUrl="http://127.0.0.1:8888/personService/mex"/>
     8           <serviceDebug includeExceptionDetailInFaults="true"/>
     9         </behavior>
    10       </serviceBehaviors>
    11     </behaviors>
    12 
    13     <services>
    14       <service name="Chinaer.WcfDemo.Services.CalService" behaviorConfiguration="metaDataBehavior">
    15         <endpoint address="http://127.0.0.1:8888/calService" binding="wsHttpBinding" contract="Chinaer.WcfDemo.Contracts.ICal"></endpoint>
    16       </service>
    17       <service name="Chinaer.WcfDemo.Services.Person" behaviorConfiguration="metaDataBehavior">
    18         <endpoint address="http://127.0.0.1:8888/personService" binding="wsHttpBinding" contract="Chinaer.WcfDemo.Contracts.IPerson"
    19                   ></endpoint>
    20         
    21       </service>
    22     </services>
    23 
    24     <bindings></bindings>
    25     
    26   </system.serviceModel>
    27 </configuration>
    复制代码

    配置文件很简单,服务宿主的运行依然很简单ServiceHost

    复制代码
     1 using System;
     2 using System.Collections.Generic;
     3 using System.Linq;
     4 using System.Text;
     5 using System.ServiceModel;
     6 using Chinaer.WcfDemo.Contracts;
     7 using System.ServiceModel.Description;
     8 using Chinaer.WcfDemo.Services;
     9 using System.ServiceModel.Dispatcher;
    10 namespace Chinaer.WcfDemo.ConsoleHosting
    11 {
    12     class Program
    13     {
    14         static void Main(string[] args)
    15         {
    16             using (ServiceHost host = new ServiceHost(typeof(Person)))
    17             {
    18                 //1.创建ServiceHost对象 这里我们添加了服务的地址,如果这里没有添加服务地址,那么我们在添加服务终结点的时候就需要添加完整路径
    19                 //2/添加服务终结点 注意后面的地址我设置为空 因为我在前面添加了地址
    20                 ////////host.AddServiceEndpoint(typeof(ICal), new WSHttpBinding(), "");
    21                 if (host.Description.Behaviors.Find<ServiceMetadataBehavior>() == null)
    22                 {
    23                     //3.添加元数据终结点 上面添加了这个判断的原因 就是查找配置文件中是否配置了元数据行为 因为我们重命名了配置文件 所以元数据行为肯定为null
    24                     ServiceMetadataBehavior metaDataBehavior = new ServiceMetadataBehavior();//创建元数据行为对象
    25                     metaDataBehavior.HttpGetEnabled = true;//重点 启用元数据行为的访问
    26                     metaDataBehavior.HttpGetUrl = new Uri("http://127.0.0.1:9999/person/msdl");
    27                     host.Description.Behaviors.Add(metaDataBehavior);//添加元数据行为
    28                 }
    29                 //添加打开服务宿主的执行函数 本例为匿名函数
    30 
    31 
    32 
    33                 host.Opened += delegate
    34                 {
    35                     Console.WriteLine("服务已启动 按任意键退出");
    36                 };
    37                 try
    38                 {
    39                     if (host.State != CommunicationState.Opened)
    40                     {
    41                         host.Open();
    42                         Console.Read();
    43                     }
    44 
    45                 }
    46                 catch (Exception ex)
    47                 {
    48                     throw ex;
    49                 }
    50 
    51                 Console.Read();
    52             }
    53 
    54         }
    55     }
    56 }
    复制代码

    通过上面的步骤我们基本上就可以在浏览器中查看服务的运行情况,我们在客户端调用服务查看异常的详细信息。

    由于采用的是MVC,所以我们把调用服务的操作放到了一个Action中,

    复制代码
     1  public ActionResult Index()
     2         {
     3             try
     4             {
     5                 ViewBag.Message = "修改此模板以快速启动你的 ASP.NET MVC 应用程序。";
     6                 WSHttpBinding wsHttpbinding = new WSHttpBinding();
     7                 EndpointAddress endAddress = new EndpointAddress("http://127.0.0.1:8888/" +
     8                 "personservice");
     9                 using (ChannelFactory<IPerson> factory = new ChannelFactory<IPerson>(wsHttpbinding, endAddress))
    10                 {
    11                     IPerson person = factory.CreateChannel();
    12 
    13                     person.GetPerson();
    14                 }
    15             }
    16             catch (FaultException<StateStatus> ex)
    17             {
    18                 Response.Write(ex.Detail.BadState);
    19             }
    20             return View();
    21         }
    复制代码

    通过在客户端创建信道来调用运程服务程序,我们调用了服务的方法。如果可以正常运行,我们会看到异常信息打印到浏览器上,而且是int32的最大值.

    这里有一个调试技巧,有园友问我,每次调试都要单独的执行控制台宿主,然后再执行客户端程序,太麻烦了,有没有简单的办法。这里倒是有一个。我们选择解决方案的属性,然后选择宿主程序和客户端程序同时启动就可以。

    这样我们运行程序,在浏览器中如果看到-1 就表示我们的异常信息成功显示。

    显示了int32的最大值,说明我们的异常信息正确显示了。

     

    总结一下:这一篇博客有点乱,没有头绪。服务契约就是定义应该具有的操作,由OperationContract具体控制。

    数据契约主要是用来让数据序列化,由DataMember实际控制。

    错误契约 显示自定义的异常信息,防止详细信息被客户端看到,保护程序的安全。但是错误契约需要提前了解到哪个方法会引发哪种类型的异常。这个是很难确定的。所以我们在捕获异常信息的时候,除了捕获已知的异常信息外,我们还需要捕获FaultException 和CommunicationException异常,这两个类是我们自定义异常信息的基类。通过捕获CommunicationException 我们还可以捕获与wcf通信相关的其他异常信息。

    契约的范围很广,我们慢慢叙述,其中每种契约的各种属性的对照,我们需要深入学习。因为每个属性的误用都可能会导致程序运行出现异常。

    每天进步一点,一年就会进步一大步,十年就可以成功,君子当自强不息,君子当好好学习,每天进步
     
    分类: Asp.Net MVC 3Wcf
  • 相关阅读:
    ASP.NET 中通过Form身份验证 来模拟Windows 域服务身份验证的方法
    中华枣文化三字经
    佛祖保佑、永无BUG!!!
    fatal error C1010: 在查找预编译头时遇到意外的文件结尾 (转)
    VisualSVN Server搭建SVN服务器<转>
    AMF_OBJECT 数据结构浅析
    rtmp聊天相关归总
    signal(SIGPIPE, SIG_IGN) (转)
    malloc()与calloc区别 (转)
    select()函数以及FD_ZERO、FD_SET、FD_CLR、FD_ISSET(转)
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/2971733.html
Copyright © 2011-2022 走看看