我听说有人说枚举是邪恶的,不应该在 Web 服务中使用,因为如果分配了某些值,或者如果枚举被标记为Flags属性,服务器和客户端之间可能会发生不匹配。他们还说,公开枚举的 Web 服务更难维护,但不能真正给我可行的论据。那么根据您的经验,在 WCF Web 服务中使用枚举的优缺点是什么?
9 回答
人们建议避免在 web 服务中使用枚举的原因是因为它们会产生微妙的向后兼容问题。
这同样适用于常规枚举,但在 Web 服务中,问题更加明显,特别是在 .NET 生成的代理中(见下文)。
- 如果仅输入枚举,则没有问题。
- 如果 enumerate 可以是 out 参数,那么如果您添加一个新元素并将其返回,那么旧客户端可能会遇到问题:
- 如果客户端使用 .NET 生成的代理,它将在调用者处理它之前中断(在反序列化中)
- 即使为代理生成的代码支持更改(例如,如果它将枚举映射到字符串),客户端中的用户代码也可能无法正确处理新的意外值(它可能很容易成为从未执行的路径)
通过将参数定义为字符串,您可以向 API 的用户发出该值将来可能会更改的信号。即使您认为该值永远不会改变,也要做好准备。
Dare Obasanjo有一篇关于这个主题的好帖子。
我在 WCF 中使用过枚举,也在互操作性场景中使用过。如果您控制服务的双方,则使用起来会更容易。如果您只控制服务的一侧,则需要注意您提到的问题。
枚举比字符串变量或您可能选择使用的其他变量要好得多。使用字符串而不是枚举是 SOA 中称为“松散的 Goosey”的反模式。
xsd:enumeration
通过模式元素在 WSDL 和 XSD 中完全支持枚举。它提供对单个值和标志样式枚举的支持,其中标志枚举中的多个值由空格分隔。
因此,在任何符合标准的平台上使用枚举都应该没有问题。
当然,这完全取决于您将在何处使用此 WCF 服务。
如果是单个应用程序将使用它,那么更改合同将不会产生任何影响。
如果是多个内部应用程序,更改合约可能需要对其他应用程序进行一些更改。
最后,如果 WCF 服务是公开的,您可能必须提供 2 个版本不同的服务,以便使用它们的人有时间将他们的客户端版本转移到新服务。
老实说,这完全取决于您的需求。
使用除枚举之外的任何其他东西都不能解决您的兼容性问题,它只会隐藏它。假设您使用 int 替换枚举。你真的解决了兼容性问题还是只是伪装它直到客户端运行时达到未知值?
但是,值得一提的是:WCF 代理不会重新创建显式设置的枚举数值。如果枚举用“holes”声明,比如
enum ErrorCodes
{
OK = 0,
GenericError = 100,
SomeOtherError = 101,
}
客户端表示将是这样的
enum ErrorCodes
{
OK,
GenericError,
SomeOtherError,
}
...在客户端导致 (int)ErrorCodes.GenericError 为 1。
您将有句法等价,但没有数字等价。
WSDL 中的枚举必须被视为维护问题。
添加或删除枚举值是(应该是!)接口重大更新的触发器。如果枚举是一个输出值,那么您必然需要通过一个新的 URI 定义一个新版本的 WSDL,以防止当前客户端违反已建立的合同(“如果他们收到这些新的、意外的值作为回报怎么办? ?") 如果枚举是输入值,您可以将其视为次要更新(“因为当前客户不需要知道这个新值”),但是,这些客户从添加这个新选项/功能(您添加这个新枚举值是有原因的,对吗?)将要求他们稍后或更快地切换到新版本的界面。
我认为这与枚举的功能意义无关。
坚持最佳实践,您将是安全的。
我在基于 WCF 的服务中使用了枚举,没有任何问题。您提到的可能问题绝对是要考虑的事情,尽管如果您确保在相当静态的情况下应用枚举,您可能不会遇到太多麻烦。
真实世界的故事(值因匿名而改变)。enum
用于在应用程序中隐含
public enum { orange, banana, mango }
围绕顺序和新值进行了一些重构,我们决定明确说明:
public enum { orange=1, banana=2, grape=3, mango=4 }
看起来无伤大雅……
接下来,一个网站爆炸了。从头开始,检查服务,添加调试消息,一切似乎都很好。
一天后,在兔子洞里,原因是一个使用枚举作为返回类型的基本 wcf 服务。
显然,Wcf 不喜欢没有默认值的枚举。
使固定:
public enum { wcfBugBane=0, orange=1, banana=2, grape=3, mango=4 }
所以......它可能会咬你。
这是一种方法。也许它很麻烦。我真的不喜欢不能使用枚举。
它优雅地处理无法识别的值的反序列化,而是返回默认值。默认值需要是安全的——可以是可接受的回退,也可以是应用程序可以识别为异常的东西。(如“未指定”。)
扩展防止在比较之前必须进行空检查。
[DataContract]
public class EnumValue<T> where T : struct
{
[DataMember]
private string _raw = string.Empty;
[IgnoreDataMember]
private bool _parsed;
[IgnoreDataMember]
private T _parsedValue;
public EnumValue()
{
Set(default(T));
}
public EnumValue(T value)
{
Set(value);
}
internal T Value
{
get
{
if (_parsed) return _parsedValue;
if (!Enum.TryParse<T>(_raw, out _parsedValue))
{
_parsedValue = default(T);
}
_parsed = true;
return _parsedValue;
}
}
public void Set(T value)
{
_raw = value.ToString();
_parsedValue = value;
_parsed = true;
}
}
public static class EnumValueExtensions
{
public static T GetValue<T>(this EnumValue<T> enumValue) where T : struct
{
return enumValue == null ? default(T) : enumValue.Value;
}
public static bool EqualsValue<T>(this EnumValue<T> enumValue, T compareTo) where T : struct
{
return (enumValue.GetValue().Equals(compareTo));
}
}