强制.NET Web服务使用本地对象类,而不是代理类

Sql*_*yan 5 .net web-services class proxy-classes

I have a webservice that I'm calling from a windows forms application (both .NET, both in the same solution), and I'd like my webservice to return a custom object from elsewhere in the project - it's a common object that they both share a reference to, as it's in the third project in my solution. When I call the webservice, it returns a "Person" object, but it's in the namespace of the webservice, and it's created from a proxy class that the webservice itself generated. As such, I can't manipulate it and return it to my program, which is expecting a "Person" object based on the shared copy of the class, not a proxy copy from the webservice namespace, and I get an error when I try to CType it to the correct class type.

如何强制Web服务使用类的本地副本,而不是代理副本?在这种情况下,我的问题是否有意义?如果没有,我会澄清它.

值得注意的是 - 我已经使用了所有参数ByRef,并使用这些返回的值来填充我在返回时创建的对象的副本.这不是最好的方法!

Chr*_*ian 2

根据设计,客户端生成的代理对象(或类)与服务器端相应 Web 方法使用的对象虽然相同,但是不同的

例如,当服务器端序列化并返回对象A时,它将在客户端接收并反序列化,生成一个与对象A具有相同结构(相同成员、相同子类等)的对应对象B,但在特定于客户端 Web 服务引用的不同命名空间内,并且可能具有不同的名称。这是有充分理由的,但这里没有必要讨论它。

正如线程前面指出的,围绕对象 A 可能开发了很多有用的代码,但遗憾的是重新实现它(或简单地重复源代码)。此外我们希望尽可能避免代码重复,以方便进一步的维护。

从长远来看,在客户端存根(soap 客户端)自动生成的反序列化中进行摆弄的解决方案风险更大,因为它要求您对每个后续客户端存根与每个新版本的重新同步应用相同的操作服务器端的。此外,据我所知,在 2008 年的某个时候,这是可能的,但我们有什么保证它将继续以相同的方式与后续的 Visual Studio 版本一起工作?我在 VS2019 中无法做到这一点。

对于像我这样懒惰(且谨慎)的人来说,有一个简单(且安全)的解决方案;方法如下:

client-stub收到对象B(对应服务器端的对象A)后,可以快速将其“转换”回对象A。只需从对象A到对象进行“Deep-Copy”即可乙!如果对象 A(和 B)足够复杂,您可能会考虑使用“System.Reflection”进行通用深度复制,以按名称匹配每个子字段,或者您可以使用通用序列化器/反序列化器将接收到的对象 B 转换为文本,然后将此文本转换回对象 A。例如,我使用 Newtonsoft Nuget 包用几行代码完成了这项工作。

请参阅以下示例,在服务器端的.asmx服务页面中:

    [WebMethod]
    public ObjectTypeA WebMethodPeek()
    {
        ObjectTypeA instanceA = GetSerializableObjectA();
        return instanceA;
    }
Run Code Online (Sandbox Code Playgroud)

而Web服务客户端:

    using Newtonsoft.Json;

    public ObjectTypeA Peek()
    {
        SoapClient cli = new SoapClient("my-end-point-name");
        ObjectTypeB instanceB = cli.WebMethodPeek();
        string text = JsonConvert.SerializeObject(instanceB);
        ObjectTypeA instanceA = JsonConvert.DeserializeObject<ObjectTypeA>(text);
        return instanceA ;
    }
Run Code Online (Sandbox Code Playgroud)

感谢Enrique Reyes在这篇文章《如何在 C#.NET 中不同类型的对象之间进行深度复制》中提供了简单而优雅的“DeepCopy”答案。

写完这个答案后,我发现相同的策略(......不知何故并且没有太多强调)也在这篇文章中以不同的方式解释了使用不同类型的序列化/反序列化 System.Object

附录

