WCF开发之自定义序列化(IXmlSerializable)
这部分东西需要XML相关知识和序列化和反序列化的知识,下面我们根据一个Demo来理解一下自定义序列化的过程。
这个例子的程序结构和之前的都是一样的,唯一不同的我们这里没有使用DataContract,而是采用自己来编写序列化器的方法,为什么要自定义哪?
在完美的WCF世界中:
– 创建同业务对象描述相同功能的数据契约
– 暴露在服务契约中
在现实世界中:
– 可能无法拥有对象(不可序列化)
– 业务对象的实例化可能不正确
– 可能需要对现存的schema进行支持
如何自定义序列化气?他是怎样实现的那?
• IXmlSerializable 类型为WSDL和元数据交换(MEX)提供了XSD schema
– 支持Contract-first
• 开发人员自己处理XML与业务对象之间的映射关系
– 需要具备XML的相关知识
– 进行适当的交验
• 在开销上较使用临时的数据契约少
• IXmlSerializable
– 在服务契约中验证(类似数据契约)
• 在序列化过程中执行:
– IXmlSerializable.ReadXml()
– IXmlSerializable.WriteXml()
• XmlSchemaProviderAttribute
– 改进IXmlSerializable.GetSchema()
– 为WSDL和MEX返回schema
看具体代码:
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
namespace ContentTypes
{
public class LinkItem
{
private long m_id;
private string m_title;
private string m_description;
private DateTime m_dateStart;
private DateTime m_dateEnd;
private string m_url;
public long Id
{
get { return m_id; }
set { m_id = value; }
}
public string Title
{
get { return m_title; }
set { m_title = value; }
}
public string Description
{
get { return m_description; }
set { m_description = value; }
}
public DateTime DateStart
{
get { return m_dateStart; }
set { m_dateStart = value; }
}
public DateTime DateEnd
{
get { return m_dateEnd; }
set { m_dateEnd = value; }
}
public string Url
{
get { return m_url; }
set { m_url = value; }
}
}
}
上面的LinkItem没有使用DataContract和DataMember所以他目前只是一个普通的类,WCF不会为他自动创建序列化器,所以他不能再Server和Client之间直接进行传递。
下面开Service端的代码,这个文件就是我们自己定义的LinkItem的序列化器:
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using ContentTypes;
using System.Xml;
using System.Xml.Schema;
using System.IO;
namespace WcfServiceLibraryDemo
{
[XmlSchemaProvider("GetSchema")]
public class LinkItemSerializer : IXmlSerializable
{
static string ns = "http://www.cnblogs.com/Charlesliu";
static string xs = "http://www.w3.org/2001/XMLSchema";
private LinkItem m_linkItem;
public LinkItem LinkItem
{
get { return m_linkItem; }
set { m_linkItem = value; }
}
public LinkItemSerializer()
{
}
public LinkItemSerializer(LinkItem item)
{
this.m_linkItem = item;
}
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='LinkItem'>" +
"<xs:sequence>" +
"<xs:element name='Id' type='xs:long' nillable='false'/>" +
"<xs:element name='Title' type='xs:string' nillable='false'/>" +
"<xs:element name='Description' type='xs:string' nillable='false'/>" +
"<xs:element name='DateStart' type='xs:dateTime' nillable='false'/>" +
"<xs:element name='DateEnd' type='xs:dateTime' nillable='true' minOccurs='0'/>" +
"<xs:element name='Url' type='xs:string' nillable='false' minOccurs='0'/>" +
"</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("LinkItem", ns);
}
System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema()
{
throw new NotImplementedException("IXmlSerializable.GetSchema() is not implemented. Use static GetSchema() instead.");
}
void IXmlSerializable.ReadXml(System.Xml.XmlReader reader)
{
LinkItem item = new LinkItem();
while (reader.IsStartElement())
{
reader.MoveToContent();
reader.Read();
if (reader.IsStartElement("Id"))
{
reader.MoveToContent();
item.Id = int.Parse(reader.ReadString());
reader.MoveToContent();
reader.ReadEndElement();
}
else
throw new XmlException("ExpectedElementMissing: Id element was expected.");
if (reader.IsStartElement("Title"))
{
reader.MoveToContent();
item.Title = reader.ReadString();
reader.MoveToContent();
reader.ReadEndElement();
}
else
throw new XmlException("ExpectedElementMissing: Title element was expected.");
if (reader.IsStartElement("Description"))
{
reader.MoveToContent();
item.Description = reader.ReadString();
reader.MoveToContent();
reader.ReadEndElement();
}
else
throw new XmlException("ExpectedElementMissing: Description element was expected.");
if (reader.IsStartElement("DateStart"))
{
reader.MoveToContent();
reader.Read();
item.DateStart = reader.ReadContentAsDateTime();
reader.MoveToContent();
reader.ReadEndElement();
}
else
throw new XmlException("ExpectedElementMissing: DateStart element was expected.");
if (reader.IsStartElement("DateEnd"))
{
reader.MoveToContent();
reader.Read();
item.DateEnd = reader.ReadContentAsDateTime();
reader.MoveToContent();
reader.ReadEndElement();
}
// optional
if (reader.IsStartElement("Url"))
{
reader.MoveToContent();
item.Url = reader.ReadString();
reader.MoveToContent();
reader.ReadEndElement();
}
else
throw new XmlException("ExpectedElementMissing: Url element was expected.");
reader.MoveToContent();
reader.ReadEndElement();
}
this.m_linkItem = item;
}
void IXmlSerializable.WriteXml(System.Xml.XmlWriter writer)
{
writer.WriteStartElement("Id", ns);
writer.WriteValue(m_linkItem.Id);
writer.WriteEndElement();
writer.WriteElementString("Title", ns, m_linkItem.Title);
writer.WriteElementString("Description", ns, m_linkItem.Description);
writer.WriteStartElement("DateStart", ns);
writer.WriteValue(m_linkItem.DateStart);
writer.WriteEndElement();
writer.WriteStartElement("DateEnd", ns);
writer.WriteValue(m_linkItem.DateEnd);
writer.WriteEndElement();
writer.WriteElementString("Url", ns, m_linkItem.Url);
}
}
}
下面详细讲解这段代码,首先对于自定义的序列化器一定要实现System.Xml.Serialization.IXmlSerializable接口,[XmlSchemaProvider("GetSchema")]用途是标示出提供Schema的对应方法名字,也就是说应该有一个GetSchema的方法与之对应。程序中通常要把LinkItem这个待序列化的Class定义为属性在序列化其中,以方便访问。对于IXmlSerializable这个接口,要实现下面3个方法:
XmlSchema GetSchema();
void ReadXml(XmlReader reader);
void WriteXml(XmlWriter writer);
实际应用中通常不处理GetSchema()这个方法,而是定义一个静态的方法GetSchema(XmlSchemaSet schemaSet)和[XmlSchemaProvider("GetSchema")]这个标签对应,上面代码的就是序列化的经典过程,其中GetSchema充当了生产XML结构的左右,Write和Read两个方法实现把对象数据写入xml中(序列化)和把xml中对象数据重新为给某对象(反序列化)。
注意此时,Service代码的参数也要同时改变。
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using ContentTypes;
namespace WcfServiceLibraryDemo
{
[ServiceContract(Name = "GigManagerServiceContract", Namespace = "http://www.cnblogs.com/Charlesliu", SessionMode = SessionMode.Required)]
public interface IGigManagerService
{
[OperationContract(Name = "SaveGig", Action = "http://www.cnblogs.com/Charlesliu/GigManagerServiceContract/SaveGig", ReplyAction = "http://www.cnblogs.com/Charlesliu/GigManagerServiceContract/SaveGigResponse")]
void SaveGig(LinkItemSerializer item);
[OperationContract(Name = "GetGig", Action = "http://www.cnblogs.com/Charlesliu/GigManagerServiceContract/GetGig", ReplyAction = "http://www.cnblogs.com/Charlesliu/GigManagerServiceContract/GetGigResponse")]
LinkItemSerializer GetGig();
}
}
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using ContentTypes;
namespace WcfServiceLibraryDemo
{
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class GigManagerService : IGigManagerService
{
private LinkItem m_linkItem;
#region IGigManager Members
public void SaveGig(LinkItemSerializer item)
{
m_linkItem = item.LinkItem;
}
public LinkItemSerializer GetGig()
{
return new LinkItemSerializer(m_linkItem);
}
#endregion
}
}
客户端的代码不变:
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using WinTest.MyServiceReference;
namespace WinTest
{
public partial class Form1 : Form
{
MyServiceReference.GigManagerServiceContractClient m_proxy = new WinTest.MyServiceReference.GigManagerServiceContractClient();
public Form1()
{
InitializeComponent();
}
private void cmdSave_Click(object sender, EventArgs e)
{
LinkItem item = new LinkItem();
item.Id = int.Parse(this.txtId.Text);
item.Title = this.txtTitle.Text;
item.Description = this.txtDescription.Text;
item.DateStart = this.dtpStart.Value;
item.DateEnd = this.dtpEnd.Value;
item.Url = this.txtUrl.Text;
m_proxy.SaveGig(item);
}
private void cmdGet_Click(object sender, EventArgs e)
{
LinkItem item = m_proxy.GetGig();
if (item != null)
{
this.txtId.Text = item.Id.ToString();
this.txtTitle.Text = item.Title;
this.txtDescription.Text = item.Description;
if (item.DateStart != DateTime.MinValue)
this.dtpStart.Value = item.DateStart;
if (item.DateEnd != DateTime.MinValue)
this.dtpEnd.Value = (DateTime)item.DateEnd;
this.txtUrl.Text = item.Url;
}
}
}
}
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:2.0.50727.4200
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace WinTest.MyServiceReference {
using System.Runtime.Serialization;
using System;
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "3.0.0.0")]
[System.Runtime.Serialization.DataContractAttribute(Name="LinkItem", Namespace="http://www.cnblogs.com/Charlesliu")]
[System.SerializableAttribute()]
public partial class LinkItem : object, System.Runtime.Serialization.IExtensibleDataObject, System.ComponentModel.INotifyPropertyChanged {
[System.NonSerializedAttribute()]
private System.Runtime.Serialization.ExtensionDataObject extensionDataField;
private long IdField;
private string TitleField;
private string DescriptionField;
private System.DateTime DateStartField;
[System.Runtime.Serialization.OptionalFieldAttribute()]
private System.Nullable<System.DateTime> DateEndField;
[System.Runtime.Serialization.OptionalFieldAttribute()]
private string UrlField;
[global::System.ComponentModel.BrowsableAttribute(false)]
public System.Runtime.Serialization.ExtensionDataObject ExtensionData {
get {
return this.extensionDataField;
}
set {
this.extensionDataField = value;
}
}
[System.Runtime.Serialization.DataMemberAttribute(IsRequired=true)]
public long Id {
get {
return this.IdField;
}
set {
if ((this.IdField.Equals(value) != true)) {
this.IdField = value;
this.RaisePropertyChanged("Id");
}
}
}
[System.Runtime.Serialization.DataMemberAttribute(IsRequired=true, EmitDefaultValue=false)]
public string Title {
get {
return this.TitleField;
}
set {
if ((object.ReferenceEquals(this.TitleField, value) != true)) {
this.TitleField = value;
this.RaisePropertyChanged("Title");
}
}
}
[System.Runtime.Serialization.DataMemberAttribute(IsRequired=true, EmitDefaultValue=false, Order=2)]
public string Description {
get {
return this.DescriptionField;
}
set {
if ((object.ReferenceEquals(this.DescriptionField, value) != true)) {
this.DescriptionField = value;
this.RaisePropertyChanged("Description");
}
}
}
[System.Runtime.Serialization.DataMemberAttribute(IsRequired=true, Order=3)]
public System.DateTime DateStart {
get {
return this.DateStartField;
}
set {
if ((this.DateStartField.Equals(value) != true)) {
this.DateStartField = value;
this.RaisePropertyChanged("DateStart");
}
}
}
[System.Runtime.Serialization.DataMemberAttribute(Order=4)]
public System.Nullable<System.DateTime> DateEnd {
get {
return this.DateEndField;
}
set {
if ((this.DateEndField.Equals(value) != true)) {
this.DateEndField = value;
this.RaisePropertyChanged("DateEnd");
}
}
}
[System.Runtime.Serialization.DataMemberAttribute(EmitDefaultValue=false, Order=5)]
public string Url {
get {
return this.UrlField;
}
set {
if ((object.ReferenceEquals(this.UrlField, value) != true)) {
this.UrlField = value;
this.RaisePropertyChanged("Url");
}
}
}
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName) {
System.ComponentModel.PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
if ((propertyChanged != null)) {
propertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
[System.ServiceModel.ServiceContractAttribute(Namespace="http://www.cnblogs.com/Charlesliu", ConfigurationName="MyServiceReference.GigManagerServiceContract", SessionMode=System.ServiceModel.SessionMode.Required)]
public interface GigManagerServiceContract {
[System.ServiceModel.OperationContractAttribute(Action="http://www.cnblogs.com/Charlesliu/GigManagerServiceContract/SaveGig", ReplyAction="http://www.cnblogs.com/Charlesliu/GigManagerServiceContract/SaveGigResponse")]
void SaveGig(WinTest.MyServiceReference.LinkItem item);
[System.ServiceModel.OperationContractAttribute(Action="http://www.cnblogs.com/Charlesliu/GigManagerServiceContract/GetGig", ReplyAction="http://www.cnblogs.com/Charlesliu/GigManagerServiceContract/GetGigResponse")]
WinTest.MyServiceReference.LinkItem GetGig();
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
public interface GigManagerServiceContractChannel : WinTest.MyServiceReference.GigManagerServiceContract, System.ServiceModel.IClientChannel {
}
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
public partial class GigManagerServiceContractClient : System.ServiceModel.ClientBase<WinTest.MyServiceReference.GigManagerServiceContract>, WinTest.MyServiceReference.GigManagerServiceContract {
public GigManagerServiceContractClient() {
}
public GigManagerServiceContractClient(string endpointConfigurationName) :
base(endpointConfigurationName) {
}
public GigManagerServiceContractClient(string endpointConfigurationName, string remoteAddress) :
base(endpointConfigurationName, remoteAddress) {
}
public GigManagerServiceContractClient(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) :
base(endpointConfigurationName, remoteAddress) {
}
public GigManagerServiceContractClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :
base(binding, remoteAddress) {
}
public void SaveGig(WinTest.MyServiceReference.LinkItem item) {
base.Channel.SaveGig(item);
}
public WinTest.MyServiceReference.LinkItem GetGig() {
return base.Channel.GetGig();
}
}
}
自定义序列化的结果是通过VS工程无法正常的引用WCF服务,所以要用命令svcutil.exe http://localhost:9000/才能得到Proxy文件,将这个文件copy到客户端工程中就可以使用了。同时要把自动生成的配置文件也copy进去。
自定义序列化的给程序员提供了更加灵活的方式,因为现实世界中总是有这样那样的问题,有些时候无法用理想中的WCF的DataContract来完成。当然了,灵活了程序员就费劲了,但是不可否认这种方式的意义所在。(完)自定义序列化的给程序员提供了更加灵活的方式,因为现实世界中总是有这样那样的问题,有些时候无法用理想中的WCF的DataContract来完成。当然了,灵活了程序员就费劲了,但是不可否认这种方式的意义所在。(完)
更正个错误,在Service Class 里的LInkItem应该定义成static。