准備技術:
1.C#基礎知識
2.了解WCF基礎知識
在正常的c#開發中我們是允許用子類去替換基類的,這也是所謂的替換原則。但是我們在WCF中確不能用數據契約的子類來替換父類的,因為這中間存在一個序列化的問題。舉個例子:
我們有數據契約:
[DataContract] class Employee{...}
服務契約中:
[ServiceContract] interface IEmployeeManager { [OperationContract] void AddEmployee(Employee employee); }
然後我們在客戶端的代理中就算有類:Intern繼承於
[DataContract] class Intern:Employee{...}
然後再客戶端調用時:
proxy.AddEmployee(new Intern())是會出錯的。因為在服務器端無法識別Intern對象,因為他無法去反序列化Intern成Employee對象(WCF序列化)。
WCF提供給我們了一個解決的辦法就是使用KnownTypeAttribute特性,在基類上標識對應的子類就可以了。KnownTypeAttribute特性可以使用在Struct跟Class上。示例:
[DataContract] [KnownType(typeof(Customer))] class Employee{...} [DataContract] class Intern:Employee{...}
這樣我們就在所有的契約跟操作上,能跨越所有的服務和終結點,允許服務接受子類。但是這樣會遇到一個問題,我們不能去特定的指定某一個服務操作,所以KnownType的缺陷就是范圍過於廣泛。WCF提供了另外一個Attribute--ServiceKnownType.
ServiceKnownType 特性
KnownType只能應用在數據契約的基類上,而ServiceKnownType可以在Interface、Method、Class上標識。看一個示例Employee.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.Runtime.Serialization;
namespace HenllyeeDataContract
{
[DataContract]
public class Employee
{
#region Fields
private string _name;
private int _age;
#endregion
#region Properties
/// <summary>
/// The employee's name
/// </summary>
[DataMember(Order=0)]
public string Name
{
get
{
return this._name;
}
set
{
this._name = value;
}
}
/// <summary>
/// The employee's age
/// </summary>
[DataMember(Order=1)]
public int Age
{
get
{
return this._age;
}
set
{
this._age = value;
}
}
#endregion
}
[DataContract]
public class Intern : Employee
{
private int _internship;
/// <summary>
/// The intern's working days
/// </summary>
[DataMember]
public int Internship
{
get
{
return this._internship;
}
set
{
this._internship = value;
}
}
}
}
在數據契約中我們並沒有去指定KnownType,我們在服務契約的操作上去標識ServiceKnownType特性,EmployeeManage.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.Runtime.Serialization;
namespace HenllyeeServiceContract
{
[ServiceContract]
public interface IEmployeeManage
{
[OperationContract]
[ServiceKnownType(typeof(HenllyeeDataContract.Intern))]
void AddEmployee(HenllyeeDataContract.Employee emlpoyee);
[OperationContract]
[ServiceKnownType(typeof(HenllyeeDataContract.Intern))]
HenllyeeDataContract.Employee GetEmployee();
}
[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]
public class EmployeeManage : IEmployeeManage
{
private HenllyeeDataContract.Employee _employee;
/// <summary>
/// Set employee
/// </summary>
/// <param name="emlpoyee">the employee's object</param>
public void AddEmployee(HenllyeeDataContract.Employee emlpoyee)
{
this._employee = emlpoyee;
}
/// <summary>
/// Get a employee
/// </summary>
/// <returns></returns>
public HenllyeeDataContract.Employee GetEmployee()
{
return this._employee;
}
}
}
在客戶端我們調用:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Client
{
class Program
{
static void Main(string[] args)
{
EmployeeService.EmployeeManageClient proxy = new Client.EmployeeService.EmployeeManageClient();
EmployeeService.Intern intern = new Client.EmployeeService.Intern();
intern.Age = 22;
intern.Name = "Henllyee Cui";
intern.Internship = 120;
proxy.AddEmployee(intern);
EmployeeService.Employee internOut = proxy.GetEmployee();
Console.Write("The Employee Name:{0}\nAge:{1}\n",
internOut.Name,
internOut.Age
);
Console.Read();
}
}
}
運行後:
ServiceKnownType特性也可以表示在數據契約的類上,那麼就會應用到整個數據契約中操作上,如:
[ServiceContract]
[ServiceDataContract(typeof(HenllyeeDataContract.Intern))]
public interface IEmployeeManage{...}
那麼IEmployeeManage服務契約跟其所有的操作都可以接受Intern這個子類.
已知類型與接口
數據契約DataContract只能標識在class 或者struct上,但是數據契約的基類可以是接口,但是我們在服務契約的時候要去用ServiceKnownType特性去指定確切的數據類型。如:
interface IEmployee{...} [DataContract] class Intern:IEmployee{...}
服務契約中:
[ServiceContract] [ServiceKnownType(typeof(Intern))] interface IEmployeeManage { [OperationContract] void AdddEmployee(IEmployee employee); }
要注意的一點就是我們不能把KnownType特性應用到基接口上,因為客服端導出的元數據是不能包含接口本身的。