在SOAP反序列化

时间:2017-11-06 21:27:50

标签: c# soap enums webservices-client

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

[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”类型函数。我不认为存在,但希望得到一些帮助...

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

一些额外的背景:

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

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

2 个答案:

答案 0 :(得分:0)

仍然希望其他人能得到答案,但我至少想出了一些比我最初想到的使用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>

因此,只要此枚举的值包含在类型名称为<UserRole>UnexpectedRole</UserRole>的标记中,如果无法识别,它将替换为UserRole.Unknown,我的客户可以高兴地忽略。请注意,如果有另一个名为UserRole的标记不属于此枚举类型且预期为字符串或整数,则此操作也可能会中断。它相当脆弱。

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

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

最终会产生包含UserRole[]UserRole.InvoiceEntry的{​​{1}}。

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

UserRole.Unknown

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

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

我确实在类型上添加了一些缓存,这样我只需要检查一次Tag名称,看它是否也是枚举的名称,但为了清楚起见,我在本例中删除了它。

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

答案 1 :(得分:0)

我将冒险尝试downvote并说明显而易见的事项:不要在服务合同中使用枚举。正如您所知,除固定域外,它们都很脆弱。

如果我是该服务的消费者,那么Unknown条目会让我问“当服务返回时我该怎么办?”你会回复一些类似“不要担心,它不会只是为了客户端的兼容性”,我会回答“好吧,如果服务不会返回它在合同中做了什么?”

返回string[]并让您的客户端解析数组以获取它可以处理的信息。如果intellisense确实是您的目标,您可以在客户端中定义枚举的子集,并且您可以在搜索更精细的解决方案时将其实施一百次。步。远。从。 THE。 ENUMS。