代码改变世界

WCF 第二章 契约 在WSDL中使用KnownType暴露额外类型

2011-06-23 21:44  DanielWise  阅读(2465)  评论(7编辑  收藏  举报
如果数据类型满足任何先前描述的条件,那么它们会在WSDL中暴露出来。有一些额外的可能,当然,你也可能想强制一个类型包含在WSDL契约中。
  举一个类继承的例子。如果一个序列化的派生类到达一个期待得到一个序列化的基类的终结点时,WCF不会知道如何反序列化这个派生类因为它不是契约的一部分。另外一个例子是一个hashtable 类,存储了其他的类作为自己的元素。WSDl将会定义hashtable类,但是不是那么存储在hashtable内部的类。
  在这些情况下,你必须告诉WCF这些应该显示包含在WSDL契约中的类。这是使用KnownTypes完成的。它可以在四种方式下完成:通过添加一个KnownType属性到[DataContract],通过在[ServiceContract]或者[OperationContract]的属性,通过在配置文件中添加一个引用给它以及它的程序集,或者通过在生成WSDL时定义它。
  列表2.20显示了定义基类的数据契约,Price,有两个类派生自基类,StockPrice和MetalPrice.注意在数据契约上的[KnownType]属性。这告诉WCF当暴露契约时要包含StockPrice和MetalPrice的XSD表示。这个列表也包含了服务实现。GetPrice操作是多态的,它的返回值类型可以是StockPrice或者MetalPrice,取决于哪一个操作被请求。通过代理调用GetPrice的客户端代码必须把结构强制转换成需要的类型来访问返回值类。

列表2.20 数据契约中定义的KnownType
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
using System.ServiceModel;

namespace EssentialWCF
{
[DataContract(Namespace
="http://EssentialWCF/")]
[KnownType(
typeof(StockPrice))]
[KnownType(
typeof(MetalPrice))]
public class Price
{
[DataMember]
public double CurrentPrice;
[DataMember]
public DateTime CurrentTime;
[DataMember]
public string Currency;
}

[DataContract(Namespace
="http://EssentialWCF/")]
public class StockPrice : Price
{
[DataMember]
public string Ticker;
[DataMember]
public long DailyVolume;
}

[DataContract(Namespace
="http://EssentialWCF/")]
public class MetalPrice : Price
{
[DataMember]
public string Meta;
[DataMember]
public string Quality;
}

[ServiceBehavior(Namespace
="http://EssentialWCF/FinanceService/")]
[ServiceContract(Namespace
="http://EssentialWCF/FinanceService/")]
public class StockService
{
[OperationContract]
public Price GetPrice(string id, string type)
{
if (type.Contains("Stock"))
{
StockPrice s
= new StockPrice();
s.Ticker
= id;
s.DailyVolume
= 45000000;
s.CurrentPrice
= 94.15;
s.CurrentTime
= DateTime.Now;
s.Currency
= "RMB";
return s;
}
if(type.Contains("Metal"))
{
MetalPrice g
= new MetalPrice();
g.Meta
= id;
g.Quality
= "0..999";
g.CurrentPrice
= 785.00;
g.CurrentTime
= DateTime.Now;
g.Currency
= "RMB";
return g;
}
return new Price();
}
}
}
  相应的,你可以使用[ServiceKnownType]属性在OperationContract层次定义KnownType.当KnownTypes被定义在操作层时,派生类只可以在定义了已知类型的操作中使用。换句话说,并不是一个服务中的所有操作都可以使用派生类。列表2.21显示了使用[ServiceKnownType]的代码片段。在这个例子中,当消息从服务端返回时一个客户端可以调用GetPrice方法,反序列化器将创建一个StockPrice或者MetalPrice对象。但是当客户端调用SetPrice时只可以传递一个Price对象,而不是一个StockPrice或者MetalPrice对象,因为序列化器不知道在XML中如何表达这些派生类型。
 
列表2.21 操作契约中定义的KnownType
[ServiceBehavior(Namespace="http://EssentialWCF/FinanceService/")]
[ServiceContract(Namespace
="http://EssentialWCF/FinanceService/")]
public class StockService
{
[ServiceKnownType(
typeof(StockService))]
[ServiceKnownType(
typeof(MetalPrice))]
[OperationContract]
public Price GetPrice(string id, string type)
{
if (type.Contains("Stock"))
{
StockPrice s
= new StockPrice();
s.Ticker
= id;
s.DailyVolume
= 45000000;
s.CurrentPrice
= 94.15;
s.CurrentTime
= DateTime.Now;
s.Currency
= "RMB";
return s;
}
if(type.Contains("Metal"))
{
MetalPrice g
= new MetalPrice();
g.Meta
= id;
g.Quality
= "0..999";
g.CurrentPrice
= 785.00;
g.CurrentTime
= DateTime.Now;
g.Currency
= "RMB";
return g;
}
return new Price();
}
}
无论是在数据契约层还是服务契约层,在代码中定义已知类型的劣势在于你需要在编译时就知道转化的派生类。如果一个新类型被添加,你需要重新编译代码。这可以使用两种方法来解决。
  首先,你可以把已经类型引用从代码中挪到配置文件中去,在服务配置文件中的system.runtime.serialization部分加入已经类型信息。考虑到类继承关系,你需要添加引用到基类并添加KnownType引用到派生类。这在列表2.22中显示,当EssentialWCF.Price是基类而且EssentialWCF.StockPrice和EssentialWCF.MetalPrice是派生类时,StockService是寄宿这些类型的DLL.
 
列表2.22 配置文件中定义的KnownType
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.runtime.serialization>
<dataContractSerializer>
<declaredTypes>
<add type="EssentialWCF.Price,
StockService, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=null"></add>
<add type="EssentialWCF.StockPrice,
StockService, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=null"></add>
<add type="EssentialWCF.MetalPrice, StockService,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"></add>
</declaredTypes>
</dataContractSerializer>
</system.runtime.serialization>
</configuration>
  在契约中确定派生类性的最普通方法就是在运行时生成它。这可以通过WCF暴露的钩子来完成。[KnownType]和[ServiceKnownType]的两个构造函数都接受字符串参数。这个字符串是在序列化或者反序列化时返回一系列已知类型的方法名。如果你使用一个原数据容器,你可以在容器或者数据库中寻找类型信息并在运行时暴露它们。列表2.23显示了一个简单的实现,显示了类型名被硬编码到GetKnownTypes方法中而不是从外部容器被拖入。
列表2.23 运行时代码中定义的KnownType
[DataContract(Namespace="http://EssentialWCF/")]
[KnownType(
typeof(StockPrice))]
[KnownType(
typeof(MetalPrice))]
public class Price
{
[DataMember]
public double CurrentPrice;
[DataMember]
public DateTime CurrentTime;
[DataMember]
public string Currency;

static Type[] GetKnownTypes()
{
return new Type[] { typeof(StockPrice), typeof(MetalPrice)};
}
}

[DataContract(Namespace
="http://EssentialWCF/")]
public class StockPrice : Price
{
[DataMember]
public string Ticker;
[DataMember]
public long DailyVolume;
}

[DataContract(Namespace
="http://EssentialWCF/")]
public class MetalPrice : Price
{
[DataMember]
public string Meta;
[DataMember]
public string Quality;
}