A Simple .NET Remoting Client and Server

Team-Fly    

 
.NET and COM Interoperability Handbook, The
By Alan  Gordon
Table of Contents
Chapter Eleven.  .NET Remoting

A Simple .NET Remoting Client and Server

Let's build a very simple .NET remoting client and server. The use of .NET remoting is intended for .NET clients and servers, so, unlike COM or CORBA, you don't have to deal with IDL. You define the types that you will use remotely using the same .NET languages that you use to implement the types. The application will therefore be composed of three separate assemblies: (1) a shared library that contains the remotable types, (2) a console application that will be the server application, and (3) a console application that will be the client application.

The Shared Library

The server will contain two types: a marshal-by-reference class called Company and a marshal- by-value class called Employee . The Company class contains the members shown in Table 11-2.

Table 11-2. The members of the Company class

Member Name

Member Type

Description

Company

Constructor

There are two constructors: (1) a default constructor, which sets the name of the company to "Alans Company" and (2) a constructor that takes a string argument that contains the name of the company.

CreateEmployee

Public method

Creates a marshal-by-value Employee object and adds it to the company's internal Employee collection and returns it to the client. This method implements the factory design pattern.

GetEmployee

Public method

Looks up and returns an employee given the employee's ID.

CompanyName

Public property

Returns the name of the company.

mName

Private field

Is a private field that contains the company name. This field is exposed using the CompanyName property.

mEmployees

Private field

A private hash table field that contains all the employees of the company indexed by the employees ' IDs.

The implementation of the Company class is shown in the following code:

 1.  using System; 2.  using System.Collections; 3.  namespace SharedLibrary 4.  { 5.      public class Company : MarshalByRefObject 6.      { 7.        public Company() 8.        { 9.          Console.WriteLine( 10.             "Company created with no name"); 11.         mName="Alan's company"; 12.         mEmployees=new Hashtable(); 13.       } 14.       public Company(string aName) 15.       { 16.         Console.WriteLine( 17.             "Company created with name: {0}", 18.             aName); 19.         mName=aName; 20.         mEmployees=new Hashtable(); 21.       } 22.       public Employee CreateEmployee( 23.         int empID,string fstName, 24.         string lstName,decimal sal) 25.       { 26.         Console.WriteLine( 27.             "Called CreateEmployee with empID={0}", 28.         empID); 29.         Employee emp=new Employee( 30.           empID,fstName,lstName, 31.           DateTime.Now,sal); 32.         mEmployees.Add(empID,emp); 33.         return emp; 34.       } 35.       public Employee GetEmployee(int empID) 36.       { 37.         Console.WriteLine( 38.          "Called GetEmployee with empID={0}", 39.          empID); 40.         if (!mEmployees.ContainsKey(empID)) 41.           throw new ApplicationException( 42.             "Key: " + 43.             empID.ToString() + 44.             " does not exist."); 45.         return (Employee)mEmployees[empID]; 46.       } 47.       public string CompanyName 48.       { 49.         get { return mName; } 50.         set { mName=value; } 51.       } 52.       private string mName; 53.       private Hashtable mEmployees; 54.     } 55. } 

Line 5 declares the Company class. Notice that it inherits from the System.MarshalByRefObject class. Lines 7 through 13 declare a default constructor. Remember that you need a default constructor if you wish to configure the class for server activation. The key line of this method is line 12, where I create a System.Collections.Hashtable object to hold the list of employees for this company. Notice that line 9 writes diagnostic output to the console so that you can see when a client creates an instance of the Company class. Lines 14 through 21 declare a parameterized constructor that allows you to pass a name for the company. I also write diagnostic output to the console and create a hash table in this method also. Lines 22 through 34 declare a CreateEmployee method. This method takes an employee ID, a first name, a last name, and a salary and will create an Employee marshal-by-value object and return it to the caller. Lines 35 through 46 declare a GetEmployee method that will look up an employee by ID in the Company object's internal hash table of employees. If the employee does not exist, this method raises an error. Since the list of Employees is stored in an in-memory data structure and not a database, the GetEmployee method will only work if the CreateEmployee and GetEmployee method are called on the same instance. I will actually make use of this fact to do some experiments with lease-based life cycle management shortly. Lines 47 through 51 declare a CompanyName property that I can use to get or set the name of the company. Lines 52 and 53 declare private fields that will hold the company name and the list of employees, respectively.

