强制.NET Web服务使用本地对象类,而不是代理类
我有一个从 Windows 窗体应用程序(都是 .NET,都在同一个解决方案中)调用的 Web 服务,并且我希望我的 Web 服务从项目中的其他位置返回自定义对象 - 这是它们的常见对象两者共享一个引用,因为它位于我的解决方案中的第三个项目中。 当我调用 Web 服务时,它返回一个“Person”对象,但它位于 Web 服务的命名空间中,并且是从 Web 服务本身生成的代理类创建的。 因此,我无法操作它并将其返回给我的程序,该程序需要一个基于类的共享副本的“Person”对象,而不是来自 webservice 命名空间的代理副本,并且当我尝试时会收到错误将其 CType 为正确的类类型。
如何强制 Web 服务使用类的本地副本,而不是代理副本? 我的问题在这种情况下有意义吗? 如果没有,我会澄清。
值得注意的是 - 我已经采用 ByRef 传递所有参数,并使用这些返回值来填充我在返回时创建的对象的副本。 这不是最好的方法!
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.
How do I force the webservice to use a local copy of the class, not a proxy copy? Does my question make any sense in this context? If not, I'll clarify it.
Of note - I've resorted to passing all of the parameters ByRef, and using those returned values to populate a copy of the object I create upon return. That can't be the best way to do this!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
如果使用 svcutil.exe 生成 WCF 客户端代理,则可以在命令行上使用 /reference 来指定包含公共类的程序集。 Svcutil 应重用该类定义,而不是在服务代理命名空间中生成新的类定义。
另外,只有当您的公共类可序列化并按值传递时(即它作为数据契约公开,而不是作为服务契约公开),这才有效。
If you are using svcutil.exe to generate a WCF client proxy, you can use /reference on the command-line to specify the assembly containing the common class. Svcutil should reuse that class definition instead of generating new one in the service proxy namespace.
Also, this will work only if your common class is serializable and passed by value (i.e. it's exposed as a data contract, not as a service contract).
根据设计,客户端生成的代理对象(或类)与服务器端相应 Web 方法使用的对象不同(尽管相同)。
例如,当服务器端序列化 & 时, 返回一个对象A,它将被接收& 在客户端反序列化生成的相应对象 B 中,该对象 B 具有与对象 A 相同的结构(相同的成员、相同的子类等),但在特定于客户端 Web 服务引用的不同命名空间内< /strong> 并且可能有不同的名称。 这是有充分理由的,但这里没有必要讨论它。
正如线程前面指出的,围绕对象 A 可能开发了很多有用的代码,但遗憾的是重新实现它(或简单地重复源代码)。 此外我们希望尽可能避免代码重复,以方便进一步的维护。
从长远来看,在客户端存根(soap 客户端)自动生成的反序列化中进行摆弄的解决方案风险更大,因为它要求您对每个后续客户端存根与每个新版本的重新同步应用相同的操作服务器端的。 此外,据我所知,在 2008 年的某个时候,这是可能的,但是我们有什么保证它将继续以与后续 Visual Studio 版本相同的方式工作呢? 我无法在 VS2019 中做到这一点。
对于像我这样懒惰(且谨慎)的人来说,有一个简单(且安全)的解决方案; 方法是这样的:
客户端存根收到对象B(对应服务器端的对象A)后,可以快速将其“转换”回对象A。只需从其中进行“深复制”即可对象A到对象B! 如果对象 A(和 B)足够复杂,您可能会考虑使用“System.Reflection”进行通用深度复制,以按名称匹配每个子字段,或者您可以使用通用序列化器/反序列化器将接收到的对象 B 转换为文本,然后将此文本转换回对象 A。例如,我使用 Newtonsoft Nuget 包用几行代码完成了这项工作。
请参阅以下示例,在服务器端的
.asmx
服务的页面中:而在 Web 服务客户端:
感谢 Enrique Reyes 他在这篇文章中提供了简单而优雅的“DeepCopy”答案 如何在C#.NET中不同类型的对象之间进行深度复制)
写完这个答案后,我发现同样的策略(......不知怎的,没有太多强调)也在这篇文章中以不同的方式解释了 使用不同类型序列化/反序列化 System.Object
Addemdum
经过一些动手工作后,我发现这种方法没有什么可克服的烦恼使用此“JSon 序列化/反序列化”来执行相同(或几乎)数据结构的深层复制。
对于具有许多子类和属性的大型对象,预计会有更大的占用空间。 在客户端添加 Web 服务引用时,它不仅会复制 Web 方法参数的结构层次结构(具有不同的命名空间),而且如果用作返回类型,还会复制另一个结构层次结构。 算上原始的(通过公共项目库共享),我们最终得到了相同大型可序列化结构的 3 个副本。
然后我遇到了多态子类的障碍,它使反序列化器感到困惑。 这是因为,就我而言,它们被松散地键入为“对象”,以便保存不同的派生类,而不管它们的类型。 在这种情况下,我们仍然可以编写一组回调函数来在从
JsonConverter
派生的类中完成修补工作。 在服务器端绝对不需要做任何事情。但总体而言,尽管交换的对象体积庞大且复杂,但一切都很顺利。 我仍然使用 xsd.exe 为更大、更复杂的对象生成完全可序列化的类。
另一种更简单的策略:使用二进制序列化
后来我发现仅涉及 .NET Framework 来交换二进制对象(即
byte[]
)简单得多。 它允许在服务器端和客户端之间使用通过公共库共享的相同类。 在任何一方,我们都需要在发送之前序列化并反序列化我们收到的内容。我编造了一些非常简单的例子,省略了理解所不需要的内容。
所需的命名空间:
客户端从服务器接收的示例:
客户端发送到服务器的示例:
在服务器端,这几乎是相同的事情
服务器发送到客户端:
<前><代码> [WebMethod]
公共字节[] GetInfo()
{
SharedClass myClass = new SharedClass(xyz);
BinaryFormatter 格式化程序 = new BinaryFormatter();
使用 (MemoryStream 流 = new MemoryStream()) {
格式化程序.Serialize(stream, myClass);
返回 (stream.ToArray());
}
}
服务器从客户端接收:
<前><代码> [WebMethod]
公共无效SendInfo(字节[] byteArray)
{
SharedClass myClass = null;
BinaryFormatter 格式化程序 = new BinaryFormatter();
使用(MemoryStream流=新MemoryStream(byteArray)){
对象 obj = formatter.Deserialize(stream);
myClass = obj 作为 SharedClass;
}
}
这工作很快,不需要太多工作。 但是:
By design, the proxy object (or class) generated on the client side is distinct, although identical, to the object used by the corresponding web-method on server side.
For example, when the server side serializes & returns an object A, it will be received & deserialized on the client end within a generated corresponding object B having an identical structure to the object A (same member, same sub-classes, etc), but within a different namespace specific to the client-side web-service's reference and likely with a different name. There are good reasons for that, but there is no point discussing it here.
As pointed out earlier in the thread, there might be a lot of useful code developed around object A, and it is a pity to re-implement it (or simply duplicate source code). Besides we want to avoid code duplication as much as possible to facilitate further maintenance.
The solution of fiddling within the auto-generated de-serialization of the client-stub (soap client) is even more risky on the long run because it requires you to apply same manipulations for each subsequent client-stubs re-synchronizations with each new versions of the server-side. Besides, I understand that it was, at some point, possible in 2008, but what guaranties do we have that it will continue to work the same way with subsequent Visual Studio versions? I couldn't do it within VS2019.
There is a simple (and safe) solution for lazy (and cautious) people like me; here is how:
After the client-stub receives the object B (corresponding to object A of server side), it is possible to swiftly "transform" it back into an object A. You just need to do a "Deep-Copy" from object A to object B! If objects A (and B) are complex enough you might consider a generic Deep-Copy using "System.Reflection" to match each sub-fields by their name OR you can use a generic Serializer/Deserializer to Convert the received object B to text, then convert this text back into an object A. For instance, I did the job in few lines of code using Newtonsoft Nuget package.
See the below example, in the
.asmx
services' page on server side:While of the web-service client side:
Thanks to Enrique Reyes for the simple and elegant "DeepCopy" answer he provided in this post How to deep copy between objects of different types in C#.NET)
After writing this answer, I found the same strategy (...somehow and without much emphasis) also explained in a different way in this post Serializing/deserializing System.Object using a different type
Addemdum
After some hands-on work I found few surmountable annoyances with this method of using this "JSon Serialization/Deserialization" to do a deep copy of a identical (or almost) data-structures.
Expect a larger foot print for large objects with many sub-classes and properties. When adding a web-service reference on client side, not only it replicates the structure hierarchy (with different namespaces) for web-methods' arguments, but it replicates another one if used as a return type. Counting for the original one (which is shared via common project library) we end up with 3 copies of same large serializable structure.
Then I encountered an obstacle with polymorphic sub-classes which confuses the deserializer. That's because, in my case, those are loosly typed as "object" in order to hold different derived classes disregarding their types. In such case we still can write a set of callback functions to do the patch work in a class derived from
JsonConverter
. Nothing absolutly needs to be done on server side.But overall everything worked out despite the large size and complexity of the exchanged objects. I still use
xsd.exe
to generate fully serializable class for larger, and more complex, objects.Another strategy yet simpler: using binary serialization
Afterwards I found much simpler to exchange binary objects (i.e.
byte[]
) involving only .NET Framework. It allows for using the same class shared through a common library between the server side and client side. On either side we need to serialize before sending and deserialize what we receive.I made up some very simplified examples omitting whatever is not necessary for understanding.
Required namespaces:
Example client receiving from server:
Example client sending to server:
On server side it's pretty much the same thing
server sending to client:
server receiving from client:
This works swiftly without much work. But:
如果您使用 WCF,那么在客户端和使用者之间使用相同的数据契约和服务接口是相当容易的。 您可以在生成的代理类中进行编译并修改它以使用正确的命名空间,或者使用 ChannelFactory 类为您创建动态代理。
第一种解决方案非常脆弱,并且会导致每次服务接口发生变化时都需要修改代理类。 第二种技术效果相当好,我们在我之前从事的项目中使用过。 使用这两种方法中的任何一种,您都需要确保所有调用者都保持最新版本的界面。
从您描述问题的方式来看,听起来您希望服务和客户端共享同一个实例。 由于 WCF 在将类型发送到服务或从服务发送出类型时对类型进行序列化和反序列化,因此您必须做一些更聪明的事情。 这是你的意思吗?
If you are using WCF is it fairly easy to use the same data contracts and service interface between the client and the consumer. You can either compile in the generated proxy class and modify it to use the correct namespaces or use the ChannelFactory class to create a dynamic proxy for you.
The first solution is very brittle and will cause you to modify the proxy class every time the service interface changes. The second technique works fairly well and we used in during a previous project I worked on. With either of these methods you need to make sure all your callers continue are up to date with the latest version of the interface.
From the way you are describing the problem it sounds like you want the service and the client to share the same instance. Since WCF serializes and deserializes your types as you send them to and from the service you would have to do something a bit more clever. Is this what you meant?
我不确定,但是当您编译 .NET Web 服务时,它会创建一个 DLL 文件,您可以尝试在本地使用该文件。 但是,当我构建面向服务的应用程序时,我会在解决方案中创建不同的层,例如数据访问层、逻辑层、服务层、UI 层、控制器层,例如在控制器层中,我将执行一种用户身份验证方法,即与数据访问层和逻辑层连接,然后我将在服务层调用该方法,我也可以在 UI 层调用它,如果我从 UI 层调用它,当我想使用它时,它会在本地调用从服务层,我将使用该方法创建一个 Web 方法,该方法将返回 bool 或用户名等。
I am not sure but when you compile a .NET web service it will create a DLL file which you can try using that for the local. But when I am building service oriented applications, I create different layers within my solution for e.g Data Access Layer, Logic Layer, Service Layer, UI Layer, Controller Layer, and for example in the Controller Layer I will do a user authentication method which is connected with the Data Access Layer and Logic Layer and then I will call that method on service layer and I can also call it on the UI layer and if I call it from within the UI layer it is called locally, when I want to use it from the service layer, I will create a web method using that method which will return a bool or username etc.