How to create a WCF web service in an ASP.NET application that can return interface instances as a transparent proxy

My use case:

  • I already have a working ASP.NET application
  • I would like to implement a new web service as part of this application.
  • I should use the WCF service (* .svc), not the ASP.NET web service (*. Asmx)
  • The service must have one operation, it calls its GetInterface() , which returns an instance of the interface. This instance should be located on the server, and not be serialized for the client; methods called by this interface must be executed on the server.

Here is what I tried (please tell me where I made a mistake):

  • To test this, I created a new ASP.NET web application project called ServiceSide .

  • As part of this project, I added a WCF service using Add → New Item. I called it MainService . This created both the MainService class and the IMainService interface.

  • Now I created a new class library project called ServiceWorkLibrary to contain only an interface declaration that should be shared between the client and server, and nothing:

     [ServiceContract] public interface IWorkInterface { [OperationContract] int GetInt(); } 
  • In ServiceSide I replaced the DoWork() method in the IMainService interface, as well as its implementation in the MainService class, and also added a simple implementation for the general IWorkInterface . Now they 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 launching 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 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 create a configuration file and a code file that contains the client class. Add two files to the client application and use the generated client class to call the Service. For instance:

  • So, to the client. 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 a link to it from ClientSide .

  • Then I made a call to svcutil.exe . This generated MainService.cs and 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 no longer works with cryptic exception in the constructor call. I managed to fix this by renaming output.config to App.config .

  • I noticed that the return type of GetInterface() is equal to object instead of IWorkInterface . Does anyone know why? But let's move on ...

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

    The connected connection was closed: the connection was unexpectedly closed.

How to fix this to get the transparent IWorkInterface proxy that I expect?

Ive tried things

  • I tried adding [KnownType(typeof(WorkInterfaceImpl))] to the WorkInterfaceImpl . If I do this, I get another exception in the same place. Now this is a NetDispatcherFaultException message with the message:

    Formatting threw an exception when trying to deserialize the message: an error occurred while trying to deserialize the http://tempuri.org/:GetInterfaceResult parameter. The InnerException message was "Error at position 1 of position 491. The element" http://tempuri.org/:GetInterfaceResult "contains data from a type that maps to the name" http://schemas.datacontract.org/2004/07/ServiceSide: WorkInterfaceImpl. Deserializer does not know any type that maps to this name. Consider using a DataContractResolver or add a type corresponding to "WorkInterfaceImpl" in the list of known types, for example, using the KnownTypeAttribute attribute or adding it to the list of known types passed to the DataContractSerializer. '. See InnerException for more information.

    The specified InnerException is a SerializationException with the message:

    Error at position 1 of position 491. The element 'http://tempuri.org/:GetInterfaceResult' contains data from a type that maps to the name 'http://schemas.datacontract.org/2004/07/ServiceSide: WorkInterfaceImpl. Deserializer does not know any type that maps to this name. Consider using a DataContractResolver or add a type corresponding to "WorkInterfaceImpl" in the list of known types, for example, using the KnownTypeAttribute attribute or adding it to the list of known types passed to the DataContractSerializer.

    Notice how this indicates that the system is trying to serialize the type. He should not do that. Instead, it is supposed to create a transparent proxy. How can I tell him not to try to serialize it?

  • I tried to add the [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)] attribute to the [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)] class. There is no effect.

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

  • I also tried adding the following magic system.diagnostics element to 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 generates the c:\traces.svclog , as promised, but I'm not sure I can understand its contents. Here I sent the generated file to pastebin. You can view this information in a friendlier user interface using svctraceviewer.exe . I did this, but honestly, all this does not tell me anything ...

What am I doing wrong?

+4
source share
3 answers

The use case that I am describing is not directly supported by WCF.

The decision made is to return an instance of EndpointAddress10 that points to the service for the "other" interface. Then the client must manually create a channel to access the remote object. WCF does not encapsulate this process.

An example demonstrating this is related to the MSDN article, “From .NET Remoting to Windows Communication Foundation (WCF)” (find text that reads, “Click here to download sample code for this article”). This code example demonstrates both .NET Remoting and WCF. It defines an interface that looks like this:

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

Note that the interface return method is not part of the contract, only the one that returns EndpointAddress10 is marked [OperationContract] . In the example, the first method is called using Remoting, where it correctly creates the remote proxy server, as expected, but when using WCF, it calls the second method and then creates a separate ChannelFactory with a new endpoint address to access the new object.

+1
source

What is MainServiceClient() ? This is the class that marshals client messages to the server.

You should take a look at the related SO entry on returning interfaces as parameters in WCF . ServiceKnownTypeAttribute may be useful.

Sessions can also be what you are looking for MarshalByRef , as this applies to .NET Remoting behaviors.

Another approach (as mentioned in the MSDN Forums ) is to return the EndpointAddress the service interface, not the interface itself.

WCF serializes everything - regardless of binding. The best approach you should take if you need to communicate with a service on the same system is to use the IPC transport binding ( net.pipe ).

0
source

What you are trying to do is a direct violation of the SOA principle: "Service access scheme and contract, not class." This means that you do not actually pass the implementation code from the service to your consumers, but only the return values ​​specified in the contract itself.

The main focus of WCF and SOA as a whole is compatibility, that is, services should be available to customers of any platform. How can a Java or C ++ user use this service you are designing? The short answer is that it cannot, so it will be difficult, if not impossible, to serialize this code compared to messaging standards such as SOAP.

A more appropriate way to structure this code would be to place each implementation of IWorkerInterface as its own service (after all, it was defined as a service contract) and expose each service to a different endpoint. Instead of the MainService acting as a remote factory for IWorkerInterface proxies, it could act as a factory endpoint for the different services that you configured. Endpoint metadata can be easily serialized and provided to the IMainService client. Then the client could take this metadata and create a proxy for the remote implementation, either through some IServiceProxy custom implementation, or even through objects already provided to you by WCF (for example, ChannelFactory).

0
source

Source: https://habr.com/ru/post/1395246/


All Articles