The CreateEmployee and GetEmployee methods return an instance of the Employee class. The Employee class is the marshal-by-value class that will hold information about the employees who work for a company. The code for this class is shown here:

 1.  using System 2.  namespace SharedLibrary 3.  { 4.     [Serializable] 5.     public class Employee 6.     { 7.         public Employee() 8.         { 9.           Console.WriteLine( 10.          "Called default Employee constructor"); 11.        } 12.        public Employee(int aID, 13.          string aFirst,string aLast, 14.          DateTime aDate,decimal aSalary) 15.        { 16.          mID=aID; 17.          mFirstName=aFirst; 18.          mLastName=aLast; 19.          mHireDate=aDate; 20.          mSalary=aSalary; 21.          Console.WriteLine( 22.          "Called parameterized Employee constructor"); 23.          Console.WriteLine( 24.          "ID={0},FirstName={1},LastName={2}", 25.          mID,mFirstName,mLastName); 26.        } 27.        public int ID 28.        { 29.          get { return mID; } 30.          set { mID=value; } 31.        } 32.        public DateTime HireDate 33.        { 34.          get { return mHireDate; } 35.          set { mHireDate=value; } 36.        } 37.        public string LastName 38.        { 39.          get { return mLastName; } 40.          set { mLastName=value; } 41.        } 42.        public string FirstName 43.        { 44.          get {      Console.WriteLine( 45.          "Calling get for FirstName property"); 46.          return mFirstName; } 47.          set {  Console.WriteLine( 48.          "Calling set for FirstName property"); 49.          mFirstName=value; } 50.        } 51.        public decimal Salary 52.        { 53.          get { return mSalary; } 54.          set { mSalary=value; } 55.        } 56.        private int mID; 57.        private DateTime mHireDate; 58.        private string mLastName; 59.        private string mFirstName; 60.        private decimal mSalary; 61.    } 62. } 

I won't explain this code; it's pretty straightforward. The only thing new is the use of the System.Serializable attribute on line 4, which makes this class a marshal-by-value class.

I compiled both the Company and the Employee class into a library (DLL) assembly called sharedlibrary. I won't go through the detailed steps for doing this because you have created Visual Studio .NET projects a number of times already in this book. Just remember that the sharedlibrary assembly should include all the code from the last two code listings.

The Server

The next thing you need to do is to create the server application. In this case, I use a Visual Studio .NET console application. The code for the server follows :

 1.  using System; 2.  using SharedLibrary; 3.  using System.Runtime.Remoting; 4.  using System.Runtime.Remoting.Channels; 5.  using System.Runtime.Remoting.Channels.Http; 6.  namespace CompanyServer 7.  { 8.    public class CompanyServer 9.    { 10.     static void Main(string[] args) 11.     { 12.       HttpChannel chnl=new HttpChannel(8080); 13.       ChannelServices.RegisterChannel(chnl); 14.       RemotingConfiguration. 15.         RegisterWellKnownServiceType( 16.         typeof(Company), 17.         "Company.soap", 18.         WellKnownObjectMode.Singleton); 19.       Console.WriteLine( 20.         "Company server started!!!"); 21.       Console.ReadLine(); 22.     } 23.   } 24. } 

Line 2 contains a using statement for the sharedlibrary assembly. I need this because I use the SharedLibrary.Company class on line 16. Lines 3 through 5 contain using statements for three remoting- related namespaces. Lines 10 through 22 declare the main method for this application. Line 12 creates an HTTP channel on port 8080. Line 13 calls the RegisterChannel method on the System.Runtime.Remoting.Channels.ChannelServices class to register this channel. Lines 14 through 18 call the RegisterWellKnownServiceType method on the System.Runtime.Remoting.RemotingConfiguration class to register the type with the .NET remoting infrastructure. The parameters to this method are a System.Type object for the type that I am exposing, the Uniform Resource Identifier (URI) for the object and the mode, that is, whether it's Singleton or SingleCall. The complete URI that you would need to access a remote object from a client is the following:

 [protocol]://[servername]:[port]/[specified URI] 

Where [SpecifiedURI] is the URI that I passed as the second parameter to the RegisterWellKnownServiceType method. Therefore, given that I have specified company.soap for the URI of the company object, the full URI to access the company object from an HTTP channel on port 8080 of the localhost machine is the following:

