Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Runtime Generated WCF Service Exposing .NET or COM Types

4.69/5 (38 votes)
24 Apr 2008CPOL8 min read 1   1.1K  
A WCF service wrapper is generated at runtime around a .NET or COM type to expose its interface.

Introduction

This article shows how to expose public methods and properties of a .NET type or a COM type with the Windows Communication Foundation (WCF) service automatically. All a user should do is the following: create a host process for the service, add to it a reference to a standard assembly described below, add the section <system.serviceModel> defining the service parameters of his/her choice to the .config file, and actually start the service with just one line of code. This line specifies the type you would like to expose and the assembly containing this type.

A WCF service wrapper is generated anew on every start of the host process. If the public interface of an exposed type is changed, then the modified interface will be exposed to the client automatically without any modification in code. When the host process is running, then the Service Reference may be automatically (e.g., with MS Visual Studio) generated on the client side, enabling client access to all the public methods and properties of the exposed type.

Background

The technique of automatic "on-the-fly" service generation is rather straightforward. The component WcfServiceHost loads an assembly containing the type to be exposed and reflects it over the type's public methods and properties. Based on the reflection, a

[ServiceContract]
interface and the service class are generated as a string of C# code in the memory. Each method of the service class implements the appropriate
[OperationContract]
, and actually calls the corresponding method of the exposed type. The generated C# code is built into in-memory assembly by means of a System.CodeDom.Compiler, and a new System.ServiceModel.ServiceHost object containing the service class is created and opened.

When a COM interface is exposed, its Runtime Callable Wrapper (RCW) is used for reflection.

Code Sample

The projects in the attached code sample are presented in the following table:

FolderProjects in FolderProject Description
ServiceHostFolderWcfServiceHostA managed class library responsible for actually creating the WCF service component. It loads the assembly containing the exposed type, reflects it, generates "on-the-fly" a WCF service component assembly, and then creates the service type, constructs its System.ServiceModel. ServiceHost object, and opens it for communication with the client.
ServiceHostContainerFolderServiceHostContainerAn executable console application; serves as a container for the WcfServiceHost component. It instantiates the SvcHost type and calls its CreateServiceHost() method to create a service. The App.config file of the application contains the <system.serviceModel> definition for all generated WCF services.
ComponentNetFolderComponentNetA managed class library implementing a sample of type ComponentNet.Class1 to be exposed with the WCF service.
ComponentNetBaseThese managed class libraries contain the "father" and "grandfather" of ComponentNet.Class1. They are given to illustrate the necessity for reference to all ancestors of the exposed type in the generated code.
ComponentNetBase2
ComponentComFolderComponentComCOM object exposing its custom, automation-compatible interface, IClass2.
DummyComInteropThis is just a dummy managed console application. Its only purpose is to automatically produce and test RCW on the ComponentCom COM object. Actually, the tlbimp.exe utility is used under the hood.
ClientFolderConsoleClientA client console application. It holds Service References to all generated WCF services, and provides a very simple sample of the services consumption.

Let's examine how the sample works. Our first task is to take a type ComponentNet.Class1 located in the assembly ComponentNet.dll and expose its public methods and properties. The type ComponentNet.Class1 is derived from the type ComponentNetBase.BaseClass1 located in ComponentNetBase.dll, and the type ComponentNetBase.BaseClass1 is derived in its turn from the type ComponentNetBase2.BaseClass2 located in ComponentNetBase2.dll:

ComponentNetBase2.BaseClass2 <-- ComponentNetBase.BaseClass1 <-- ComponentNet.Class1

The appropriate service is hosted by the ServiceHostContainer.exe. This assembly refers to the WcfServiceHost assembly, and creates the required service with just one line of code:

C#
new SvcHost().CreateServiceHost("ComponentNet.dll", "Class1", true);

