如何在 ASP.NET 应用程序中创建可返回接口实例作为透明代理的 WCF Web 服务
我的用例:
- 我已经有一个可用的 ASP.NET 应用程序
- ,我想实现一个新的 Web 服务作为该应用程序的一部分
- ,我应该使用 WCF 服务 (*.svc),不 ASP.NET Web 服务 (*.asmx)
- 该服务需要有一个操作,我们将其称为
GetInterface()
,它返回接口的实例。该实例必须驻留在服务器上,不能被序列化到客户端;在该接口上调用的方法必须在服务器上执行。
以下是我的尝试(请告诉我哪里出错了):
为了测试这一点,我创建了一个名为
ServiceSide
的新 ASP.NET Web 应用程序 项目.在该项目中,我使用“添加 → 新项目”添加了一个WCF 服务。我将其命名为
MainService
。这创建了一个MainService
类以及一个IMainService
接口。现在我创建了一个名为
<前><代码>[服务合同] 公共接口 IWorkInterface { 【运营合同】 int GetInt(); }ServiceWorkLibrary
的新类库项目,仅包含要在客户端和服务器之间共享的接口声明,没有其他内容:回到
<前><代码>[服务合同] 公共接口IMainService { 【运营合同】 IWorkInterface GetInterface(); } 公共类 MainService :IMainService { 公共 IWorkInterface GetInterface() { 返回新的 WorkInterfaceImpl(); } } 公共类 WorkInterfaceImpl :MarshalByRefObject、IWorkInterface { 公共 int GetInt() { 返回 47; } }ServiceSide
,我替换了IMainService
接口中默认的DoWork()
方法及其在ServiceSide 中的实现。 code>MainService
类,我也添加了共享IWorkInterface
的简单实现。它们现在看起来像这样:现在运行这个应用程序“有效”,因为它在浏览器中为我提供了默认的 Web 服务页面,其中显示:
<块引用>您已经创建了一项服务。
要测试此服务,您需要创建一个客户端并使用它来 致电服务人员。您可以使用 svcutil.exe 工具来执行此操作 命令行具有以下语法:
svcutil.exe http://localhost:59958/MainService.svc?wsdl
这将生成一个配置文件和一个代码文件,其中包含 客户类。将这两个文件添加到您的客户端应用程序并使用 生成的客户端类来调用服务。例如:
接下来就到客户端了。在单独的 Visual Studio 中,我使用新的解决方案创建了一个名为
ClientSide
的新控制台应用程序项目。我添加了ServiceWorkLibrary
项目,并从ClientSide
添加了对它的引用。然后我运行了上面的
svcutil.exe
调用。这生成了一个MainService.cs
和一个output.config
,我将其添加到ClientSide
项目中。最后,我将以下代码添加到
Main
方法中:using (var client = new MainServiceClient()) { var workInterface = client.GetInterface(); Console.WriteLine(workInterface.GetType().FullName); }
这已经失败,并在构造函数调用中出现神秘异常。我设法通过将
output.config
重命名为App.config
来解决此问题。我注意到
GetInterface()
的返回类型是object
而不是IWorkInterface
。有人知道为什么吗?但是让我们继续...现在,当我运行此程序时,在调用
<块引用>GetInterface()
时出现CommunicationException
:底层连接已关闭:连接意外关闭。
如何解决此问题,以便获得我期望的 IWorkInterface
透明代理?
我尝试过的事情
我尝试添加
<块引用>[KnownType(typeof( WorkInterfaceImpl))]
到WorkInterfaceImpl
的声明。如果我这样做,我会在同一个地方得到不同的异常。现在它是一个NetDispatcherFaultException
,并带有以下消息:格式化程序在尝试反序列化消息时抛出异常:尝试反序列化参数时出错 http:// tempuri.org/:GetInterfaceResult。 InnerException 消息是“第 1 行位置 491 中出现错误”。元素“http://tempuri.org/:GetInterfaceResult”包含来自映射到名称“http://schemas.datacontract.org/2004/07/”的类型的数据。 ServiceSide:WorkInterfaceImpl'。解串器不知道映射到该名称的任何类型。考虑使用 DataContractResolver 或将与“WorkInterfaceImpl”对应的类型添加到已知类型列表中 - 例如,通过使用 KnownTypeAttribute 属性或将其添加到传递给 DataContractSerializer 的已知类型列表中。请参阅 InnerException 了解更多详细信息。
提到的
<块引用>InnerException
是一个SerializationException
,其消息如下:第 1 行位置 491 中出现错误。元素“http://tempuri.org/:GetInterfaceResult”包含映射到名称“http://schemas.datacontract.org/2004/07/ServiceSide:”的类型的数据: WorkInterfaceImpl'。解串器不知道映射到该名称的任何类型。考虑使用 DataContractResolver 或将与“WorkInterfaceImpl”对应的类型添加到已知类型列表 - 例如,通过使用 KnownTypeAttribute 属性或将其添加到传递给 DataContractSerializer 的已知类型列表。
请注意,这似乎表明系统正在尝试序列化该类型。 它不应该这样做。它应该生成一个透明代理。我如何告诉它停止尝试序列化它?
我尝试向
WorkInterfaceImpl
类添加属性[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
。没有效果。我尝试将
IWorkInterface
接口(在共享库ServiceWorkLibrary
中声明)上的属性[ServiceContract]
更改为[ServiceContract (SessionMode = SessionMode.Required)]
。也没有效果。我还尝试将以下神奇的
system.diagnostics
元素添加到ServerSide
中的Web.config
:<系统.诊断> <来源> <源名称=“System.ServiceModel”switchValue=“信息,ActivityTracing”propagateActivity=“true”> <听众> <添加名称=“traceListener”类型=“System.Diagnostics.XmlWriterTraceListener”initializeData=“c:\traces.svclog”/> />
这确实按照承诺生成了
c:\traces.svclog
文件,但我不确定我能否理解其内容。 我已将生成的文件发布到pastebin此处。您可以在更多位置查看此信息使用 svctraceviewer.exe 实现友好的 UI。我这样做了,但坦率地说,所有这些东西并没有告诉我任何事情...
我做错了什么?
My use-case:
- I already have a working ASP.NET application
- I would like to implement a new Web Service as part of that application
- I am supposed to use a WCF service (*.svc), not an ASP.NET web service (*.asmx)
- The service needs to have one operation, let’s call it
GetInterface()
, which returns instance of an interface. This instance must reside on the server, not be serialized to the client; methods called on that interface must execute on the server.
Here’s what I tried (please tell me where I went wrong):
For the purpose of testing this, I created a new ASP.NET Web Application project called
ServiceSide
.Within that project, I added a WCF Service using “Add → New Item”. I called it
MainService
. This created both aMainService
class as well as anIMainService
interface.Now I created a new Class library project called
ServiceWorkLibrary
to contain only the interface declaration that is to be shared between the client and server, nothing else:[ServiceContract] public interface IWorkInterface { [OperationContract] int GetInt(); }
Back in
ServiceSide
, I replaced the defaultDoWork()
method in theIMainService
interface as well as its implementation in theMainService
class, and I also added a simple implementation for the sharedIWorkInterface
. They now look like this:[ServiceContract] public interface IMainService { [OperationContract] IWorkInterface GetInterface(); } public class MainService : IMainService { public IWorkInterface GetInterface() { return new WorkInterfaceImpl(); } } public class WorkInterfaceImpl : MarshalByRefObject, IWorkInterface { public int GetInt() { return 47; } }
Now running this application “works” in the sense that it gives me the default web-service page in the browser which says:
You have created a service.
To test this service, you will need to create a client and use it to
call the service. You can do this using the svcutil.exe tool from the
command line with the following syntax:svcutil.exe http://localhost:59958/MainService.svc?wsdl
This will generate a configuration file and a code file that contains
the client class. Add the two files to your client application and use
the generated client class to call the Service. For example:So on to the client then. In a separate Visual Studio, I created a new Console Application project called
ClientSide
with a new solution. I added theServiceWorkLibrary
project and added the reference to it fromClientSide
.Then I ran the above
svcutil.exe
call. This generated aMainService.cs
and anoutput.config
, which I added to theClientSide
project.Finally, I added the following code to the
Main
method:using (var client = new MainServiceClient()) { var workInterface = client.GetInterface(); Console.WriteLine(workInterface.GetType().FullName); }
This already fails with a cryptic exception in the constructor call. I managed to fix this by renaming
output.config
toApp.config
.I notice that the return type of
GetInterface()
isobject
instead ofIWorkInterface
. Anyone know why? But let’s move on...Now when I run this, I get a
CommunicationException
when callingGetInterface()
:The underlying connection was closed: The connection was closed unexpectedly.
How do I fix this so that I get the IWorkInterface
transparent proxy that I expect?
Things I’ve tried
I tried adding
[KnownType(typeof(WorkInterfaceImpl))]
to the declaration ofWorkInterfaceImpl
. If I do this, I get a different exception in the same place. It is now aNetDispatcherFaultException
with the message:The formatter threw an exception while trying to deserialize the message: There was an error while trying to deserialize parameter http://tempuri.org/:GetInterfaceResult. The InnerException message was 'Error in line 1 position 491. Element 'http://tempuri.org/:GetInterfaceResult' contains data from a type that maps to the name 'http://schemas.datacontract.org/2004/07/ServiceSide:WorkInterfaceImpl'. The deserializer has no knowledge of any type that maps to this name. Consider using a DataContractResolver or add the type corresponding to 'WorkInterfaceImpl' to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding it to the list of known types passed to DataContractSerializer.'. Please see InnerException for more details.
The
InnerException
mentioned is aSerializationException
with the message:Error in line 1 position 491. Element 'http://tempuri.org/:GetInterfaceResult' contains data from a type that maps to the name 'http://schemas.datacontract.org/2004/07/ServiceSide:WorkInterfaceImpl'. The deserializer has no knowledge of any type that maps to this name. Consider using a DataContractResolver or add the type corresponding to 'WorkInterfaceImpl' to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding it to the list of known types passed to DataContractSerializer.
Notice how this seems to indicate that the system is trying to serialize the type. It is not supposed to do that. It is supposed to generate a transparent proxy instead. How do I tell it to stop trying to serialize it?
I tried adding an attribute,
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
, to theWorkInterfaceImpl
class. No effect.I tried changing the attribute
[ServiceContract]
on theIWorkInterface
interface (declared in the shared libraryServiceWorkLibrary
) to[ServiceContract(SessionMode = SessionMode.Required)]
. Also no effect.I also tried adding the following magic
system.diagnostics
element to theWeb.config
inServerSide
:<system.diagnostics> <!-- This logging is great when WCF does not work. --> <sources> <source name="System.ServiceModel" switchValue="Information, ActivityTracing" propagateActivity="true"> <listeners> <add name="traceListener" type="System.Diagnostics.XmlWriterTraceListener" initializeData="c:\traces.svclog" /> </listeners> </source> </sources> </system.diagnostics>
This does generate the
c:\traces.svclog
file as promised, but I’m not sure I can make any sense of its contents. I’ve posted the generated file to pastebin here. You can view this information in a more friendly UI by usingsvctraceviewer.exe
. I did that, but frankly, all that stuff doesn’t tell me anything...
What am I doing wrong?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
我描述的用例不受 WCF 直接支持。
接受的解决方法是返回 EndpointAddress10 指向“其他”接口的服务。然后客户端必须手动创建一个 Channel 来访问远程对象。 WCF 没有正确封装此过程。
演示这一点的示例链接到 MSDN文章“从 .NET Remoting 到 Windows Communication Foundation (WCF)”(查找显示“单击此处下载本文的代码示例”的文本)。此示例代码演示了 .NET Remoting 和 WCF。它定义了一个如下所示的接口:
请注意,返回接口的方法不是契约的一部分,只有返回
EndpointAddress10
的方法才标记为[OperationContract]
。该示例通过 Remoting 调用第一个方法,它按照预期正确创建了一个远程代理,但在使用 WCF 时,它求助于第二个方法,然后使用新端点地址实例化一个单独的 ChannelFactory 来访问新对象。The use-case I am describing is not directly supported by WCF.
The accepted work-around is to return an instance of EndpointAddress10 which points to the service for the “other” interface. The client must then manually create a Channel to access the remote object. WCF doesn’t properly encapsulate this process.
An example that demonstrates this is linked to from the MSDN article “From .NET Remoting to the Windows Communication Foundation (WCF)” (find the text that says “Click here to download the code sample for this article”). This example code demonstrates both .NET Remoting as well as WCF. It defines an interface that looks like this:
Notice that the interface-returning method is not part of the contract, only the one that returns an
EndpointAddress10
is marked with[OperationContract]
. The example calls the first method via Remoting, where it correctly creates a remote proxy as one would expect — but when using WCF it resorts to the second method and then instantiates a separateChannelFactory
with the new endpoint address to access the new object.什么是
MainServiceClient()
?它是将客户端消息编组到服务器的类。您应该查看关于 将接口作为 WCF 中的参数 的相关 SO 帖子。 ServiceKnownTypeAttribute 可能会有所帮助。
会话也可能是您想要的正在寻找与 .NET Remoting 行为相关的
MarshalByRef
。另一种方法(MSDN 论坛)是返回
EndpointAddress
而不是界面本身。WCF 会序列化所有内容 - 无论绑定如何。如果需要与同一系统上的服务通信,应采取的最佳方法是使用 IPC 传输绑定 (net.pipe)。
What is
MainServiceClient()
? It is the class marshaling the client messages to the server.You should take a look at a related SO post on returning interfaces as parameters in WCF. ServiceKnownTypeAttribute may be helpful.
Sessions may also be what you're looking for
MarshalByRef
as it relates to .NET Remoting behaviors.Another approach (as mentioned on MSDN Forums) is to return the
EndpointAddress
of the service interface instead of the interface itself.WCF does serialize everything - regardless of the binding. The best approach you should take if you need to communicate with the service on the same system is to use IPC transport binding (net.pipe).
您尝试做的事情直接违反了 SOA 宗旨:“服务共享架构和契约,而不是类”。这意味着您实际上并未将实现代码从服务传递给其使用者,而只是传递合约本身中指定的返回值。
一般来说,WCF 和 SOA 的主要关注点是互操作性,这意味着任何平台的客户端都应该可以访问服务。 Java 或 C++ 消费者如何能够使用您正在设计的服务?简短的回答是它不能,这就是为什么您会发现通过 SOAP 等消息传递标准序列化此代码即使不是不可能也很困难。
构建此代码的更合适的方法是将 IWorkerInterface 的每个实现托管为自己的服务(毕竟它已被定义为服务契约),并将每个服务公开在不同的端点上。 MainService 不是充当 IWorkerInterface 代理的远程工厂,而是可以充当您已设置的不同服务的端点工厂。端点元数据可以轻松地序列化并通过 IMainService 提供给客户端。然后,客户端可以获取该元数据并通过某些自定义 IServiceProxy 实现,甚至通过 WCF 已提供给您的对象(例如 ChannelFactory)构建远程实现的代理。
What you are trying to do is a direct violation of the SOA Tenet: "Services share schema and contract, not class". What this means it that you don't actually pass implementation code from the service to its consumers, just the return values that are specified in the contract itself.
The main focus of WCF and SOA in general is interoperability, meaning services should be accessible to clients of any platform. How would a Java or C++ consumer be able to use this service you are designing? Short answer is that it couldn't, which is why you will find it difficult if not impossible to serialize this code over messaging standards like SOAP.
A more appropriate way to structure this code would be to host each implementation of IWorkerInterface as its own service (it has been defined as a service contract, after all), and expose each service on a different endpoint. Instead of MainService behaving as remote factory for proxies to an IWorkerInterface, it could act a as an endpoint factory to the different services you have set up. Endpoint metadata could easily be serialized and provided to the client by IMainService. The client could then take that metadata and construct a proxy to the remote implementation, either through some custom IServiceProxy implementation, or even through the objects already provided to you by WCF (such as the ChannelFactory).