【问题标题】:Ignore invalid Enum values in SOAP deserialization忽略 SOAP 反序列化中的无效枚举值
【发布时间】:2017-11-06 21:27:50
【问题描述】:

我在 ASP.NET 网络服务上有一个 WebMethod,它返回一个枚举数组。如果添加了一个新值,并且该值由函数调用返回,那么 Web 服务的使用者将抛出异常,即使它并不关心该枚举值。

[WebMethod]
public UserRole[] GetRoles(string token)

部分 wsdl:

  <s:simpleType name="UserRole">
    <s:restriction base="s:string">
      <s:enumeration value="Debug" />
      <s:enumeration value="EventEditor" />
      <s:enumeration value="InvoiceEntry" />
    </s:restriction>
  </s:simpleType>

(使用此 wsdl 编译消费者,但随后 wsdl 更改并且现在允许新值 - 如果返回该值,则客户端将引发 XML 异常。)

有没有办法覆盖这种类型的 SOAP 反序列化,以便我可以捕获错误并从数组中删除该项目或将其替换为默认值?如果这是使用 JSON 而不是 XML,我可以注册一个 JsonConverter 来处理该类型,所以我想我正在寻找一个类似的全局“RegisterConverter”类型函数。我认为不存在,但希望得到一些帮助......

任何用 Attributes 装饰 Enum 的方法都不起作用,因为所有代码都是由 wsdl 生成的,并在更新 Web 引用时重新生成。通常,如果我想修改由 wsdl 生成的类,我可以创建一个部分类,但这不适用于 Enum。甚至不确定我是否可以覆盖 XmlSerialization 代码,即使它是一个类。



一些额外的背景:

这实际上是作为我对动态枚举的尝试而实现的。 wsdl 是从数据库查找中生成的,因此我可以向数据库添加额外的值,并且消费应用程序将可以访问允许的值,而无需重新编译 web 服务。通过这种方式,我通过枚举类型获得智能感知和约束实施,但能够在不紧密耦合 Web 服务代码和客户端代码的情况下添加值。问题是,如果我添加一个新值,它可能会破坏未使用新 wsdl 更新的消费者……我宁愿忽略该值,因为消费者不知道如何处理无论如何。

SOAP 扩展可能是解决此问题的方法(我知道如何将 SOAP 扩展添加到 WebService 本身,但不知道如何在客户端添加一个...),但这并不理想,因为我真的很想有一种通用的方法来轻松处理这个问题,这样我就可以在我的代码中拥有更多动态枚举(它们并不是真正动态的,但想法是值通过 web 服务的中间层而无需重新编译那个中间层)。像“XmlSerialization.RegisterConverter(MyDynamicEnumType, DynamicEnum.Convert)”这样的东西是理想的,我可以定义一个通用函数来使用和注册。)

【问题讨论】:

  • 好吧,我想到了两种可能的解决方案。一种是在 WebService 声明上覆盖 GetReaderForMessage(在部分类中),然后对枚举名称和值进行 Regex 匹配,另一种是将可接受值的列表发送到 Web 服务并让 Web 服务拒绝发送不在该列表中的值。这两个都很糟糕,我希望有人能提出比我迄今为止得到的更好的答案。如果没有,我明天会发布我自己的 janky 答案。
  • 好吧,我确实最终覆盖了 GetReaderForMessage,但在枚举名称的整个消息字符串上进行的正则表达式匹配稍好...仍然希望有更好的方法来覆盖枚举的反序列化。跨度>
  • WSDL 和 XSD 的全部意义在于在服务和客户端之间建立一个契约,而您正在实施一些东西来打破这个契约。你不应该重新考虑一个不同的实现吗?或者更改 WSDL,以便允许任何内容而不是枚举。
  • 如何违约? WSDL 会改变,但编译后的代码不能动态更新它的 wsdl,至少对于 .NET 的使用 Web 服务的实现来说是这样。我不能让对 wsdl 的每一次更改都成为重大更改,那将是混乱的。这只是一种将 web 服务代码与客户端代码解耦,并将代码与数据解耦,同时仍允许设计时智能感知和约束的方法。后者是枚举的用途。
  • 如果你试图用一个整数替换 Enum 怎么办?并让客户决定如何处理它? (是否将其转换为枚举)

标签: c# soap enums webservices-client


【解决方案1】:

仍然希望其他人能给出答案,但我至少想出了比我最初使用 Regex.Replace 去除对我不认识的枚举值的引用的想法更好的东西。

我正在为 Web 服务使用部分类并覆盖 GetReaderForMessage,如下所示:

namespace Program.userws  //note this namespace must match the namespace 
                          //that the webservice is declared in (in auto-generated code)
{
    partial class UserWebService
    {
        protected override XmlReader GetReaderForMessage(SoapClientMessage message, int bufferSize)
        {
            return new EnumSafeXmlReader(message.Stream);
        }
    }
}

这是 EnumSafeXmlReader 的定义:

public class EnumSafeXmlReader : XmlTextReader
{
    private Assembly _callingAssembly;

    public EnumSafeXmlReader(Stream input) : base(input)
    {
        _callingAssembly = Assembly.GetCallingAssembly();
    }