The method CreateServiceHost() of the SvcHost type has three parameters, namely, path to an assembly with the type to be exposed, name of this type, and a boolean parameter indicating whether the service supports sessions or not. For simplicity, all service modules are placed in the same directory; so, the first parameter is actually reduced to just the assembly file name. The method SvcHost.CreateServiceHost() internally calls the static method WcfGenerator.GenerateServiceType(). The latter loads ComponentNet.dll, reflects it over, and generates C# code with the GenerateInterfaceAndClass() static private method. This method looks ugly enough, but does the job: its output C# code contains the appropriate ServiceContract and the class implementing it. Next, the static method WcfGenerator.GenerateServiceType() builds the C# code to an in-memory service assembly. Optionally, by setting the method's last parameter to true, you order to put debug information including the generated C# code into the disk files. By the way, it is possible to get in-memory service assembly directly in Intermediate Language (MSIL) using classes of the System.Reflection.Emit namespace, without prior C# code generation. But I found it easier to generate code in a more readable language, which is also easier to change and debug. Finally, an instance of the System.ServiceModel.ServiceHost type is created, and with its Open() method, put to opened state.

The ServiceHost instance is created using the App.config file (which becomes the ServiceHostContainer.exe.config file). There is only a wsHttpBinding implemented in the sample, but endpoints with the other binding configurations may be easily added. Services describe themselves to clients with standard Metadata Exchange (mex) endpoints.

To run the attached sample without compilation, first, you need to download and unzip the demo project in some directory, then run its RunServices.cmd file (which registers a COM object, starts ServiceHostContainer.exe, and later, after it will be closed, unregisters the COM), then run the ConsoleClient.exe client application. Please wait until all services start and the message "Press any key to stop Services..." appears in the ServiceHostContainer.exe console, and then press any key in the client console to let ConsoleClient.exe proceed. In the midst of its execution, the client application "falls asleep" for some time in order to test the lifetime control feature.

Discussion

Code Sample Limitations

For simplicity, the code sample in this article has the following limitations: an instance of the exposed type should be created with the default constructor (in the case of the COM exposed type, this requirement is compulsory for creating the RCW anyway), and parameters and return values of the exposed methods should not be of user-defined types. Both limitations could be lifted with some extra generated code. For example, user-defined parameters should be either originally attributed with [DataContract] and [DataMember] (meaning additional requirements to the original component), or serialized with more generated code.

PerSession vs. PerCall

Service may be created with either the PerSession or the PerCall Instance Context Mode (the last parameter in the SvcHost.CreateServiceHost() method set to true or false, respectively). In the PerSession case, a session between one client and an instance of an exposed type is created. The exposed type constructor is called once per session, on its start. The state of the exposed type instance is preserved during the entire session and available to the client. So, if the exposed type object state should be kept, then PerSession behavior is an option. This requirement probably presents in most of the cases. Even if the state preservation is not an issue, once created, the object on the server side exists throughout entire session. In the PerCall case, the instance of the exposed type is created on each client call (this can be seen by counting the number of constructor calls in the output of ServiceHostContainer.exe in the sample). So, the PerCall approach is suitable for those [rare] cases when a stateless exposed type is called seldom, and therefore there is no need to keep a "stand-by" instance of the exposed type in the memory all the time.

In the code sample, in the case of PerCall behaviour, a _PerCall suffix is automatically added to the generated service class name.

Exposed Type Instance Lifetime

In the PerSession mode, there is no way to explicitly destroy the exposed type instance in the code sample. Its lifetime can be controlled by defining the value of the receiveTimeout binding parameter in the service .config file. This may be tested by playing with the values of receiveTimeout and the argument of the Sleep() method in the ConsoleCleint project. If sleeping time exceeds the receive timeout, then by the moment of the client call, the appropriate instance of the service object is no longer valid, and the client throws an exception.

Conclusion

A technique for automatic generation of WCF interfaces for .NET and COM types' public methods and properties is presented. The type to be exposed is reflected over, and an appropriate

[ServiceContract]
interface and the service class implementing it are generated on-the-fly. Each method of the service class calls the correspondent method of the exposed type. Each service object may be created with either the PerSession or the PerCall behavior. A client proxy is generated automatically using the service Metadata Exchange. The code sample illustrates the technique for .NET and COM exposed types.

Thanks

My thanks go to my respected colleagues Ronen Halili, Eran David, Barak Cohen and Alex Katz for the useful discussion on the subject of this article.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)