WCF: 一些技巧






4.61/5 (57投票s)
使用 WCF 的一些技巧。
引言
我绝不是 WCF 专家,但我最近在工作中经常使用它,遇到了一些问题,我认为值得写成一篇“顶级技巧”风格的文章。
所以,本文将是这样的。
本文假定您对 WCF 有一定的了解,并且知道如何编辑配置文件。
在尝试运行代码之前,请务必阅读“运行代码”部分。
技巧 1:调试 WCF
您最想做的第一件事可能是调用 WCF 服务并进行调试。在我最初在公司创建 WCF 服务时,我创建了 IIS 托管的 Web 服务。虽然这可能是一个很好的最终解决方案,但它并不容易调试。因此,我的建议是创建一个基于控制台的 WCF 服务实现,并将其用于调试目的,然后当您满意时,可以将经过测试的代码推送到基于 IIS Web 的服务中。
附加的演示应用程序使用控制台托管(自托管)的 WCF 服务,其托管方式如下:
using System;
using System.ServiceModel;
namespace WcfServiceLibrary1
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("----------------------------" +
"-----------------------------------------");
Console.WriteLine("This is the WCF Service " +
"hosting within a console application");
Console.WriteLine("The service can also be hosted with a web browser");
Console.WriteLine("");
Console.WriteLine("Initializing WCF Service...");
// The service configuration is loaded from app.config
using (ServiceHost host = new ServiceHost(typeof(Service)))
{
host.Open();
Console.WriteLine("WCF Service is ready for requests." +
"Press any key to close the service.");
Console.WriteLine();
Console.Read();
Console.WriteLine("Closing service...");
}
}
}
}
这样就足以托管 WCF 服务了。
技巧 2:添加服务引用
如果我们考虑服务器端如何配置附加的演示应用程序 WCF 服务:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="InitialTransactionValue" value="12" />
</appSettings>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="MetadataBehavior">
<serviceMetadata httpGetEnabled="true"
httpGetUrl="https://:8001/ServiceHost/mex" />
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service behaviorConfiguration="MetadataBehavior"
name="WcfServiceLibrary1.Service">
<endpoint
address="service"
binding="netTcpBinding"
contract="WcfServiceLibrary1.IService"
name="TcpBinding" />
<endpoint
address="service"
binding="wsDualHttpBinding"
contract="WcfServiceLibrary1.IService"
name="HttpBinding" />
<endpoint
address="mex"
binding="mexHttpBinding"
contract="IMetadataExchange"
name="MexBinding"/>
<host>
<baseAddresses>
<add baseAddress="net.tcp://:8000/ServiceHost/" />
<add baseAddress="https://:8001/ServiceHost/" />
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
</configuration>
可以看到有三个可能的终结点:
- netTcpBinding
- HttpBinding
- MexBinding
以及两个可能的托管地址:
- net.tcp://:8000/ServiceHost/
- https://:8001/ServiceHost/
我的个人经验是,我试图使用“tcp”托管地址添加服务引用时,总是找不到正在运行的服务。我**总是**必须使用“http”托管地址才能找到正在运行的服务。如下所示:
因此,我尝试使用“tcp”托管地址时失败了。但如果我使用“http”托管地址,一切都很好。
所以我建议始终创建一个 HTTP 绑定,以便即使您最终不使用 HTTP 绑定,也能访问该服务。
修订
本文的一位读者 Benoît Dion 建议,可以通过修改服务端的 mexBinding 终结点来解决此问题。
这就是我在服务端的 `App.Config` 文件中进行的小小更改:
<!--<endpoint
address="mex"
binding="mexHttpBinding"
contract="IMetadataExchange"
name="MexBinding"/>-->
<endpoint
address="mex"
binding="mexTcpBinding"
contract="IMetadataExchange" />
我很高兴地通知您,现在您可以使用 TCP 地址 `net.tcp://:8000/ServiceHost/` 添加服务引用了。
感谢 Benoît。
技巧 3:服务配置
在附加的演示应用程序中,WCF 服务的定义如下所示:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.Net.Security;
namespace WcfServiceLibrary1
{
/// <summary>
/// The service contract
/// </summary>
[ServiceContract(
Name = "IService",
Namespace = "WcfServiceLibrary1.Service")]
public interface IService
{
[OperationContract]
[ReferencePreservingDataContractFormat]
List<Person> GetPeople();
[OperationContract]
List<Person> GetPeopleWithOutCicularReferencing();
}
//A simple DataContract serializable class
[DataContract]
public class Person
{
int age = 0;
string name = string.Empty;
List<Person> children = new List<Person>();
Person parent = null;
[DataMember]
public int Age
{
get { return age; }
set { age = value; }
}
[DataMember]
public string Name
{
get { return name; }
set { name = value; }
}
[DataMember]
public List<Person> Children
{
get { return children; }
set { children = value; }
}
[DataMember]
public Person Parent
{
get { return parent; }
set { parent = value; }
}
}
}
可以看到服务契约操作(方法)返回一个通用的 `List
要添加引用,必须确保您能够访问正在运行的 WCF 服务主机(即实际服务);在附加的演示中,这是控制台应用程序。
因此,只要您能够访问实际的 WCF 服务,就应该确保将服务配置为使用与服务契约接口中定义的那些操作契约相同的类型。如上所示,在这种情况下,它是一个通用的 `List
如果您没有服务引用并且正在添加它,则可以通过“添加服务引用”对话框中的“高级”按钮访问此“服务引用设置”对话框。
技巧 4:服务参数
当最终添加 WCF 服务时,发现一些默认参数不够大。下面是客户端的 `App.Config` 的修改前后对比:
所以我更改了其中一些参数以提高服务吞吐量。
技巧 5:循环引用
在我们正在做的事情中,我需要有一个循环引用;你知道,对象 A 引用对象 B,对象 B 引用对象 A。
发现在默认的 `DataContractSerializer` 无法通过配置文件设置为处理序列化循环对象图,尽管它完全能够胜任这项任务。似乎 `DataContractSerializer` 的某些属性只能通过构造函数调用设置,而不能通过配置文件设置。所以我对此进行了一些研究,发现了一些有趣的代码,允许您创建一个专门的 `DataContractSerializer`,它可以序列化循环对象图。我找到的代码允许您在服务接口的操作契约上添加一个名为 `ReferencePreservingDataContractFormatAttribute` 的自定义属性。
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Runtime.Serialization;
using System.ServiceModel.Description;
namespace WcfServiceLibrary1
{
/// <summary>
/// Allows us to adorn the IService contract with this
/// attribute to indicate that a specialized DataContractSerializer
/// should be used that has preserveObjectReferences set true
/// </summary>
public class ReferencePreservingDataContractFormatAttribute
: Attribute, IOperationBehavior
{
#region IOperationBehavior Members
public void AddBindingParameters(OperationDescription description,
BindingParameterCollection parameters)
{
}
public void ApplyClientBehavior(OperationDescription description,
System.ServiceModel.Dispatcher.ClientOperation proxy)
{
IOperationBehavior innerBehavior =
new ReferencePreservingDataContractSerializerOperationBehavior(description);
innerBehavior.ApplyClientBehavior(description, proxy);
}
public void ApplyDispatchBehavior(OperationDescription description,
System.ServiceModel.Dispatcher.DispatchOperation dispatch)
{
IOperationBehavior innerBehavior =
new ReferencePreservingDataContractSerializerOperationBehavior(description);
innerBehavior.ApplyDispatchBehavior(description, dispatch);
}
public void Validate(OperationDescription description)
{
}
#endregion
}
}
这反过来会创建一个新的 `ReferencePreservingDataContractSerializerOperationBehavior` 对象,它继承自 `IOperationBehavior`。正是这个类负责创建实际的 `DataContractSerializer`,它允许序列化循环对象图。该类的代码如下:
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Runtime.Serialization;
using System.ServiceModel.Description;
namespace WcfServiceLibrary1
{
/// <summary>
/// A specialized DataContractSerializer that has
/// preserveObjectReferences set true, which allows for
/// circular references to be serialized
/// </summary>
public class ReferencePreservingDataContractSerializerOperationBehavior :
DataContractSerializerOperationBehavior
{
#region Ctor
public ReferencePreservingDataContractSerializerOperationBehavior(
OperationDescription operationDescription)
: base(operationDescription) { }
#endregion
#region Public Methods
public override XmlObjectSerializer CreateSerializer(Type type,
XmlDictionaryString name, XmlDictionaryString ns,
IList<Type> knownTypes)
{
return new DataContractSerializer(type, name, ns, knownTypes,
2147483646 /*maxItemsInObjectGraph*/,
false/*ignoreExtensionDataObject*/,
true/*preserveObjectReferences*/,
null/*dataContractSurrogate*/);
}
#endregion
}
}
重要部分是 `DataContractSerializer` 的构造函数,我们在其中设置 `preserveObjectReferences` 值。这是至关重要的一部分。
如果我们看到附加演示应用程序中的示例屏幕截图,其中适用于以下情况,这个类的重要性就会更加清楚:
- 第一次 WCF 调用使用 `ReferencePreservingDataContractFormatAttribute`,因此可以安全地返回循环引用。
- 第二次 WCF 调用使用标准的 `DataContractSerializer`,因此会导致通信异常。
让我们回顾一下服务契约的样子:
/// <summary>
/// The service contract
/// </summary>
[ServiceContract(
Name = "IService",
Namespace = "WcfServiceLibrary1.Service")]
public interface IService
{
[OperationContract]
[ReferencePreservingDataContractFormat]
List<Person> GetPeople();
[OperationContract]
List<Person> GetPeopleWithOutCicularReferencing();
}
我们可以看到第一次调用工作正常,因为我们使用了 `ReferencePreservingDataContractFormatAttribute`,所以可以安全地返回循环引用。
但下一次调用完全失败了,导致了通信异常。这是因为标准的 `DataContractSerializer` 没有开启 `preserveObjectReferences` 参数。
对于创建 `ReferencePreservingDataContractFormatAttribute` 或 `ReferencePreservingDataContractSerializerOperationBehavior` 代码,我概不负责。这是 Sowmy Srinivasan 的功劳,我在他的博客上找到了它。我完全理解它的含义,希望您现在也理解了。您可以在 Sowmy Srinivasan 的博客 上阅读更多内容。
总结
我完全意识到这些技巧可能不适用于所有人的 WCF 服务,但它们是我发现有助于我自己的代码的一些东西;因此,我认为值得分享我的发现。希望其中至少有一个技巧对您有用。
运行代码
您需要更改 `ServiceClientTestApp` 项目中的 `App.Config` 文件,以使用您自己的 PC 用户。因此,您需要更改以下行:
<userPrincipalName value="XXXXXX" />
改为类似:
<userPrincipalName value="YOUR_PC_NAME\YOUR_USER"/>
如果更改了这一点,`ServiceClientTestApp` 项目将无法工作。
我们完成了
如果您喜欢这篇文章并且觉得它很有用,请留下投票/评论。