    public override string ReadElementString()
    {
        string typename = this.Name;
        var val = base.ReadElementString();

        var possibleTypes = _callingAssembly.GetTypes().Where(t => t.Name == typename);
        Type enumType = possibleTypes.FirstOrDefault(t => t.IsEnum);

        if (enumType != null)
        {
            string[] allowedValues = Enum.GetNames(enumType);

            if (!allowedValues.Contains(val))
            {
                val = Activator.CreateInstance(enumType).ToString();
            }
        }

        return val;
    }
}

我还为 UserRole 添加了一个新值 - UserRole.Unknown,并确保它是允许值列表中的第一个。

<s:simpleType name="AcctUserRole">
  <s:restriction base="s:string">
    <s:enumeration value="Unknown"/>
    <s:enumeration value="Debug"/>
    <s:enumeration value="EventEditor"/>
    <s:enumeration value="InvoiceEntry"/>
  </s:restriction>
</s:simpleType>

所以只要这个枚举的一个值被包装在一个类型名为&lt;UserRole&gt;UnexpectedRole&lt;/UserRole&gt;的标签中,如果它不被识别,它将被替换为UserRole.Unknown,我的客户可以很高兴地忽略它。请注意,如果有另一个名为 UserRole 的标记不是此枚举类型并且预期为字符串或 int,这也可能会中断。它相当脆弱。


此解决方案仍有很多不足之处,但它通常适用于枚举值列表...

<GetRolesForUserResult>
    <UserRole>InvoiceEntry</UserRole>
    <UserRole>UnexpectedRole</UserRole>
</GetRolesForUserResult>

这最终会产生一个包含UserRole.InvoiceEntryUserRole.UnknownUserRole[]

但如果我有一个 UserRole 类型的字段或属性:

<User>
    <ID>5</ID>
    <Name>Zorak</Name>
    <PrimaryRole>UnexpectedRole</PrimaryRole>  <!-- causes an exception -->
</User>

这仍然会失败,因为 Reader 无法知道“PrimaryRole”需要反序列化才能键入 UserRole。 XmlSerializer 知道这一点,但据我所知,没有办法覆盖 XmlSerializer,只有 XmlReader。

我想给 EnumSafeXml 阅读器提供足够的信息来识别将反序列化为枚举类型的标签并不是完全不可能的,但这比我现在愿意去的麻烦更多——我特别需要它在“枚举值数组”的情况,现在就是这样。

我确实在 Types 上添加了一些缓存,因此我只需检查一次标记名称即可查看它是否也是枚举的名称,但为了清楚起见,我在本示例中删除了它。


我欢迎任何其他可能的解决方案或改进此解决方案的建议。

【讨论】:

    【解决方案2】:

    我会冒着被否决的风险并声明显而易见的事实:不要在您的服务合同中使用枚举。正如您已经确定的那样,除了固定域外,它们都很脆弱。

    如果我是服务的消费者,Unknown 条目会引导我问“当服务返回时我应该做什么?”你会回复类似“别担心,它不会只是为了客户端兼容性”之类的话,我会回复“如果服务不返回它,它在合同中做了什么?”

    返回 string[] 并让您的客户端解析数组以获取它可以处理的信息。如果智能感知确实是您的目标,您可以在客户端中定义枚举的子集,并且在您花费时间寻找更复杂的解决方案时,您可能已经实现了一百次。步。离开。从。这。枚举。

    【讨论】:

    • 这不是一个有用的答案。对数据进行限制是有充分理由的,多年来我一直在寻找查找表、枚举、字符串值和幻数之间的最佳答案。单独来说,它们都不是很有效。我终于有办法以提供 Intellisense、数据约束、可发现性和可扩展性的方式将查找表和枚举链接在一起,我只需要 XML 反序列化,在涉及到值时不会出错并破坏整个程序。不明白。我已经找到了一种方法,但我正在寻找更优雅的方法。
    • 查找表不是枚举。 “动态枚举”不是枚举。服务契约与接口不同。你的设计中细微的不一致是你一直用头撞墙的原因。现在你有一把锤子,一切看起来都像钉子。尝试改用螺丝刀。
    • 查找表、枚举、“字符串类型”变量和幻数都有相同的最终目标,即表示可选择的值。他们都没有完美地独自完成目标。似乎没有人拥有同时包含约束、可扩展性、可移植性、关注点分离和可发现性的实现。这是一个难题,这就是为什么我在努力解决它——这不是因为我“试图敲定一个螺丝钉”。我现在唯一的问题是重写反序列化代码,这不应该这么难。
    • 如果你有一个不同的解决方案来实现我想要的可枚举值的目标,而不是覆盖反序列化代码的解决方案,那么请随意发布,而不是“你想做的事是愚蠢的。”
    • 我很确定我没有说你愚蠢,但如果它以这种方式出现,我很抱歉。有时你可能会全神贯注,以致于只见树木不见森林;我试图鼓励你退后一步,重新评估你是否做对了。查找表是配置数据。枚举是代码。这种基本区别是您问题的根源,因为您将它们混为一谈。我希望有时间进一步阐述。
    猜你喜欢
    • 2020-04-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多