.NET Remoting - Part III/IV...
Part III/IV: Remoting Applied: configuration issues and interface assemblies.
Knowledge assumed for series: VB.NET, VS.NET, Parts I and II
By: Chris Sully
Date: October 14, 2003
Introduction
Remoting provides a very flexible environment for distributed applications in the .NET arena. In part one
I introduced the background to distributed applications and the .NET remoting architecture that aims to
support distributed applications. In part two we looked at some examples of the application of this theory,
in particular we created a remotable class, server activated objects and a client activated object. In this
third part we continue to look at the practicalities with an examination of related configuration issues
and the use of interface assemblies. In part four we'll look at the role of IIS and the topic of
asynchronous remoting.
Declarative Configuration
In the examples up until this point the remoting framework has been configured programmatically, i.e. the
channel and remote object have been registered. There is the additional option of declarative configuration
via an XML-based configuration file. This has the distinct advantage that any configuration changes are
automatically picked up - there is no need to recompile code to effect changes.
The files utilised are those standard to .NET � machine.config for machine level configuration and
web.config for application specific configuration. The section of interest here is the
<system.runtime.remoting> section and it has the following child elements:
| Child Elements |
| Element | Description |
| <application> |
Contains information about remote objects the application consumes and exposes. Can occur once in the
<system.runtime.remoting> element. |
| <channels> |
Contains channel templates. Can occur once in the <system.runtime.remoting> element. |
| <channelSinkProviders> |
Contains providers of channel sinks to be inserted in the channel sink chain. Can occur once in the
<system.runtime.remoting> element. |
| <debug> |
Requires the .NET Remoting system to load all types when the application starts, to make it easy to
catch typing errors in the configuration file. Can occur once in the <system.runtime.remoting>
element. |
Looking at the application element:
<application name="AppName">
<lifetime/>
<channels/>
<service/>
<client/>
<soapInterop/>
</application>
|
| Optional Attributes |
| Attribute | Description |
| name |
Names the application. You cannot use this attribute when hosting a remote type in Internet Information
Services (IIS). In other hosting scenarios, the name becomes a part of the activation URL. For details,
see Activation URLs. |
| Child Elements |
| Element | Description |
| <lifetime> |
Contains information about the lifetime of all remotable objects. Can occur once in the
<application> element. |
| <service> |
Contains objects the application exposes. Can occur one or more times in the <application>
element. |
| <client> |
Contains objects the application consumes. Can occur one or more times in the <application>
element. |
| <channels> |
Contains channels that the application uses to communicate with remote objects. Can occur once in the
<application> element. |
| <soapInterop>
| Contains type mappings used with SOAP. Can occur once in the <application> element. |
and an example might be:
<configuration>
<system.runtime.remoting>
<application>
<lifetime
leaseTime="10S"
sponsorshipTimeout="0S"
renewOnCallTime="5S"
leaseManagerPollTime="5S"
/>
<service>
<wellknown
type="ServerActivatedType, RemoteAssembly"
objectUri="ServerType.rem"
mode="Singleton"
/>
</service>
<channels>
<channel port="8080" ref="http"/>
</channels>
</application>
<debug loadTypes="true"/>
</system.runtime.remoting>
</configuration>
|
Most of this relates to what we've done programmatically earlier. You can see this is a Singleton SAO. For
a remoting client you would specify a <client> section rather than a <service> section.
As an example we'll create a singleton SAO similar to the SAOs that we implemented in the last article
(in fact we implemented a singlecall SAO � I'm choosing the very similar singleton here for completeness).
In this example we'll be using a configuration file. Add another console application project to the
solution. I�ve called it DBConnectSingletonServerConfig. Add references to the remotable class assembly and
to System.Runtime.Remoting, rename the module to DbConnectSingletonServer and add the following code:
Imports System.Runtime.Remoting
Module DbConnectSingletonServer
Sub Main()
'load remoting configuration">
RemotingConfiguration.Configure("DBConnectSingletonServer.exe.config")
Console.WriteLine("Started server in the Singleton mode")
Console.WriteLine("Press <ENTER> to terminate server...")
Console.ReadLine()
End Sub
End Module
|
Add a new XML file to the project naming it DBConnectSingletonServer.exe.config.
Modify this file to contain the following code:
<configuration>
<system.runtime.remoting>
<application>
<service>
<!-- set the activation mode, remotable object and its URL -->
<wellknown mode="Singleton" type= "RemotingDB.DbConnect, RemotingDB" objectUri="DbConnect" />
</service>
<channels>
<!-- set the channel and port -->
<channel ref="tcp" port="54321" />
</channels>
</application>
</system.runtime.remoting>
</configuration>
|
This should be self-explanatory, particularly if you compare with the remoting servers we created in the
previous article � it does exactly the same except declaratively rather than programmatically.
Move the file to the projects bin folder. Build the project.
You've just created a remoting server capable of registering the DbConnect class for remote invocation
using the Singleton activation mode via the settings in the config file.
Now configure your solution to use this remoting server, i.e. set DbConnectSingletonServer to be the
startup project and specify that the project should be started without debugging. Do the same for the
remoting client - you can use the same client as previously: DbConnectClientSAO. Make sure they are
configured in this order. Now start the solution without debugging.
The configuration of a remoting client is similar to that of a remoting server. You configure the
<client> element of the config file rather than the <service> element. I'll leave this for the
reader to explore.
Interface Assemblies
As introduced in the previous article in this series, interface assemblies are a preferable way of
facilitating compilation of remoting clients without the need for the client project to have the assembly
implementation available. An interface does not contain implementation detail, only information concerning
members exposed by the class.
In this section we'll look at how to replace the assemblies used by client projects with their equivalent
interfaces. Also introduced shall be the soapsuds tool, which can automatically create the interface for
a remotable class.
The steps we'll need to take to produce a compete solution using SAOs on this basis are:
- create the interface assembly
- create a remotable object that implements this interface assembly
- create the remoting server to register the remotable object that implements an interface assembly
- create a remoting client that uses an interface instead of the implementation
The majority of the code presented won't be significantly different from that already presented based on
the assembly itself. Taking each step in turn:
1. The interface assembly
Create a new blank solution and add a VB.NET class library project named RemotingDBInterface; rename the
default class library file to IDBConnect.vb. Replace the code with:
Imports System
Imports System.Data
Public Interface IDbConnect
Function ExecuteQuery(ByVal strQuery As String) As DataSet
End Interface
|
Build the project. An assembly that contains the definition of the IDBConnect interface has been created.
2. The remotable object
The next step is to implement the IDBConnect interface in a class named DBConnect which must adhere to
the contract defined by the interface.
Add a new class library project to the solution and call it RemotingDB. Add references to
System.Runtime.Remoting and RemotingDBInterface. Rename the default class to DBConnect.vb and replace
the code therein with:
Imports System
Imports System.Data
Imports System.Data.SqlClient
Imports RemotingDBInterface
' Marshal-by-Reference Remotable Object
Public Class DbConnect
Inherits MarshalByRefObject
' Implement the IDbConnect interface
Implements IDBConnect
Private sqlconn As SqlConnection
' Default constructor connects to the Northwind database
Public Sub New()
sqlconn = New SqlConnection("data source=(local); initial catalog=Northwind;" & _
"integrated security=SSPI")
Console.WriteLine("Created a new connection to the Northwind database")
End Sub
' Parameterized constructor connects to the specified database
Public Sub New(ByVal DbName As String)
sqlconn = New SqlConnection("data source=(local); initial catalog=" & DbName & ";" & _
"integrated security=SSPI")
Console.WriteLine("Created a new connection to the " & DbName & " database")
End Sub
Public Function ExecuteQuery(ByVal strQuery As String) As DataSet _
Implements IDbConnect.ExecuteQuery
Console.Write("Starting to execute the query...")
' Create a SqlCommand to represent the query
Dim sqlcmd As SqlCommand = sqlconn.CreateCommand()
sqlcmd.CommandType = CommandType.Text
sqlcmd.CommandText = strQuery
' Create a SqlDataAdapter object
' to talk to the database
Dim sqlda As SqlDataAdapter = New SqlDataAdapter
sqlda.SelectCommand = sqlcmd
' Create a DataSet to hold the results
Dim ds As DataSet = New DataSet
Try
' Fill the DataSet
sqlda.Fill(ds, "Results")
Catch ex As Exception
Console.WriteLine(ex.Message, "Error executing query")
End Try
Console.WriteLine("Done.")
ExecuteQuery = ds
End Function
End Class
|
Build the project.
This is very similar to our earlier remotable object with the addition that the class is also implementing
the IDBConnect interface in addition to deriving from the MarshalByRefObject class.
This remotable object can be exposed to clients via the remoting framework exactly as per earlier examples
as the interface usage is encapsulated in the object implementation.
3. The remoting server
See article 2,
Creating a SAO- SingleCall SAO. The implementation is exactly the same. As before the remoting server
will register the remotable object. In this case however, the remotable object implements an interface
assembly.
In this instance name the console application DBConnectSingleCallServer. Building the project then creates
a remoting server capable of registering RemotingDB.DBConnect, the remotable object that implements the
RemotingDBInterface.IDBConnect interface for remote invocation using the singlecall activation mode.
4. The remoting client
Now we want to create a remoting client that uses an interface instead of the actual implementation. This
is achieved by simply including a reference to the interface assembly rather than that of the implementation
assembly at the client side but there are a couple of further changes to be made in the code necessary
because of the use of an interface assembly. The client will then extract the necessary type information
and metadata for compiling and running the program from the interface.
Add a new VB.NET Windows application named DBConnectClientSAOIf to the solution. Add references to System.Runtime.Remoting and RemotingDBInterface. Add a Windows form to the project naming it DBConnectClient, setting it as the startup object for the project. The user interface implementation is as per article 2 (LINK), Creating a SAO- SingleCall SAO, the code behind differs slightly:
Imports System.Runtime.Remoting
Imports System.Runtime.Remoting.Channels
Imports System.Runtime.Remoting.Channels.Tcp
Imports RemotingDBInterface
Public Class DbConnectClient
Inherits System.Windows.Forms.Form
'declare a remote object
Dim dbc As IDbConnect
Private Sub btnExecute_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnExecute.Click
Try
'invoke a method on the remote object
Me.dgResults.DataSource = dbc.ExecuteQuery(Me.txtQuery.Text)
dgResults.DataMember = "Results"
Catch ex As Exception
MessageBox.Show(ex.Message, "Query Execution Error")
End Try
End Sub
Private Sub DbConnectClient_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
'create and register a TCP client channel
Dim channel As TcpClientChannel = New TcpClientChannel
ChannelServices.RegisterChannel(channel)
'instantiate the remote class
dbc = CType(Activator.GetObject(GetType(IDbConnect), _
"tcp://localhost:54321/DBConnect"), IDbConnect)
End Sub
End Class
|
Build the project. Set up the solution to run as previously. Run the solution and check it is working as
you expect � exactly as per the earlier example but now using an interface assembly.
The main difference apparent in the code is the way the remote object is instantiated via the Activator
class. This class contains methods to create types of objects locally or remotely, or obtain references
to existing remote objects. Key methods are:
Activator.GetObject, which creates a proxy for a currently running remote object, server-activated
well-known object, or XML Web service
CreateInstance, which creates an instance of the specified type using the constructor that best matches
the specified parameters. This method is useful for activating client-activated objects as we�ll see shortly.
Soapsuds
We've just introduced the use of interface assemblies which have the advantage that you do not have to
distribute the actual implementation to clients using your remotable objects. However, using this approach
you still have the overhead of having to distribute the interface assembly to.
The soapsuds tools will, given the URL of a remotable object, generate an interface assembly for the
remote object. This saves considerable resources from having to distribute the interface to all of the
clients. A typical usage might be:
soapsuds �NoWrappedProxy
�url:http://MyServer.com/DbConnect?wsdl
-outputAssemblyFile:RemotingDBInterface.dll
The NoWrappedProxy switch instructs SoapSuds to generate an unwrapped proxy.
Here the URL switch specifies the URL of the remote object. You normally have to append the ?wsdl to the
URL to allow soapsuds to generate the metadata from the URL.
The outputAssemblyFile switch specifies the name of the file in which you want the output assembly to
be created.
By default soapsuds generates wrapped proxies. These proxies store extra information like channel
formatting and the URL within the proxy itself. This can save implementing these details but it also
means that you cannot write code to specify this information at a later date so is not recommended ...
hence the use of unwrapped proxies.
I'll leave as an exercise for the reader the task of using soapsuds to generate an interface assembly
for our application. Here a couple of pointers however:
- you'll need to expose the remotable object over HTTP to use the Soapsuds tool
- the interface class generated by the tool allows you to directly use the name of the remotable class.
This means that you can access the remotable class at the client as if you have a direct reference to the
class, i.e. you can use the RegisterWellKnownClientType() method of the RemotingConfiguration class to
register the remote class on the client instead of using Activator.GetObject()
- remember to make sure the remoting server is up and running before using Soapsuds!
Interface Assemblies and CAOs
Neither of the options described thus far will work with CAOs due to the fact that they can use non-default
constructors and an interface cannot contain the declaration of a constructor as it does with a method or
property. This problem is generally solved using the technique of 'abstract factory patterns' where you
declare as many methods as there are constructors in the remotable class. Each of these methods is
responsible for creating an object in a way defined by its corresponding constructor. 'Factory' because
the technique allows you to create or 'manufacture' objects in different ways.
We won't go into significant detail of the implementation here but the process involves the following:
- Create an interface and remotable class as previously, e.g. IDBConnect and DBConnect.
- Create an interface that declares as many methods as there are constructors in the remotable class,
IDBConnectFactory say.
- Create a remoting server that registers a remotable class (DBConnectFactory) that derives from the
MarshalByRefObject class and implements IDBConnectFactory in which the methods to create DBConnect are
implemented based on the supplied parameters and returns the corresponding created instance.
- Create a client that connects to the server and creates an instance of DBConnectFactory.
Then when a method is invoked at the client that corresponds to a constructor on the remotable object the
return value of the 'constructor method' will be a remotable object of type DBConnect.
As you can this is a couple of degrees up in complexity involving extra steps and a significant change
to the remoting server implementation as well as minor changes to the client implementation.
Conclusion
In this article we took a look at two main remoting topics: declarative configuration and interface
assemblies. In the final article in this series we complete our look at remoting with a consideration of
the use of IIS as an activation agent as well as asynchronous remoting.
References
.NET SDK
Developing XML WebServices and Server Components with VB.NET and the .NET Framework
Mike Gunderloy
Que