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?