第二节
【学习】构建WCF面向服务的应用程序系列课程笔记:(2) 契约设计
的内容比较多,需要时间消化一下。这几天工作也比较忙,节奏慢了哦,罪过罪过。
本文对应第二节演示的DEMO,自己针对服务契约、复杂类型序列化中的DataContract、KnownType(实现多态)、IXmlSerialiable与消息契约动手实践了一下。
ServiceContract:
BussinessServices
项目中写了一个IServiceA的服务契约,类ServiceA为IServiceA的实现:
IServiceA:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
namespace BussinessServices
{
[ServiceContract]
public interface IServiceA
{
[OperationContract]
string Operation1();
[OperationContract]
string Operation2();
[OperationContract(Name="OperationABC")]
string Operation3();
}
}
ServiceA:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BussinessServices
{
public class ServiceA:IServiceA
{
public string Operation1()
{
return "IServiceA Operation1 invoked.";
}
public string Operation2()
{
return "IServiceA Operation2 invoked.";
}
public string Operation3()
{
return "IServiceA 中定义的方法为Operation3,但客户端调用是用OperationABC.";
}
}
}
Host项目运行服务:
App.config:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service name="BussinessServices.ServiceA">
<endpoint address="ServiceA" binding="wsHttpBinding" contract="BussinessServices.IServiceA">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="http://localhost:8000/" />
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
</configuration>
Program:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
namespace Host
{
class Program
{
static void Main(string[] args)
{
ServiceHost hostA = null;
try
{
hostA = new ServiceHost(typeof(BussinessServices.ServiceA));
hostA.Open();
Console.WriteLine();
Console.WriteLine("Press <Enter> to closed host.");
Console.ReadLine();
}
catch (System.Exception ex)
{
}
finally
{
hostA.Close();
}
}
}
}
Client客户端项目:
首先运行Host,并在Client客户端中引用服务引用,生成LocalHost代码,在主窗口中拖放三个按钮:
代码如下:
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 Client
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
using (LocalHost.ServiceAClient proxy = new LocalHost.ServiceAClient())
{
string s = proxy.Operation1();
MessageBox.Show(s);
}
}
private void button2_Click(object sender, EventArgs e)
{
using (LocalHost.ServiceAClient proxy = new LocalHost.ServiceAClient())
{
string s = proxy.Operation2();
MessageBox.Show(s);
}
}
private void button3_Click(object sender, EventArgs e)
{
using (LocalHost.ServiceAClient proxy = new LocalHost.ServiceAClient())
{
//OperationABC是IService契约的Operation3
string s = proxy.OperationABC();
MessageBox.Show(s);
}
}
}
}
到此这个例子结束,运行时选运行Host,开启服务,然后运行Client,分别点击三个按钮,看到效果。
DataContract
这个例子的项目结构见下图:
Person.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
namespace ContentTypes
{
/// <summary>
/// 人的基类
/// </summary>
[DataContract]
public class Person
{
private string m_sn;
private string m_name;
private string m_sex;
/// <summary>
/// 编号
/// </summary>
[DataMember]
public string SN
{
get { return m_sn; }
set { m_sn = value; }
}
/// <summary>
/// 姓名
/// </summary>
[DataMember]
public string Name
{
get { return m_name; }
set { m_name = value; }
}
/// <summary>
/// 性别
/// </summary>
[DataMember]
public string Sex
{
get { return m_sex; }
set { m_sex = value; }
}
}
}
服务IPersonManagerService.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using ContentTypes;
namespace BussinessServices
{
[ServiceContract]
public interface IPersonManagerService
{
[OperationContract]
Person GetPerson();
[OperationContract]
void SetPerson(Person person);
}
[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession)]
public class PersonManagerService : IPersonManagerService
{
private Person m_person;
public Person GetPerson()
{
return m_person;
}
public void SetPerson(Person person)
{
m_person = person;
}
}
}
Host项目的App.config文件:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service name="BussinessServices.PersonManagerService">
<endpoint address="Person" binding="wsHttpBinding" contract="BussinessServices.IPersonManagerService">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="http://localhost:8000/" />
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
</configuration>
Host项目的Program.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
namespace Host
{
class Program
{
static void Main(string[] args)
{
using (ServiceHost host = new ServiceHost(typeof(BussinessServices.PersonManagerService)))
{
host.Open();
Console.WriteLine();
Console.WriteLine("Press <Enter> to closed host.");
Console.ReadLine();
}
}
}
}
PersonEntry
客户端同样先使用服务引用,VS会自动生成调用服务的代码,我这里的命名是localhost,在主窗体中放了对应Person类的属性的TextBox,两个按钮分别是保存:负责取表单中数据并保存,获取按钮从服务取得数据并加载到表单中。
代码:
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 PersonEntry
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
localhost.PersonManagerServiceClient proxy = new localhost.PersonManagerServiceClient();
private void btn_Save_Click(object sender, EventArgs e)
{
localhost.Person person = new localhost.Person();
person.SN = txt_SN.Text;
person.Sex = txt_Sex.Text;
person.Name = txt_Name.Text;
proxy.SetPerson(person);
MessageBox.Show("info set sucess.");
}
private void btn_GetInfo_Click(object sender, EventArgs e)
{
var person = proxy.GetPerson();
if (person != null)
{
txt_SN.Text = person.SN;
txt_Sex.Text = person.Sex;
txt_Name.Text = person.Name;
MessageBox.Show("info get sucess.");
return;
}
MessageBox.Show("请先点击【保存】按钮,保存成功后,改变表单数据,再【获取】!");
}
}
}
运行效果:
KnownTypesDataContracts
实现多态的例子,项目结构见下图:
对多态的实现有四种方式:
-
在实体类上使用KnownType
-
在契约上使用ServiceKnownType
-
在契约的方法中使用ServiceKnownType
-
通过配置文件实现
在代码中有三种方式,详细看注释,第4种自己没有测试,就没写了。
Person.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
namespace ContentTypes
{
/// <summary>
/// 人的基类
/// </summary>
[DataContract]
//第一种方式,在基类上指定KnownType,标示子类,实现多态
[KnownType(typeof(Employee))]
public class Person
{
private string m_sn;
private string m_name;
private string m_sex;
/// <summary>
/// 编号
/// </summary>
[DataMember]
public string SN
{
get { return m_sn; }
set { m_sn = value; }
}
/// <summary>
/// 姓名
/// </summary>
[DataMember]
public string Name
{
get { return m_name; }
set { m_name = value; }
}
/// <summary>
/// 性别
/// </summary>
[DataMember]
public string Sex
{
get { return m_sex; }
set { m_sex = value; }
}
}
}
可以在基类上用KnownType指定子类,见代码注释。
Employee.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
namespace ContentTypes
{
[DataContract]
public class Employee : Person
{
private string m_id;
/// <summary>
/// 成员ID
/// </summary>
[DataMember]
public string Id
{
get { return m_id; }
set { m_id = value; }
}
}
}
IPersonManagerSerivce服务:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using ContentTypes;
namespace BussinessServices
{
[ServiceContract]
//第二种方式,在契约上使用ServiceKwnoType,这是一种全局的方式
//,如果在契约上标示过的子类,则在契约中的所有方法都可使用标示过的子类
//[ServiceKnownType(typeof(Employee))]
public interface IPersonManagerService
{
[OperationContract]
Person GetPerson();
[OperationContract]
//第三种方式,在方法上使用ServiceKnownType,这种方式限制在此方法上只能使用标示过的子类,若有其它子类
//,但没有使用ServiceKnownType标示,则不能被使用,这种方式使用控制更加灵活
[ServiceKnownType(typeof(Employee))]
void SetPerson(Person person);
}
[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession)]
public class PersonManagerService : IPersonManagerService
{
private Person m_person;
public Person GetPerson()
{
return m_person;
}
public void SetPerson(Person person)
{
m_person = person;
}
}
}
这个例子中的Host表上面例子一样,就不写了。
PersonEntry
客户端,主窗口内容与DataContract的例子一样,但是在按钮事件中,我们没有使用Person,而是使用的子类Employee:
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 PersonEntry
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
localhost.PersonManagerServiceClient proxy = new localhost.PersonManagerServiceClient();
private void btn_Save_Click(object sender, EventArgs e)
{
localhost.Employee employee = new localhost.Employee();
employee.SN = txt_SN.Text;
employee.Sex = txt_Sex.Text;
employee.Name = txt_Name.Text;
proxy.SetPerson(employee);
MessageBox.Show("info set sucess.");
}
private void btn_GetInfo_Click(object sender, EventArgs e)
{
var employee = proxy.GetPerson();
if (employee != null)
{
txt_SN.Text = employee.SN;
txt_Sex.Text = employee.Sex;
txt_Name.Text = employee.Name;
MessageBox.Show("info get sucess.");
return;
}
MessageBox.Show("请先点击【保存】按钮,保存成功后,改变表单数据,再【获取】!");
}
}
}
运行这个例子可以发现,如果不使用KnownType指定子类,则客户端根本不会生成Employee,则肯定是无法使用的。
DEMO下载:WCF服务契约与复杂类型序列化DEMO
未完续:
【学习】WCF的服务契约、复杂类型序列化、消息契约的实现续-IXmlSerializable与MessageContract