经过一些实践工作后,我发现使用“JSon 序列化/反序列化”对相同(或几乎)数据结构进行深度复制的方法几乎没有什么可克服的烦恼。

  1. 具有许多子类和属性的大型对象预计会占用更大的足迹。在客户端添加 Web 服务引用时,它不仅会复制 Web 方法参数的结构层次结构(具有不同的命名空间),而且如果用作返回类型,还会复制另一个结构层次结构。算上原始的(通过公共项目库共享),我们最终得到了相同大型可序列化结构的 3 个副本。

  2. 然后我遇到了多态子类的障碍,这使反序列化器感到困惑。这是因为,就我而言,它们被松散地键入为“对象”,以便保存不同的派生类,而不管它们的类型。在这种情况下,我们仍然可以编写一组回调函数来在派生的类中完成修补工作JsonConverter。在服务器端绝对不需要做任何事情。

但总体而言,尽管交换的对象体积庞大且复杂,但一切都很顺利。我仍然使用xsd.exe为更大、更复杂的对象生成完全可序列化的类。

另一种更简单的策略:使用二进制序列化

后来我发现仅涉及 .NET Framework来交换二进制对象(即)要简单得多。byte[]它允许在服务器端和客户端之间使用通过公共库共享的相同类。在任何一方,我们都需要在发送之前序列化并反序列化我们收到的内容。

我编造了一些非常简化的例子,省略了理解所不需要的内容。

  • 所需的命名空间:

     using System.IO;
     using System.Runtime.Serialization.Formatters.Binary;
    
    Run Code Online (Sandbox Code Playgroud)
  • 客户端从服务器接收的示例:

     SharedClass myClass = null;
     byte[] byteArray = my_WebService.GetInfo();
     BinaryFormatter formatter = new BinaryFormatter();
     using (MemoryStream stream = new MemoryStream(byteArray)) {
         object obj = formatter.Deserialize(stream);
         myClass = obj as SharedClass ;
     }
    
    Run Code Online (Sandbox Code Playgroud)
  • 客户端发送到服务器的示例:

     byte[] byteArray = null;
     SharedClass myClass = new SharedClass(xyz);
     BinaryFormatter formatter = new BinaryFormatter();
     using (MemoryStream stream = new MemoryStream()) {
         formatter.Serialize(stream, myClass);
         byteArray = stream.ToArray();
     }
     my_WebService.SendInfo(byteArray);
    
    Run Code Online (Sandbox Code Playgroud)

在服务器端,这几乎是一样的事情

  • 服务器发送给客户端:

     [WebMethod]
     public byte[] GetInfo()
     {    
         SharedClass myClass = new SharedClass(xyz);
         BinaryFormatter formatter = new BinaryFormatter();
         using (MemoryStream stream = new MemoryStream()) {
             formatter.Serialize(stream, myClass);
             return (stream.ToArray());
         }
     }
    
    Run Code Online (Sandbox Code Playgroud)
  • 服务器从客户端接收:

     [WebMethod]
     public void SendInfo(byte[] byteArray)
     {    
         SharedClass myClass = null;
         BinaryFormatter formatter = new BinaryFormatter();
         using (MemoryStream stream = new MemoryStream(byteArray)) {
             object obj = formatter.Deserialize(stream);
             myClass = obj as SharedClass;
         }
     }
    
    Run Code Online (Sandbox Code Playgroud)

这不需要太多工作就能迅速发挥作用。但:

  • 确保共享结构始终是相同的版本(或更准确地说:完全相同),否则反序列化将中断
  • 如果反序列化没有破坏。即使从受信任的用户接收数据时,仍会进行最低限度的数据完整性验证。查找“超出范围”的值、损坏的日期等
  • 安全方面。出于安全原因,许多人不愿意(常常惊慌失措)通过网络服务以二进制形式交换数据。这始终取决于您如何处理接收到的数据。如果你只是按原样保存二进制博客,确实令人担忧。但如果它是结构化数据,则可能完全无关,即分解为较小的数据元素并重新组装成结构化对象。因此,非法数据甚至很可能无法通过基本类型验证。在最坏的情况下,您最终会得到一些损坏的“有效”数据,这些数据无论如何都是从善意的受信任用户那里获得的。
  • 话虽如此,Web 服务应该有最低限度的访问控制:防火墙、IP 过滤、用户名/密码等