准备技术:
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:
Code
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:
Code
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;
}
}
}
在客户端我们调用:
Code
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特性应用到基接口上,因为客服端导出的元数据是不能包含接口本身的。
[代码下载]