【问题标题】:Do you use enum types in your WCF web services?您是否在 WCF Web 服务中使用枚举类型?
【发布时间】:2010-09-24 11:45:37
【问题描述】:

我听一些人说枚举是邪恶的,不应该在 Web 服务中使用,因为如果分配了一些值,或者如果枚举被标记为Flags 属性。他们还说,暴露枚举的 Web 服务更难维护,但不能真正给我可行的论据。那么根据您的经验,在 WCF Web 服务中使用枚举的优缺点是什么?

【问题讨论】:

    标签: wcf enums


    【解决方案1】:

    人们建议在 web 服务中避免使用枚举的原因是因为它们会产生微妙的向后兼容问题。

    这同样适用于常规枚举,但在 Web 服务中,问题更加明显,特别是在 .NET 生成的代理中(见下文)。

    • 如果仅输入枚举,则没有问题。
    • 如果 enumerate 可以是 out 参数,那么如果您添加一个新元素并将其返回,那么旧客户端可能会遇到问题:
      • 如果客户端使用 .NET 生成的代理,它会在调用者处理它之前中断(在反序列化中)
      • 即使为代理生成的代码支持更改(例如,如果它将枚举映射到字符串),客户端中的用户代码也可能无法正确处理新的意外值(很容易成为从未执行的路径)

    通过将参数定义为字符串,您可以向您的 API 用户发出该值将来可能会更改的信号。即使您认为该值永远不会改变,也是做好准备的好习惯。

    Dare Obasanjo 有一个good post 讨论这个话题。

    【讨论】:

    • ​作为枚举向后兼容的一种解决方法,可以将它们声明为 [DataContract] 并且仅将旧值设置为 [DataMember] ,但保留不带属性的新值。
    • "如果仅输入枚举,则没有问题。" 不完全正确:如果您重命名了 .NET 生成的代理使用的值之一,那么旧的当服务器无法反序列化该值时,向您发送旧名称的客户端会出现异常。
    【解决方案2】:

    我在 WCF 中使用过枚举,也在互操作性场景中使用过。 如果您控制服务的双方,则使用起来会更容易。 如果您只控制服务的一侧,则需要注意您提到的问题。

    枚举比字符串变量或您可能选择使用的其他变量要好得多。 使用字符串而不是枚举是 SOA 中称为“松散的 Goosey”的反模式。

    【讨论】:

      【解决方案3】:

      在 WSDL 和 XSD 中通过 xsd:enumeration 架构元素完全支持枚举。它同时支持单个值和标志样式的枚举,其中标志枚举中的多个值用空格分隔。

      因此,在任何符合标准的平台上使用枚举都应该没有问题。

      【讨论】:

      • MMind 提到的向后兼容性是一个严重的问题,经常被忽视。
      【解决方案4】:

      当然,这完全取决于您打算在哪里使用此 WCF 服务。

      如果是单个应用程序使用它,那么更改合约不会有任何影响。

      如果是多个内部应用程序,更改合约可能需要对其他应用程序进行一些更改。

      最后,如果 WCF 服务是公开的,您可能必须提供 2 个版本不同的服务,以便使用它们的人有时间将他们的客户端版本转移到新服务。

      这完全取决于您的需求。

      【讨论】:

        【解决方案5】:

        使用除枚举之外的任何其他东西都不能解决您的兼容性问题,它只会隐藏它。 假设您使用 int 替换枚举。你是真的解决了兼容性问题还是只是伪装了它,直到客户端运行时达到未知值?

        但是,有一点值得一提:WCF 代理不会重新创建显式设置的枚举数值。如果枚举是用“holes”声明的,比如

        enum ErrorCodes
        {
          OK = 0,
          GenericError = 100,
          SomeOtherError = 101,
        }
        

        客户端表示将是这样的

        enum ErrorCodes
        {
          OK,
          GenericError,
          SomeOtherError,
        }
        

        ...在客户端导致 (int)ErrorCodes.GenericError 为 1。

        你会有句法等价,但不是数字等价。

        【讨论】:

        • 在较新的 .Net 版本(例如 4.5.1)中已修复不保留“漏洞”。
        【解决方案6】:

        WSDL 中的枚举必须被视为维护问题。

        添加或删除枚举值是(应该是!)接口重大更新的触发器。 如果枚举是一个输出值,那么您必然需要通过一个新的 URI 定义一个新版本的 WSDL,以防止当前客户端违反已建立的合同(“如果他们收到这些新的、意外的值作为回报怎么办? ?”) 如果枚举是输入值,您可以将其视为次要更新(“因为当前客户不需要知道这个新值”),但是,这些客户从添加这个中受益的唯一方法新选项/功能(您添加这个新枚举值是有原因的,对吗?)将要求他们稍后或更快地切换到新版本的界面。

        我认为这与枚举的功能意义无关。

        坚持最佳做法,您会很安全。

        【讨论】:

        • ​枚举可以声明为 [DataContract] 并且只有旧值设置为 [DataMember] ,但在新的 MAJOR 版本之前保留没有属性的新值
        【解决方案7】:

        我在基于 WCF 的服务中使用了枚举,没有任何问题。您提到的可能问题绝对是要考虑的事情,但如果您确保在相当静态的情况下应用枚举,您可能不会遇到太多麻烦。

        【讨论】:

          【解决方案8】:

          真实世界的故事(值因匿名而改变)。在应用程序中使用隐式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 }
          

          所以……它可能会咬你。

          【讨论】:

            【解决方案9】:

            这是一种方法。也许它很麻烦。我真的不喜欢不能使用枚举。

            它优雅地处理无法识别的值的反序列化,而是返回默认值。默认值需要是安全的——可以是可接受的回退,也可以是应用程序可以识别为异常的值。 (如“未指定”。)

            扩展可以避免在比较之前进行空值检查。

            [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));
                }
            }
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2011-04-03
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-08-28
              相关资源
              最近更新 更多