Building a Distributed Component Server using C# and .NET
Gopalan Suresh Raj
work with any of these samples, you will need the
.........................................Microsoft .NET SDK
.........................................Microsoft Visual Studio.NET Beta 2 or higher
The DataSet is actually an in-memory view of the database. It can contain multiple DataTable and DataRelation objects. This allows developers to navigate and manipulate the collection of tables and their relationships. ADO.NET involves disconnected DataSets as it is modeled for a distributed architecture. As the DataSet itself is disconnected from the data source, it must provide a way to track changes to itself. For this purpose, the DataSet class provides a number of methods which can be used to reconcile changes to itself with the actual database (or other data source) at a later point in time. Some of these methods include HasChanges(), HasErrors(), GetChanges(), AcceptChanges(), and RejectChanges(). These methods can be used to check for changes that have happened to the DataSet, obtain modifications in the form of a changed DataSet, inspect the changes for errors, and then either accept or reject those changes. If the changes need to be communicated back to the data store back-end, the Data Adapter can be asked to be updated using the Data Adapter's Update() method.
The DataSet is intended to help web applications which are by their very nature disconnected. You never know that the data in the database has changed until you have updated the records that you were editing or perform other tasks which needs reconciliation with the back end Data Store.
The BookKeeper Module
In this article, we are going to build a Distributed Component to illustrate ADO.NET's disconnected operation facility - the DataSet. As mentioned earlier, the DataSet facilitates the client to manipulate and update a local copy of any number of related tables while still disconnected from the data source and submit the modified data back for processing using a related data adapter at a later point in time. As a result, all our data is going to be maintained on the server in an XML datastore (or flat-file) until changes are committed to a database back-end at a later point in time !!!
Our hypothetical BookKeeper Module (that we will build in this article), is actually a Distributed component that performs just a bunch of functions. It is the module that manages creation and deletion of accounts (Checking accounts or Savings accounts) for a Large Commercial Bank Project. It offers no other services except "Create Account", and "Delete Account", and "Find Account".
Building Distributed Components using the .NET Framework
While traditional Distributed COM (DCOM - the wire protocol that provides support for distributed computing in COM) is fine for distributed computing, it is not appropriate for the internet because it does'nt work well in the face of firewalls and NAT software. The other shortcomings of DCOM are expensive lifecycle management, protocol negotiation, and binary formats.
To ease these shortcomings, .NET provides the Remoting API that allows you to use a host of channels - such as TCP and HTTP (which uses SOAP) - for distributed computing. It also permits you to plug in your own custom channels, if you require this functionality.
To ease development, I recommend using the Visual Studio.NET IDE. However, you are free to develop your application in the favorite editor of your choice, using the command-line to execute the various commands to build and deploy it.
The various steps that are involved in creating a Distributed Component using C# and the .NET Framework are as follows (I'm going to assume you're using the VS.NET IDE):
Create a Visual C# - Console Application project
Generate a Key-Value pair to use when deploying your Shared Assembly
Configure your Project Property Pages with the right information
Develop the BookKeeper.cs library
Modify the generated AssemblyInfo.cs to add the right assembly information
Build the Project Files
Deploy the application as a Shared Assembly
1. Create a Visual C# - Console Application project
Create a new Visual C# Console Application project.
2. Generate a Key-Value pair to use when deploying your Shared Assembly
Shared Assemblies are those that can be used by any client application, such as a system DLL that every process in the system can use. Unlike private-assemblies, shared assemblies must be published or registered in the system's Global Assembly Cache (GAC). As soon as they are registered in the GAC, they act as system components. An essential requirement for GAC registration is that the component must possess originator and version information. In addition to other metadata information, these two items allow multiple versions of the same component to be registered and executed on the same machine. Unlike Classic COM, we don't have to store any information in the system registry for clients to use these shared assemblies.
There are three general steps to registering shared assemblies in the GAC:
The Shared Name (sb.exe) utility should be used to obtain the public/private key pair. This utility generates a random key pair value, and stores it in an output file - for example, BookKeeper.key.
Build the assembly with an assembly version number and the key information in the BookKeeper.key
Using the .NET Global Assembly Cache (gacutil.exe) utility, register the assembly in the GAC.
The assembly now becomes a shared assembly and can be used by any client in the system.
Therefore, as a first step, use the Shared Name Utility to obtain a public/private key pair and store it in a file (BookKeeper.key, in this case) as shown below.
Microsoft (R) .NET Framework Strong Name Utility Version 1.0.2914.16
Copyright (C) Microsoft Corp. 1998-2001. All rights reserved.
Key pair written to BookKeeper.key
The -k option generates the random key pair and saves the key information in the BookKeeper.key file. We use this file as input when we build our Shared Assemblies.
3. Configure your Project Property Pages with the right information
Configure the Project Properties with the right information. Make sure you specify the Assembly Name that you want for the Assembly. Specifically, move to the General tab, and in the Wrapper Assembly Key File area, enter the key file to use. In this case, it is BookKeeper.key.
4. Develop the BookKeeper.cs library
The DataSet's properties expose the Tables and Relations that it contains. After creating a new DataSet object on Line 54, we add a Table and Columns to it. The createSchema() method from Lines 143-163 demonstrates how to create a Table, and it's columns dynamically. Line 147 shows how to create a Table using the Add() method of the Table object. We go through a similar process from Lines 149 to 156 to create Columns in a similar fashion by invoking the Add() method on each Table's Column collections. Each of these Tables or Columns can later be referred to by name. In order to assign the primary key for the Accounts table, we have to create the DataColumn array to hold one or more fields representing a key or a composite key as shown from Lines 159 to 161. In this case we only have a single key field, AccountKey. We set the PrimaryKey property of the table to this array of key columns.
The insertData() method from Lines 172 to 191 shows how to insert data into the Table. Tables and Relations are important properties of the DataSet. Not only do they describe the structure of the in-memory database, but the DataTables inside the collection also hold the content of the DataSet.
There is an inherent and 'intentional bug' in the way deletes are handled in the BookKeeper Server. You can only delete records in the order that they were created - i.e., you'd have to delete the latest record first !!! If you do try to delete in any other order, you will not be able to create the next new Account !!! This is because of the way new Primary Keys are generated. For explanation, please look at the BookKeeper::getNextKey() method. I intentionally introduced this bug in the program to demonstrate that even though ADO.NET's DataSet is physically disconnected from the DataBase, it maintains and manages its data internally, and still checks Constraints and behaves very much like a regular database.
XML and the DataSet
The DataSet tightly integrates with XML and is therefore universally interoperable. The three methods that facilitate this are WriteXml(), WriteXmlSchema(), and ReadXml(). WriteXmlSchema() only dumps the schema of the tables, including all tables and the relationship between the tables. WriteXml() can dump both the schema and the table data as an XML encoded string as shown on Lines 283 and 285. Both WriteXmlSchema() and WriteXml()accept a Stream, TextWriter, XmlWriter, or a String representing a filename. WriteXml() accepts an XmlWriteMode as the second argument.
When the XmlWriteMode.WriteSchema is passed in as the second argument to WriteXml() method, the method dumps both the Schema and the Data as shown on Lines 283 and 285. You can retrieve only the data portion of the XML by using the XmlWriteMode.IgnoreSchema property.
The DataSet also provides methods to reconstruct itself from an XML Document. Use ReadXmlData() for reading XML data documents, and ReadXmlSchema() for reading XML Schema documents. As shown on Line 58, use ReadXml() to read both the schema and the data from an XML file and reconstruct and populate the DataSet object.
The DataSet is therefore the most important construct in ADO.NET. Since the DataSet does not tie to an underlying representation such as Microsoft Access or SQL Server, it is extremely portable. Its data format is self-described in its schema, and its data is in pure XML. A DataSet is self-contained regardless of how it was created, perhaps be reading data from Microsoft Access, from SQL Server, from an external XML file, or even being dynamically generated as we've seen in this example. This portable XML-based entity sets a new standard for data exchange.
In the code example of BookKeeper.cs above, since we're using the TCP channel, we need to tell the compiler that we need definitions in the System.Runtime.Remoting and System.Runtime.Remoting.Channels.Tcp namespaces as shown on Lines 14 and 15. Also note on Line 43, that the BookKeeper class derives from MarshalByRefObject so that it can have a distributed identity. The default is marshaling-by-value, which means that a copy of the remote object is created on the client side. Subclassing from the MarshalByRefObject class gives the object a distributed identity, allowing the object to be referenced across application domains, or even across process and machine boundaries. Even though a marshal-by-reference object requires a proxy to be setup on the client side and a stub to be setup on the server side, the infrastructure handles this automatically, without us having to do any other extra work.
Any external client can invoke all the public methods of the BookKeeper class because the Main() method uses the TcpChannel class. The Main() method on Line 294 instantiates a TcpChannel, passing in a port number from which the server will listen for incoming requests.
Once we've created a Channel object, we register the Channel to the ChannelServices, which supports channel registration and object resolution on Line 295. The RegisterWellKnownServiceType() method of the RemotingConfiguration class allows you to register you object with the RemotingConfiguration so that it can be activated as shown on Line 297. When you invoke this method, you need to provide the class name, URI, and an object activation mode. The URI is important as the client will use it to refer specifically for this registered object.
5. Modify the generated AssemblyInfo.cs to add the right assembly information
You provide the compiler with your assembly information in an assembly file called AssemblyInfo.cs. The assembly information file is compiled with the rest of the project's source files. The information is in the form of assembly attributes - directives to the compiler on the information to embed in the assembly.
In particular, pay attention to the fact that
we specify a version number for this library using the
6. Build the Project Files
Build the files that make up the project.
Rebuild All started: Project: BookKeeper, Configuration: Debug .NET ------
Performing main compilation...
Build complete -- 0 errors, 0 warnings
Building satellite assemblies...
---------------------- Done ----------------------
Rebuild All: 1 succeeded, 0 failed, 0 skipped
7. Deploy the component as a Shared Assembly
After you've built the assembly, you can use the .NET Global Assembly Cache (GAC) utility to register this assembly into the GAC as shown below.
Microsoft (R) .NET Global Assembly Cache Utility. Version 1.0.2914.16
Copyright (C) Microsoft Corp. 1998-2001. All rights reserved.
Assembly successfully added to the cache
Successful registration against the cache turns this component into a shared assembly. A version of this component is copied into the GAC so that even if you delete this file locally, you will still be able to run your client program.
8. Execute the Server
Once you've built the Server, start the server program, which will wait endlessly until you hit the Enter Key. The server is now ready to service client requests.
Now you need to build a client application that can access this Distributed Component.
|Channels and .NET Remoting|
|Building a Distributed Component Server using C# and .NET|
|Building a Distributed Component Client using C# and .NET|
Download the entire source code as a zip file.
click here to go
My Advanced C#/.NET Tutorial Page...
|About the Author...|
|Gopalan Suresh Raj is a Software Architect, Developer and an active Author. He has co-authored a number of books including "Professional JMS", "Enterprise Java Computing-Applications and Architecture" and "The Awesome Power of JavaBeans". His expertise spans enterprise component architectures and distributed object computing. Visit him at his Web Cornucopia© site (http://gsraj.tripod.com/) or mail him at firstname.lastname@example.org.|
This site was developed and is maintained by Gopalan Suresh Raj
This page has been visited times since January 1, 2002.
Last Updated : Jan 01, '02
All products and companies mentioned at this site are trademarks of their respective owners.