如何在 ASP.NET 应用程序中创建可返回接口实例作为透明代理的 WCF Web 服务

发布于 2025-01-03 06:23:10 字数 5033 浏览 0 评论 0原文

我的用例:

  • 我已经有一个可用的 ASP.NET 应用程序
  • ,我想实现一个新的 Web 服务作为该应用程序的一部分
  • ,我应该使用 WCF 服务 (*.svc), ASP.NET Web 服务 (*.asmx)
  • 该服务需要有一个操作,我们将其称为 GetInterface(),它返回接口的实例。该实例必须驻留在服务器上,不能被序列化到客户端;在该接口上调用的方法必须在服务器上执行。

以下是我的尝试(请告诉我哪里出错了):

  • 为了测试这一点,我创建了一个名为 ServiceSide 的新 ASP.NET Web 应用程序 项目.

  • 在该项目中,我使用“添加 → 新项目”添加了一个WCF 服务。我将其命名为MainService。这创建了一个 MainService 类以及一个 IMainService 接口。

  • 现在我创建了一个名为 ServiceWorkLibrary 的新类库项目,仅包含要在客户端和服务器之间共享的接口声明,没有其他内容:

    <前><代码>[服务合同] 公共接口 IWorkInterface { 【运营合同】 int GetInt(); }
  • 回到 ServiceSide,我替换了 IMainService 接口中默认的 DoWork() 方法及其在 ServiceSide 中的实现。 code>MainService 类,我也添加了共享 IWorkInterface 的简单实现。它们现在看起来像这样:

    <前><代码>[服务合同] 公共接口IMainService { 【运营合同】 IWorkInterface GetInterface(); } 公共类 MainService :IMainService { 公共 IWorkInterface GetInterface() { 返回新的 WorkInterfaceImpl(); } } 公共类 WorkInterfaceImpl :MarshalByRefObject、IWorkInterface { 公共 int GetInt() { 返回 47; } }

    现在运行这个应用程序“有效”,因为它在浏览器中为我提供了默认的 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 a MainService class as well as an IMainService 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 default DoWork() method in the IMainService interface as well as its implementation in the MainService class, and I also added a simple implementation for the shared IWorkInterface. 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 the ServiceWorkLibrary project and added the reference to it from ClientSide.

  • Then I ran the above svcutil.exe call. This generated a MainService.cs and an output.config, which I added to the ClientSide 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 to App.config.

  • I notice that the return type of GetInterface() is object instead of IWorkInterface. Anyone know why? But let’s move on...

  • Now when I run this, I get a CommunicationException when calling GetInterface():

    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 of WorkInterfaceImpl. If I do this, I get a different exception in the same place. It is now a NetDispatcherFaultException 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 a SerializationException 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 the WorkInterfaceImpl class. No effect.

  • I tried changing the attribute [ServiceContract] on the IWorkInterface interface (declared in the shared library ServiceWorkLibrary) to [ServiceContract(SessionMode = SessionMode.Required)]. Also no effect.

  • I also tried adding the following magic system.diagnostics element to the Web.config in ServerSide:

      <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 using svctraceviewer.exe. I did that, but frankly, all that stuff doesn’t tell me anything...

What am I doing wrong?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

很快妥协 2025-01-10 06:23:10

我描述的用例不受 WCF 直接支持。

接受的解决方法是返回 EndpointAddress10 指向“其他”接口的服务。然后客户端必须手动创建一个 Channel 来访问远程对象。 WCF 没有正确封装此过程。

演示这一点的示例链接到 MSDN文章“从 .NET Remoting 到 Windows Communication Foundation (WCF)”(查找显示“单击此处下载本文的代码示例”的文本)。此示例代码演示了 .NET Remoting 和 WCF。它定义了一个如下所示的接口:

[ServiceContract]
public interface IRemoteFactory
{
    IMySessionBoundObject GetInstance();
    [OperationContract]
    EndpointAddress10 GetInstanceAddress();
}

请注意,返回接口的方法不是契约的一部分,只有返回 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:

[ServiceContract]
public interface IRemoteFactory
{
    IMySessionBoundObject GetInstance();
    [OperationContract]
    EndpointAddress10 GetInstanceAddress();
}

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 separate ChannelFactory with the new endpoint address to access the new object.

泪痕残 2025-01-10 06:23:10

什么是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).

遗弃M 2025-01-10 06:23:10

您尝试做的事情直接违反了 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).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文