.NET Remoting - Part II...
Part II: Practicalities: Creating a remotable class, SAOs and CAOs.
Knowledge assumed for series: VB.NET, VS.NET, Windows Forms, Part I.
Remoting provides a very flexible environment for distributed applications in the .NET arena. In part one of this series of four articles (note that this is an extension of the initially planned three as indicated in article I) I introduced the background to distributed applications and the .NET remoting architecture that aims to support such applications. In this article and the next two articles in the series we'll look at some examples of the application of this theory. In particular in this article we shall be creating a remotable class, a server activated object and a client activated object. The remaining topics for articles III and IV will then be declarative configuration issues, interface assemblies, the role of IIS and asynchronous remoting.
Remotable classes are created by inheriting from the MarshalByRefObject class. Our example class is going to connect and retrieve data from as instance of SQLServer. In our implementation this instance, somewhat artificially for a remoting scenario, shall be local. If this isn’t your scenario and your database isn't local you'll need to amend the corresponding occurrences of connection string information in the code. Similar applies if you're not using integrated security.
Note that this series of examples is based on those presented in Mike Gunderloy’s 'Developing XML Web Services and Server Components with VB.NET and the .NET Framework' (see references), a book I can recommend, particularly if studying for Microsoft exam 70-310.
The first thing we need to do is create a new VS.NET solution in which to create the projects we'll need. Next add a VB.NET class library project to this solution named RemotingDb, rename the default class library to DBConnect and add the following code:
|
Imports System Imports System.Data Imports System.Data.SqlClient 'Marshal-By-Reference remotable object Public Class DbConnect Inherits MarshalByRefObject 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 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 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 using the DataAdapter 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. The implementation is simple so far: as you can see it's just a standard class that inherits from the MarshalByRefObject class to provide the necessary infrastructure. We now have a remotable class but for it to be useful we now need to connect it to the remoting framework.
A remotable class is usually connected with the remoting framework through a separate server application, a remoting server. This application listens for the client request on a specified communication channel and instantiates the remote object or invokes its class members as required.
A remoting server must complete the following steps:
I'll introduce implementation of these steps for both the activation modes of an SAO: SingleCall and Singleton, albeit briefly in the case of the latter. I'll also show how we link up the client application at the other end of the channel thus providing a complete working example.
The server process for the example will be long running UI-less process that will continue to listen for incoming client requests on a channel. We're going to implement the classes as console applications. Any reader who knows anything about Windows services might be wondering why we're not implementing such an activity as a Windows service. Normally you would, or you might use an existing Windows service such as IIS to work as a remoting server. The choice is down to helping you, the reader, understand what's going on … the console Window shall be utilised to display various informative messages to reinforce the involved concepts.
Create a new VS.NET VB.NET project of type console application within the existing solution. Add a reference to System.Runtime.Remoting. Rename the default vb file to DBConnectSingleCallServer and add the following code:
|
Imports System.Runtime.Remoting Imports System.Runtime.Remoting.Channels Imports System.Runtime.Remoting.Channels.Tcp Imports RemotingDB Module DbConnectSingleCallServer Public Sub Main() 'step 1: create and register a TCP server channel that listens on port 54321 Dim channel As TcpServerChannel = New TcpServerChannel(54321) 'step 2: register the channel ChannelServices.RegisterChannel(channel) 'step 3: register the service that publishes DbConnect for remote access in SingleCall mode RemotingConfiguration.RegisterWellKnownServiceType(GetType(DbConnect), "DbConnect", WellKnownObjectMode.SingleCall) 'write an informative message to the console Console.WriteLine("Started server in the " & "SingleCall mode") Console.WriteLine("Press <ENTER> to terminate " & "server...") Console.ReadLine() End Sub End Module |
Note that we create a TCP channel on a semi-arbitrary port 54321. This is a number in the private range so this should be fine on a company network assuming the port is not being used by another application. For a more widely distributed Internet application you will need to register the number with the appropriate authority – the IANA (Internet Assigned Numbers Authority).
We now have a remotable object and a remoting server. We now need a remoting client which must perform the following steps:
We shall implement the client as a Windows form. Add a Windows form project to the solution naming it DBConnectClientSAO. Add references to System.Runtime.Remoting and to the RemotingDB dll just created. Rename the default form to DBConnectClient. Add a test box to enter the query (txtQuery), a button to execute the query (btnExecute) and a datagrid (dgResults) to display the results of the query. Add the following code behind for the form:
|
Imports System.Runtime.Remoting Imports System.Runtime.Remoting.Channels Imports System.Runtime.Remoting.Channels.Tcp Imports RemotingDB Public Class DbConnectClient Inherits System.Windows.Forms.Form 'declare a remote object Dim dbc As DbConnect 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 'step 1: create and register a TCP client channel Dim channel As TcpClientChannel = New TcpClientChannel ChannelServices.RegisterChannel(channel) 'step 2: register the remote class as a valid type in the client's application domain RemotingConfiguration.RegisterWellKnownClientType(GetType(DbConnect), "tcp://localhost:54321/DbConnect") 'step 3: instantiate the remote class using the default constructor dbc = New DbConnect End Sub End Class |
As previously, comments are used in the above as explanatory text.
We now have three projects in our solution. Build the complete solution. We also need to specify multiple startup projects and in what order they are started. This is achieved via the property pages of the solution (Startup project). Select 'Multiple Startup Projects' and specify the settings and order as follows:
| RemotingDB | none |
| DBConnectSingleCallServer | start |
| DBConnectClientSAO | start |
Run the client project. You should get a command window popping up telling you the server object has been created in SingleCall mode, shortly followed by the Windows client. Enter a query like 'SELECT * from Customers'.
Have you spotted an issue with the client implementation above? The code imports a reference to the server dll, which is also in the client project. Why? Because:
This situation is counter-intuitive as well as being undesirable for a variety of other reasons. The situation can be improved however via the use of interface assemblies – assemblies that contain just interface information, not actual business logic. We'll return to this topic in the next article in this series.
We've implemented a SingleCall SAO. What about a SingletonSAO? The same remotable object can be activated in different modes without making any changes to the remotable object itself and with SAOs the choice of activation is specified at the server. Thus we can use the same client program as per the last example, simply modifying the remoting server code that registers the SAO, as follows:
|
RemotingConfiguration.RegisterWellKnownServiceType(GetType(DbConnect), "DbConnect", _ WellKnownObjectMode.Singleton) |
Of course when you run this you will see little difference … but you'll know things are operating slightly differently under the hood and your appreciation of these differences when you come to more complex real world scenarios is key.
Progressing onto Client Activated Objects, no changes are required to the remotable class itself but changes are required to the remoting server (i.e. registration of the remotable class) and to the client.
Both tasks are very similar to what we've already seen with SAOs, so we'll jump into the code just highlighting the differences. You'll see that in this code we make use of one on the benefits of using CAOs – multiple constructors – via an extension of the implementation. We are going to additionally enable selection of a database within the SQLServer instance from the client.
Create a new Console application project in the same solution we've been using throughout this article, renaming the default file to DBConnectCAOServer and add the following code:
|
Imports System.Runtime.Remoting Imports System.Runtime.Remoting.Channels Imports System.Runtime.Remoting.Channels.Tcp Imports RemotingDB Module DbConnectCAOServer Sub Main() 'create and register a TCP Channel on port 54321 Dim channel As TcpServerChannel = New TcpServerChannel(54321) ChannelServices.RegisterChannel(channel) 'register the client activated object RemotingConfiguration.RegisterActivatedServiceType(GetType(DbConnect)) 'write some informative output to the console Console.WriteLine("Started server in the Client Activation mode") Console.WriteLine("Press <ENTER> to terminate server...") Console.ReadLine() End Sub End Module |
Next we need our new client application. Create a new Windows form project called DBConnectClientCAO; rename the default form to DBConnectClient. The form UI elements are as per the previous client application with the addition of a drop down list box (aka combobox) to allow selection of the target database and a corresponding button. The former should be named cboDatabases and the latter named btnSelect with a text property of 'select'. The controls have also been grouped into areas: Database, Query, Results with groupbox controls named grpDatabases, grpQuery and grpResults. A screen shot of the form design might assist:
The code behind also differs a little:
|
Imports System.Runtime.Remoting Imports System.Runtime.Remoting.Channels Imports System.Runtime.Remoting.Channels.Tcp Imports RemotingDB Public Class DbConnectClient Inherits System.Windows.Forms.Form 'declare a remote object Dim dbc As DbConnect Private Sub DbConnectClient_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load cboDatabases.SelectedIndex = 0 grpQuery.Enabled = False End Sub Private Sub btnSelect_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnSelect.Click 'disable the databases group box and enable the Query group box grpDatabases.Enabled = False grpQuery.Enabled = True 'register a TCP client channel Dim channel As TcpClientChannel = New TcpClientChannel ChannelServices.RegisterChannel(channel) 'register the remote class as a valid type in the client's application domain 'by passing the Remote class and its URL RemotingConfiguration.RegisterActivatedClientType( _ GetType(DbConnect), "tcp://localhost:54321") 'instantiate the remote class dbc = New DbConnect(cboDatabases.SelectedItem.ToString()) End Sub 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 End Class |
Again we need to configure the project properties: ensure the SAO server and previous associated client are not set to start and that DBConnectCAOServer and DBConnectCAOClient are set to start without debugging, and in this order. Make sure the startup objects for the individual projects are also set correctly, similarly to the SAO example. Start the solution without debugging and make sure all is working.
Note that:
We've run through implementations of the different approaches to remoting: singlecall and singleton SAOs and CAOs. We highlighted the interface assembly issue, which we'll discuss further in the next article in this series along with the use of declarative configuration files and the benefits they bring to the table.
.NET SDK
Developing XML WebServices and Server Components with VB.NET and the .NET Framework
Mike Gunderloy
Que