一个示例 WCF 数据代理实现





5.00/5 (2投票s)
使用 WCF 数据合同代理的完整示例
引言
本文将展示如何修改使用 DataContractSerializer
的选定类型的序列化方法。可下载的源代码包含一个完整的代理实现和使用示例。该示例代理使用 BinaryFormatter
序列化选定的类。例如,如果您想通过 WCF 传输更大的对象,并且不想更改序列化器,您可以使用此 BinarySurrogate
来减小 XML 的大小。这只是一个示例实现,如果您不想使用 BinaryFormatter,可以很容易地更改为使用其他序列化方式。(例如,protobuf、自定义序列化器)。
背景
了解数据契约代理的基本知识非常重要。请先阅读 MSDN。
该示例包含两个类库项目,一个 WCF 服务应用程序,一个控制台客户端。您应该能够生成并运行这些项目,并配置 WCF 消息日志,以便检查代理的结果。
使用代码
解决方案包含四个项目
- WcfSurrogateTutorial.Surrogate:这是一个类库项目。仅包含进行代理的类和接口,没有任何业务逻辑的具体知识。
- WcfSurrogateTutorial.Common:这是一个类库,包含“业务逻辑”对象和其他通用类。例如,类型提供程序。
- WcfSurrogateTutorial.TestServer:这是一个 WCF 服务库项目,包含返回“业务逻辑”对象的示例服务协定。
- WcfSurrogateTutorial.TestClient:这是一个控制台应用程序,包含对 TestServer 的服务引用。
使用数据契约代理,您可以在序列化过程中更改类的类型。我们将手动序列化对象,并将序列化后的字符串存储在一个通用类中。首先,我们创建这个类,它将替代所有选定的类型。这就是 BinaryStringContainer
,它只有一个属性,即 BinaryString
。每个被替代的类都将使用二进制格式化程序进行序列化,并将结果字符串存储在此属性中。然后,WCF 将使用 DataContractSerializer
序列化这个类,而无需使用 DataContractSerializer
序列化完整的对象层次结构。
[DataContract]
public class BinaryStringContainer
{
private string binaryString = null;
[DataMember]
public string BinaryString
{
get
{
return binaryString;
}
set
{
if (value == null)
{
throw new ArgumentNullException("BinaryString");
}
binaryString = value;
}
}
}
编写自己的代理的第一步是实现 IDataContractSurrogate
接口。此接口包含三个对我们很重要的重要方法:
- GetDataContractType:获取当前类型并返回序列化后的类型。在我们的例子中,它会返回
BinaryStringContainer
类型,用于所有我们想要使用BinaryFormatter
进行序列化的类型。对于其他类型,它返回它接收到的类型。 - GetObjectToSerialize:获取对象并将其转换为序列化后的类型。如果序列化后的类型是
BinaryStringContainer
,它将使用BinaryFormatter
序列化对象,并返回一个带有序列化字符串的新实例。 - GetDeserializedObject:获取序列化后的对象并转换回。如果序列化后的对象是
BinaryStringContainer
,它将使用BinaryFormatter
反序列化对象并返回原始对象。
我们不需要实现接口的其他方法。如果其中任何一个被调用,我们将抛出不支持的异常。我们的 BinarySurrogate
类包含此实现。
public Type GetDataContractType(Type type)
{
if (surrogatedTypes.Contains(type))
{
return typeof(BinaryStringContainer);
}
else
{
return type;
}
}
public object GetObjectToSerialize(object obj, Type targetType)
{
if (targetType == typeof(BinaryStringContainer))
{
BinaryStringContainer bs = surrogateSerializer.ConvertToSurrogate(obj);
return bs;
}
else
{
return obj;
}
}
public object GetDeserializedObject(object obj, Type targetType)
{
BinaryStringContainer bs = obj as BinaryStringContainer;
if (bs != null)
{
object ret = surrogateSerializer.ConvertFromSurrogate(bs);
return ret;
}
else
{
return obj;
}
}
为了使此实现更灵活,此 BinarySurrogate
通过构造函数使用两个依赖项进行配置。IBinarySerializedTypeProvider
仅包含一个方法,该方法返回我们想要代理的类型。ISurrogateConverter
接口是对序列化的抽象。它的两个方法用于将对象转换为 BinaryStringContainer
和反之。我们实现了一个 BinarySurrogateConverter
类,它使用 BinaryFormatter
将对象序列化为字符串。这意味着所有被代理的类现在都应该被标记为 Serializable
。
public class BinarySurrogateConverter : ISurrogateConverter
{
public BinaryStringContainer ConvertToSurrogate(object obj)
{
BinaryStringContainer ret = new BinaryStringContainer();
if (obj == null)
{
return ret;
}
using (MemoryStream ms = new MemoryStream())
{
IFormatter formatter = new BinaryFormatter();
formatter.Serialize(ms, obj);
var ba = ms.ToArray();
ret.BinaryString = Convert.ToBase64String(ba);
}
return ret;
}
public object ConvertFromSurrogate(BinaryStringContainer bs)
{
if (bs.BinaryString == null)
{
return null;
}
object ret;
byte[] ba = Convert.FromBase64String(bs.BinaryString);
using (MemoryStream ms = new MemoryStream(ba))
{
IFormatter formatter = new BinaryFormatter();
ret = formatter.Deserialize(ms);
}
return ret;
}
类型提供程序接口在 Surrogate 项目中没有实现,因为它应该知道具体的类。因此,我们在 Common 项目中创建了一个类型转换器提供程序类。正如您所见,它只返回 SurrogatedData
类型,我们只想代理这个类型,而保持其他类型不变。
public class TestSurrogateTypeConverter : IBinarySerializedTypeProvider
{
private static readonly List<Type> types = new List<Type> { typeof(SurrogatedData) };
public IEnumerable<Type> GetBinarySerializableTypes()
{
return types;
}
}
下一步是在您的终结点应用此代理。我们创建了一个可以附加到 ServiceContracts 接口的 Attribute 类。此 Attribute 遍历所有 Operation,通过设置 DataContractSerializerOperationBehavior
类的 DataContractSurrogate
属性来应用代理。
foreach (OperationDescription od in contract.Operations)
{
DataContractSerializerOperationBehavior dataContractBehavior = od.Behaviors.Find<DataContractSerializerOperationBehavior>() as DataContractSerializerOperationBehavior;
if (dataContractBehavior != null)
{
dataContractBehavior.DataContractSurrogate = new BinarySurrogate(typeProvider, surrogateConverter);
}
else
{
dataContractBehavior = new DataContractSerializerOperationBehavior(od);
dataContractBehavior.DataContractSurrogate = new BinarySurrogate(typeProvider, surrogateConverter);
od.Behaviors.Add(dataContractBehavior);
}
}
在服务器端,您只需要将此 Attribute 应用于 ISurrogateTestService
服务协定接口。服务器会将 SurrogatedData
类序列化为 BinaryStringContainer
类型。
[ServiceContract]
[UseBinarySurrogateBehavior(typeof(TestSurrogateTypeConverter), typeof(BinarySurrogateConverter))]
public interface ISurrogateTestService
{
[OperationContract]
SurrogatedData GetSurrogatedData();
[OperationContract]
NonSurrogatedData GetNonSurrogatedData();
}
在客户端,我们应该应用此代理类以正确反序列化对象。为了反序列化对象,您应该在客户端程序集中拥有具体类型的引用。如果您使用 Visual Studio 生成代理类,您应该检查该程序集的重用选项。否则,BinaryFormatter
反序列化将失败,因为对象定义在未引用的程序集中。在客户端,您不能使用 Attribute,因此必须手动应用代理。但方法与服务器端相同。请参阅 ApplyDataContractSurrogate
方法。
现在您可以启动 WCF 服务并运行客户端控制台应用程序。控制台应用程序首先获取代理数据,然后获取未代理的数据。这两种数据都应该正常工作,代理是透明的。如果您想检查差异,应该记录 WCF 消息。
代理响应仅包含 BinaryString
,它是对象的 base64 编码字符串表示。
<MessageLogTraceRecord Time="2016-03-31T17:02:49.8664839+02:00" Source="TransportSend" Type="System.ServiceModel.Dispatcher.OperationFormatter+OperationFormatterMessage" xmlns="http://schemas.microsoft.com/2004/06/ServiceModel/Management/MessageTrace">
<Addressing>
<Action>http://tempuri.org/ISurrogateTestService/GetSurrogatedDataResponse</Action>
</Addressing>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<GetSurrogatedDataResponse xmlns="http://tempuri.org/">
<GetSurrogatedDataResult xmlns:a="http://schemas.datacontract.org/2004/07/WcfSurrogateTutorial.Surrogate" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<a:BinaryString>AAEAAAD/////AQAAAAAAAAAMAgAAAFJXY2ZTdXJyb2dhdGVUdXRvcmlhbC5Db21tb24sIFZlcnNpb249MS4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1udWxsBQEAAAAuV2NmU3Vycm9nYXRlVHV0b3JpYWwuVGVzdF...</a:BinaryString>
</GetSurrogatedDataResult>
</GetSurrogatedDataResponse>
</s:Body>
</s:Envelope>
</MessageLogTraceRecord>
未代理的对象看起来就像其他任何正常的 DataContract 序列化对象。它包含所有属性和列表项作为 XML 元素。
<MessageLogTraceRecord Time="2016-03-31T17:02:50.2063213+02:00" Source="TransportSend" Type="System.ServiceModel.Dispatcher.OperationFormatter+OperationFormatterMessage" xmlns="http://schemas.microsoft.com/2004/06/ServiceModel/Management/MessageTrace">
<Addressing>
<Action>http://tempuri.org/ISurrogateTestService/GetNonSurrogatedDataResponse</Action>
</Addressing>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<GetNonSurrogatedDataResponse xmlns="http://tempuri.org/">
<GetNonSurrogatedDataResult xmlns:a="http://schemas.datacontract.org/2004/07/WcfSurrogateTutorial.TestServer" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<a:Id>8669505</a:Id>
<a:ListValue xmlns:b="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<b:string>7568d8e1-6431-4f39-9238-a5948a3bf9e0</b:string>
<b:string>7a53e6e1-fbb7-4ef5-bd6c-aa89c220dd8a</b:string>
<b:string>a82de840-3dff-4586-8cb3-c338d68fa782</b:string>
.
.
.
<b:string>aa2252dd-4952-463c-ac33-973f46f92f90</b:string>
</a:ListValue>
<a:StringValue>Non surrogated data</a:StringValue>
</GetNonSurrogatedDataResult>
</GetNonSurrogatedDataResponse>
</s:Body>
</s:Envelope>
</MessageLogTraceRecord>
关注点
在我们的项目中,我们使用这种代理来提高序列化性能。我们不想改变 WCF 序列化器,但我们希望更快地序列化大型对象层次结构。我们为不同的类型使用 BinaryFormatter
、protobuf 和自定义序列化器。
历史
在此处保持您所做的任何更改或改进的实时更新。