4

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?

Timwi
  • 65,159
  • 33
  • 165
  • 230
  • the key difference in the MSDN example is that the proxy is generated at the service level - not at the method level. You just have to expose your method interface as a service endpoint instead. – SliverNinja - MSFT Feb 07 '12 at 16:26

3 Answers3

1

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.

Timwi
  • 65,159
  • 33
  • 165
  • 230
0

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).

Community
  • 1
  • 1
SliverNinja - MSFT
  • 31,051
  • 11
  • 110
  • 173
  • Error: Attribute `DataContract` is not valid on this declaration type. It is only valid on 'class, struct, enum' declarations. — But besides that, it also doesn’t make sense because this interface *does not* encapsulate data, it encapsulates operations... Thanks for your answer though. – Timwi Feb 07 '12 at 15:23
  • Typically the interfaces are implemented as Operation Contracts. I've not seen WCF using `OperationContracts` of `OperationContracts`. Typically I've only seen it as 1-to-1. Each service implements a single `OperationContract`. – SliverNinja - MSFT Feb 07 '12 at 15:25
  • Are you implying that the use-case I’m describing is not supported by WCF? That’s hard to believe because it *is* supported by .NET Remoting (via transparent proxy objects) and WCF is really supposed to be a superset of the functionality... – Timwi Feb 07 '12 at 15:32
  • @Timwi - you should put the [`DataContract`](http://msdn.microsoft.com/en-us/library/system.runtime.serialization.datacontractattribute.aspx) on `WorkInterfaceImpl` and mark it as a [`KnownType`](http://msdn.microsoft.com/en-us/library/ms730167.aspx) in the MainService `ServiceContract` – SliverNinja - MSFT Feb 07 '12 at 15:41
  • @Timwi - see answer edits. It seems your proxy approach should work - using [WCF Sessions](http://social.msdn.microsoft.com/Forums/eu/wcf/thread/1072b10d-a6ea-434a-b894-7ae8fac40e10) seems to be the key. – SliverNinja - MSFT Feb 07 '12 at 15:51
  • Thanks, tried that — but that just takes me straight back to getting the `CommunicationException` (“The underlying connection was closed: The connection was closed unexpectedly.”). – Timwi Feb 07 '12 at 15:52
  • The documentation for `ServiceKnownTypeAttribute` says “Specifies known types to be used by a service when serializing or deserializing.”. I am *not* trying to serialize anything... – Timwi Feb 07 '12 at 15:53
  • I saw the SO question you linked to before I posted this, btw. The accepted answer on that post basically claims that you can’t do it, but it gives reasons for that which are clearly false. I’ve commented on that answer now. – Timwi Feb 07 '12 at 15:58
  • 1
    You ARE trying to serialize EVERYTHING. You can't make a call to a remote service without serialization, because these classes have to be transformed into something that can be sent over the wire. – mclark1129 Feb 07 '12 at 15:59
  • @MikeC: No, only the method call and its parameters (and its return value, if any) need to be serialised — and those are all strings, so trivially serialisable. It is trying to serialise a type that I don’t want serialised, but proxied. – Timwi Feb 07 '12 at 16:08
  • @Timwi - this issue seems to have come up from many people moving from .NET Remoting to WCF. The solution seems to be you cannot return an interface directly, but you can return an endpoint address pointing to the implementation of that service interface (another WCF ServiceHost). – SliverNinja - MSFT Feb 07 '12 at 16:11
  • @Timwi: The return value of IMainService.GetInterface() is an IWorkerInterface, specifically WorkerInterfaceImpl. You said yourself that you are serializing the return value, so you are in fact attempting to serialize an instance of WorkerInterfaceImpl. – mclark1129 Feb 07 '12 at 16:15
  • @SliverNinja: I did realise that I could do that, but it seems to be a step backwards surely? That’s two levels of abstraction *less* (i.e. worse) than .NET Remoting: As a consumer of the WCF API, I shouldn’t have to worry about endpoints and ServiceHosts, and the consumers of *my* API (my web service) most definitely shouldn’t... – Timwi Feb 07 '12 at 16:16
  • @MikeC: No, I am not. Re-read the first section of my question (the “use-case”). The instance of `WorkInterfaceImpl` shall reside on the server, not be passed to the client. – Timwi Feb 07 '12 at 16:17
  • @Timwi: I understand what you are attempting to do, what I'm trying to help you understand is what you are ACTUALLY doing. When you call "return New WorkerInterfaceImpl();" on the server side that literally says "Return a new instance of WorkerInterfaceImpl to the caller." Not, "Create a new serializable proxy to this new instance and return that to the caller" – mclark1129 Feb 07 '12 at 16:27
0

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).

mclark1129
  • 7,532
  • 5
  • 48
  • 84
  • What I am trying to do is **exactly** according to the SOA tenet you quoted. I am trying to return an interface that specifies a contract, **not** a class. A Java or C++ consumer would use this service the same way it uses any other web service: by remote-calling the methods defined in the contract, **not** by serialising anything. – Timwi Feb 07 '12 at 16:13
  • 2
    What I think that you don't understand is that the contract you are consuming here is actually IMainService, not IWorkerInterface. Even if you were just intending on the returned object to be a proxy, even that PROXY needs an IMPLEMENTATION underneath in order to do it's remoting work. There's no magic in WCF that says, "If the return value is an interface, and the underlying implementation contains .NET code, then generate the return value in some way so that the client knows it needs to generate an implementation of the proxy on the fly." – mclark1129 Feb 07 '12 at 16:21
  • It seems that you really do not know what a transparent proxy is. .NET *does* have special support to *transparently* call methods on a *remote* object when the client has *nothing but the interface type* (i.e. no class that implements the interface). You *really do* end up with an object whose run-time type *is* the interface type (*not* a class that *implements* it) and all the proxying is done completely automatically by the CLR. The WCF method `ChannelFactor.CreateChannel()` and the .NET Remoting method `Activator.GetObject()` generate such transparent proxies. – Timwi Feb 07 '12 at 16:57
  • So, in summary, there *is* exactly the magic that you describe, at least in .NET Remoting. The “implementation that does the remoting work” is in the CLR. But besides that, on a higher level, what I think you don’t understand is that the contract I am defining (and the client shall be consuming) is neither just `IMainService` nor just `IWorkInterface`, but it comprises both. The former is a stand-in for a static method (the “factory pattern”), while the latter is to encapsulate server-side state, both of which shall be accessible remotely. – Timwi Feb 07 '12 at 17:01