接前面
【学习】WCF的服务契约、复杂类型序列化、消息契约的实现
这帖实践对应
【学习】构建WCF面向服务的应用程序系列课程笔记:(2) 契约设计
IXmlSerializable
- IXmlSerializable类型为WSDL和元数据交换(MEX)提供了XSD schema
- 支持Contract-first
- 在服务契约中验证(类似数据契约DataContract)
- 开发人员自己处理XML与业务对象之间的映射关系
- 需要具备XML的相关知识
- 进行适当的检验
- 在开销上较使用临时的数据契约少
- 在序列化过程中执行:
- IXmlSerializable.ReadXml() 返序列化时调用
- IXmlSerializable.WriteXml() 序列化时调用
- XmlSchemaProViderAttribute
- 改进IXmlSerializable.GetSchema() 当客户端或浏览者访问schema时,会调用GetSchema方法
- 为WSDL和MEX返回schema
在这个实践中,直接使用了VS2010中的WCF Service库来替代了前面例子中的BussinessServices与Host两个项目,通过WCF Service 库创建服务后,不需要像前面那种方式需要自己手动先运行Host,更加的方便。
项目结构:
Person.cs
实体类中没有使用任何可序列化的标记,意味这个类本来是不可以被序列化的
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
namespace ContentTypes
{
/// <summary>
/// 人的基类
/// </summary>
public class Person
{
private string m_sn;
private string m_name;
private string m_sex;
/// <summary>
/// 编号
/// </summary>
public string SN
{
get { return m_sn; }
set { m_sn = value; }
}
/// <summary>
/// 姓名
/// </summary>
public string Name
{
get { return m_name; }
set { m_name = value; }
}
/// <summary>
/// 性别
/// </summary>
public string Sex
{
get { return m_sex; }
set { m_sex = value; }
}
}
}
PersonSerializer
实现Xml序列化
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Schema;
using System.Xml.Serialization;
using ContentTypes;
using System.Xml;
using System.IO;
namespace WCFServiceLibrary
{
[XmlSchemaProvider("GetSchema")]
public class PersonSerializer : IXmlSerializable
{
static string ns = "http://blog.csdn.net/llxchen";
static string xs = "http://www.w3.org/2001/XMLSchema";
private Person m_person;
public Person Person
{
get { return m_person; }
set { m_person = value; }
}
public PersonSerializer() { }
public PersonSerializer(Person person)
{
m_person = person;
}
public static XmlQualifiedName GetSchema(XmlSchemaSet schemaSet)
{
string schemaString = String.Format(
"<xs:schema xmlns:tns='{0}' xmlns:xs='{1}' targetNamespace='{0}' elementFormDefault='qualified' attributeFormDefault='unqualified'>"
+ "<xs:complexType name='Person'>"
+ "<xs:sequence>"
+ "<xs:element name='SN' type='xs:string' nillable='false'/>"
+ "<xs:element name='Name' type='xs:string' nillable='false'/>"
+ "<xs:element name='Sex' type='xs:string' nillable='false'/>"
+ "</xs:sequence>"
+ "</xs:complexType>"
+ "</xs:schema>", ns, xs);
XmlSchema schema = XmlSchema.Read(new StringReader(schemaString), null);
schemaSet.XmlResolver = new XmlUrlResolver();
schemaSet.Add(schema);
return new XmlQualifiedName("Person", ns);
}
public XmlSchema GetSchema()
{
throw new NotImplementedException("IXmlSerializable.GetSchema() is not implemented. Use static GetSchema() instead.");
}
public void ReadXml(System.Xml.XmlReader reader)
{
Person person = new Person();
while (reader.IsStartElement())
{
reader.MoveToContent();
reader.Read();
if (reader.IsStartElement("SN"))
{
reader.MoveToContent();
person.SN = reader.ReadString();
reader.MoveToContent();
reader.ReadEndElement();
}
else
throw new XmlException("ExpectedElementMissing: SN element was expected.");
if (reader.IsStartElement("Name"))
{
reader.MoveToContent();
person.Name = reader.ReadString();
reader.MoveToContent();
reader.ReadEndElement();
}
else
throw new XmlException("ExpectedElementMissing: Name element was expected.");
if (reader.IsStartElement("Sex"))
{
reader.MoveToContent();
person.Sex = reader.ReadString();
reader.MoveToContent();
reader.ReadEndElement();
}
else
throw new XmlException("ExpectedElementMissing: Sex element was expected.");
reader.MoveToContent();
reader.ReadEndElement();
}
this.m_person = person;
}
public void WriteXml(System.Xml.XmlWriter writer)
{
writer.WriteElementString("SN", ns, m_person.SN);
writer.WriteElementString("Name", ns, m_person.Name);
writer.WriteElementString("Sex", ns, m_person.Sex);
}
}
}
IPersonManagerService
服务契约,注意这里的传递的参数类型与返回类型已经变成了我们的PersonSerializer
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using ContentTypes;
namespace WCFServiceLibrary
{
[ServiceContract(Namespace = "http://blog.csdn.net/llxchen", SessionMode = SessionMode.Required)]
public interface IPersonManagerService
{
[OperationContract(Action = "http://blog.csdn.net/llxchen/GetPerson")]
PersonSerializer GetPerson();
[OperationContract(Action = "http://blog.csdn.net/llxchen/SetPerson")]
void SetPerson(PersonSerializer person);
}
}
PersonManagerService
实现契约
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using ContentTypes;
namespace WCFServiceLibrary
{
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class PersonManagerService : IPersonManagerService
{
private Person m_person;
public PersonSerializer GetPerson()
{
return new PersonSerializer(m_person);
}
public void SetPerson(PersonSerializer personSerializer)
{
m_person = personSerializer.Person;
}
}
}
客户端
代码与前面例子没有任何变化,意味着我们即使通过IXmlSerializable这种方式实现了序列化,但对客户端没有任何影响。
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("请先点击【保存】按钮,保存成功后,改变表单数据,再【获取】!");
}
}
}
消息契约(MessageContract)
- MessageContractAttribute
- 对控制消息头和消息元素提供强力支持
- 所支持的属性
- MessageHeaderAttribute
- 应用到消息契约的域(fields)或者(properties)
- 为创建自定义头提供了简单的方法
- 能够提供Name,NameSpace,ProtectionLevel
- 可以设置SOAP协议的设置:Relay,Actor,MustUnderstand
- 应用到消息契约的域(fields)或者(properties)
- MessageBodyMemberAttribute
- 应用到消息契约的域(fields)或者属性(properties)
- 能够拥有多个body元素
- 等价于在操作中拥有多个参数
- 返回多个复杂类型数据的唯一方法
- 总是提供顺序(Order)
- 可以设置Name,Namespace,ProtectionLevel
- MessageHeaderAttribute
- 用于
- 添加自定义头(custom headers)
- 控制消息是否被包装
- 控制签名与加密
实践:
项目结构:
Person.cs
使用的DataContract方式序列化:
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; }
}
}
}
Message.cs
定义MessageContract,其中定义了SetPersonRequest与SetPersonResponse对应契约的SetPerson方法,GetPersonRequest与GetPersonResponse对应GetPerson方法:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using ContentTypes;
namespace WCFServiceLibrary
{
[MessageContract(IsWrapped=false)]
public class SetPersonRequest
{
private Person m_person;
[MessageBodyMember] //定义了一个body
public Person Person
{
get { return m_person; }
set { m_person = value; }
}
}
[MessageContract(IsWrapped=false)]
public class SetPersonResponse
{
}
[MessageContract(IsWrapped=false)]
public class GetPersonRequest
{
private string m_licenseKey;
[MessageHeader]
public string LicenseKey
{
get { return m_licenseKey; }
set { m_licenseKey = value; }
}
}
[MessageContract(IsWrapped=false)]
public class GetPersonResponse
{
private Person m_person;
public GetPersonResponse() { }
public GetPersonResponse(Person person)
{
m_person = person;
}
[MessageBodyMember]
public Person Person
{
get { return m_person; }
set { m_person = value; }
}
}
}
总结一点:凡是定义在Request中有[MessageHeader]或[MessageBody]的那些属性,它们就是在客户端调用服务相应方法的参数。
IPersonManagerService.cs
定义契约,在使用MessageContract时,传入的参数应该是Request,返回则是对应的Response
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using ContentTypes;
namespace WCFServiceLibrary
{
[ServiceContract(Namespace = "http://blog.csdn.net/llxchen", SessionMode = SessionMode.Required)]
public interface IPersonManagerService
{
[OperationContract(Action = "http://blog.csdn.net/llxchen/GetPerson")]
GetPersonResponse GetPerson(GetPersonRequest request);
[OperationContract(Action = "http://blog.csdn.net/llxchen/SetPerson")]
SetPersonResponse SetPerson(SetPersonRequest request);
}
}
PersonManagerService.cs
实现契约:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using ContentTypes;
namespace WCFServiceLibrary
{
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class PersonManagerService : IPersonManagerService
{
private Person m_person;
public GetPersonResponse GetPerson(GetPersonRequest request)
{
if (request.LicenseKey != "xxx")
{
throw new FaultException("Invalid license key.");
}
return new GetPersonResponse(m_person);
}
public SetPersonResponse SetPerson(SetPersonRequest request)
{
m_person = request.Person;
return new SetPersonResponse();
}
}
}
客户端:
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("xxx");
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("请先点击【保存】按钮,保存成功后,改变表单数据,再【获取】!");
}
}
}
MessageContractAttribute 类型公开以下成员