http://localhost:8080/company.soap

The following URI will access an object from a TCP channel on port 2400 on a server called gordon1.

tcp://gordon1:2400/company.soap

Lines 19 and 20 write a message to the console to show that the server process has started, and line 21 calls Console.ReadLine to force the server process remain alive until you press a key on the keyboard. Notice that I don't associate the Channel directly with the object that I am exposing. An object can be accessed through multiple channels, and a channel can provide access to multiple objects. A channel object provides access to all the types that are exposed by its host process.

I compiled the code shown previously into a console executable assembly called CompanyServer using Visual Studio .NET. The server project should reference the SharedLibrary assembly. Once again, I won't go into this in detail because I have built projects with Visual Studio .NET a number of times in this book.

The Client

The client will also use the Company and Employee types from the shared library. I will also build the client using a Visual Studio .NET console application. The code for the client application is the following:

 using System; 2.  using System.Runtime.Remoting.Channels; 3.  using System.Runtime.Remoting.Channels.Http; 4.  using SharedLibrary; 5.  namespace CompanyClient 6.  { 7.    class Class1 8.    { 9.        [STAThread] 10.       static void Main(string[] args) 11.       { 12.         HttpChannel chnl=new HttpChannel(); 13.         ChannelServices.RegisterChannel(chnl); 14.         int empID; 15.         Employee emp1, emp2; 16.         Company obj=(Company)Activator.GetObject( 17.             typeof(Company), 18.             "http://localhost:8080/Company.soap"); 19.         Console.WriteLine("Enter an Employee ID"); 20.         empID=int.Parse(Console.ReadLine()); 21.         Console.WriteLine("Enter a first name"); 22.         string strFirstName=Console.ReadLine(); 23.         Console.WriteLine("Enter a last name");               string strLastName=Console.ReadLine(); 25.         emp1=obj.CreateEmployee(empID, 26.         strFirstName,strLastName,1000); 27.         Console.WriteLine( 28.           "Enter the key of an Employee"); 29.       empID=int.Parse(Console.ReadLine()); 30.       emp2=obj.GetEmployee(empID); 31.       Console.WriteLine( 32.         "Employee first name is: " + 33.         emp2.FirstName); 34.       } 35.     } 36. } 

Lines 2 and 3 contain using statements for two remoting-related namespaces. Line 4 has the using statement for the SharedLibrary assembly that contains the Company and Employee types. Lines 10 through 34 define the Main method for the client application. Lines 12 and 13 define and register an HTTP channel for the client. Notice that I don't have to define the port for this channel. I specify the port in the URI that I use to access the object. Lines 16 through 18 use the GetObject method on the System.Activator class to create an instance of the Company class in the server process.

Note

Later on you will see how to use the new operator to create an instance of a remote object. I prefer to use the Activator.GetObject method in most cases because I prefer to specify explicitly where the object should be created. When you use the new operator, it can be difficult to tell whether the object resides on the client or the server.


Notice that I specify the protocol, the server name, and the port for the channel as well as the same URI that I specified in the RegisterWellKnownServiceType method on the server. Lines 19 through 24 prompt for and read the ID, first name, and last name for an employee. Line 25 calls the CreateEmployee method on the Company object to create and return a marshal-by-value employee instance called emp1. Lines 27 through 29 prompt for and read the ID of an Employee object to look up. Line 30 calls the GetEmployee method on the Company object to look up the requested employee. Lines 31 through 33 write the first name of the employee that you find.

I compiled the client into a console application using Visual Studio .NET. The client application should reference the SharedLibrary assembly.

To run this example program, start the server first by running the executable in a Visual Studio .NET command prompt. It should display a text string that says "Company Server Started!!!". Now start the client in a different Visual Studio .NET prompt. Enter an ID, first name, and last name when prompted. After entering this information, you will see output coming from the server's command prompt. The output is being written to the console from the constructor of the Employee class. This output shows that the Employee object is running in the server's app domain. The client will then prompt you for the ID of an employee. Type in the same ID that you specified initially, and the code will look up and display the name of the matching employee.


Team-Fly    
Top
 


. Net and COM Interoperability Handbook
The .NET and COM Interoperability Handbook (Integrated .Net)
ISBN: 013046130X
EAN: 2147483647
Year: 2002
Pages: 119
Authors: Alan Gordon

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net