WCF 中的常见问题






4.69/5 (27投票s)
在项目中实现 WCF 时遇到的常见问题。
引言
当我和我的团队在项目中实现 WCF 时(最初它并非基于 SOA),我们遇到了一些挑战,这非常考验我们的耐心。我们不得不深入挖掘,解决遇到的每一个问题。因此,我决定写这篇文章,希望能帮助到那些在项目中实现 WCF 的人。本文包含解决 WCF 实现过程中常见问题的方案。
背景
本文并非关于 WCF 实现的基本指南,而是帮助解决团队在项目中实现 WCF 时遇到的问题。本文不会教你 WCF 的基础知识,相反,它假设你已经熟悉 WCF 基础,并准备好将你的 WCF 知识提升到更高的水平。
使用代码
在解释问题解决方案时,我会包含代码片段。你可以根据需要修改你的 WCF 应用程序。
1. 只读简写属性声明
当使用带有 'DataMember
' 属性的只读简写属性声明时,请确保同时包含 getter 和 setter 部分。只读属性声明(没有 setter,就像我们平常做的那样)将无法正常工作。数据成员将无法被客户端访问。
一个替代方法是定义一个带有空花括号的 set 块。请注意,用户可以使用 '=' 运算符为这类属性赋值,但这不会影响其值。
//Data Contract attribute applied to class to make it available to client.
//Data Member attribute applied to the 'MyVar' readonly property.
[DataContract]
public class MyValueObject
{
[DataMember]
public int MyVar
{
get;
//This will not work, as WCF needs both getter and setter to access property.
}
}
//Correct version with both getters and setter defined.
[DataContract]
public class MyValueObject
{
[DataMember]
public int MyVar
{
get;
set {}
//Empty setter block is supplied for readonly property.
}
}
2. 未由任何服务协定返回的值对象
通常,我们将项目使用的所有值对象定义在一个命名空间下。客户端可以通过服务引用访问此命名空间。请注意,我们不能直接将此命名空间的引用添加到客户端,因为它会产生冲突(并且每次我们都必须使用其完整的名称,这很不方便),因为相同的值对象也将通过服务引用可用。
但是有一个陷阱,客户端只能通过服务引用获取那些由至少一个操作协定返回的值对象。那么,如果客户端想要使用某个没有服务协定返回的值对象怎么办?将此类值对象作为已知类型添加到任何服务协定上,最好是第一个服务协定(一个项目中可以有多个服务协定)。
//Value object does not return by any operation contract
[DataContract]
public class MyNonReturnVO
{
[DataMember]
public int MyVar
{
get;
set;
}
}
//Value object return by an operation contract
[DataContract]
public class MyReturnVO
{
[DataMember]
public int MyProp
{
get;
set;
}
}
//Adding MyNonReturnVO as KnownType to make it avaialble to the client
[ServiceContract]
[KnownType(TypeOf(MyNonReturnVO))]
interface IMyInterface
{
[OperaionContract]
MyReturnVO getMyVO();
}
在上面的示例中,定义了两个值对象:MyReturnVO
,它由 getMyVO()
操作协定返回;以及 MyNonReturnVO
,它没有由任何操作协定返回,所以我们将其添加到了 KnowType
属性中。现在 MyNonReturnVO
也对客户端可用了。
3. WCF 服务中的 out 参数
如果任何操作协定方法只有一个 out 参数,并且返回类型为 void,则协定会将该参数转换为该协定的返回类型。
如果操作协定方法具有 void 以外的其他返回类型,则 out 参数必须是方法签名中的第一个参数。
//Original method with void return type and single out parameter
[OperationContract]
void MyMethod(int myId, out string myName);
//This method will end up in service reference as
string MyMethod(int myId);
//Method with other return type than void and out parameter
[OperationContract]
string MyMethod(out string myFirstName, int myId);
//Out must be first parameter
WCF 操作协定方法不能有超过一个 out 参数。out 参数必须是方法签名中的第一个参数。
4. WCF 中的方法重载
WCF 通过名称唯一地识别每个具有操作协定的方法。在方法重载的情况下,我们为方法提供相同的名称。那么如何在客户端使用重载方法呢?
这个问题的解决方案非常简单,就是使用别名。我们可以在定义操作协定时为方法提供别名,方法将通过其别名在客户端可用,而不是原始名称。当然,每个重载方法的别名都应该是不同的。是的,我甚至可以看到,我们在这里失去了非常独特的方法重载功能!!!
[ServiceContract]
interface IMyCalculator
{
//Providing alias AddInt, to avoid naming conflict at Service Reference
[OperationContract(Name = "AddInt")]
public int Add(int numOne, int numTwo);
//Providing alias AddDobule, to avoid naming conflict at Service Reference
[OperationContract(Name = "AddDouble")]
public double Add(int numOne, double numTwo);
}
在上面的示例中,这些方法可以通过其别名在客户端访问;分别是 AddInt
和 AddDouble
。
5. 向客户端公开枚举
这可以通过多种方式实现:
- 将
[DataContract]
和[EnumMember]
属性应用到枚举类型上,就像你对值对象类应用一样,然后将此enum
作为knowntypes
添加到类中。[DataContract] public class MyClass { [DataMember] public string { get; set; } //Enum as datamember [DataMember] public MyEnum condition; } //Applying DataContract attribute to enum itself [DataContract] public enum MyEnum { //Applying EnumMember attribute to each enum attribute [EnumMember] First, [EnumMember] Second, [EnumMember] Third }
在上面的示例中,
DataContract
和EnumMember
属性已应用于MyEnum
枚举。现在它对客户端可用了。 - 其次,我们还可以将
[Flags]
属性与[DataContract]
一起应用,这将允许你同时发送和接收enum
值。[DataContract] public class Car { [DataMember] public string { get; set; } [DataMember] public MyEnum condition; } //Applying Flags attribute to enum [DataContract] [Flags] public enum MyEnum { [EnumMember] First, [EnumMember] Second, [EnumMember] Third }
在上面的示例中,我已将 Flags 属性与
DataContract
一起应用于enum
原型,这使得可以同时发送或接收零个或多个枚举值的列表。
6. 将集合转换为数组或列表
这是一个相对小的问题,但有时可能会出现。问题在于,从服务接收到的任何集合(Lists, ArrayLists 等)都应该在客户端表示为 Array 或 List。这个选项在创建服务引用时出现。在创建服务引用时,你可以从可用的下拉选项中选择。
7. 处理异常
异常无法原封不动地从服务传递到客户端,因为异常是 .NET 特有的,而 WCF 的主要设计原则之一就是互操作性。因此,WCF 提供了另一种在服务上传递运行时异常的机制,即 FaultContracts
。
在讨论 FaultException
之前,让我先解释一下在 Fault 中包含异常详细信息所需的前期设置。
<behaviors>
<serviceBehaviors>
<behavior>
<serviceDebug includeExceptionDetailInFaults="true">
</behavior>
</serviceBehaviors>
</behaviors>
在服务行为中添加此设置,以便在客户端获取准确的异常详细信息。
现在,让我们回到 FaultContracts
。
首先,你需要定义一个类来保存所有异常详细信息。
[DataContract]
public class FaultHandle
{
FaultHandle(int id, int enumType)
{
faultId = id;
faultType = (FaultType) enumType
}
[DataMember]
public int faultId;
[DataMember]
public FaultType faultType;
}
[DataContract]
public enum FaultType
{
[EnumMember]
NullReferenceExcpetion,
[EnumMember]
ArithmaticException,
[EnumMember]
SQLException
}
上面示例中定义的 Enum
包含常见的异常,你可以根据需要定义任意数量的异常。FaultHandle
类包含 Id 和 enum
变量来传递异常详细信息。
现在,让我们考虑一个抛出这些异常之一的特定方法。
[ServiceContract]
interface IMyService
{
[OperationContract]
[FaultContract(typeOf(FaultHandle))]
public string canOccurException();
}
public class MyService: IMyService
{
public void myMethod()
{
int num = 10;
try
{
num/0;
}
catch(ArithmaticException)
{
throw new FaultException<FaultHandle>(new FaultHandle(1, 2));
}
}
}
因此,上述方法抛出了一个 fault exception。这个异常可以在客户端捕获,并根据其 enum
类型来决定所需的处理。
8. 何时更新服务引用
更新服务引用是一项繁琐的任务。所以我们希望尽可能避免它。因此,我们应该知道何时必须更新服务引用。只有在以下情况下才需要更新服务引用:
- 如果
DataContract
已更改:我们添加了任何额外的DataMember
或删除了任何DataMember
,使其不再可供客户端使用。 - 如果
ServiceContract
已更改:已向 Service Contract 添加了新的操作协定方法,或者更改了任何方法签名,或者从 Service Contract 中删除了任何方法。
如果我们只更改了任何操作方法的正文,则无需更新服务引用。只需生成更改后的类并发布(假设你使用 IIS 托管)即可。然后生成包含服务引用的类,你所做的更改将在不更新服务引用的情况下生效。
9. 更新服务引用
更新服务引用是一项非常繁琐的任务。所以,如果所有团队成员都在访问服务器上托管的服务进行开发,那么并非所有成员都需要单独更新服务引用。一个团队成员可以更新服务引用并将其签入(我们使用 TFS 进行签入)。其余成员可以下载该引用并继续工作。
10. 使用 IIS 在同一台机器上调试托管的服务
在实现新功能时,开发人员通常需要调试代码。因此,在开发环境中,如果服务使用 IIS 在同一台机器上托管,开发人员可以通过附加 w3wp 进程到应用程序来调试应用程序。如果此进程一开始不可见,请检查显示所有用户的进程选项。
* 服务的基本要点(原则)
我将把这一点作为奖励添加到本文中。在设计和编写 WCF 服务时,请牢记这些基本要点:
- 将所有服务调用合并为每个操作一个调用,或尽量减少调用次数。
- 减小跨服务边界传输的数据量。
- 将方法中的参数数量减少到 4 个或更少。
- 避免使用“out”参数。
- 使用外观层实现服务,在一个类中实现所有服务协定接口,并使用该类创建服务引用。
- 不要用无效数据调用服务,在客户端本身进行基本数据验证。