You want to update a data source using .NET remoting and use the remote application from your client application.
Use
System.MarshalByRefObject
to create
a
remoteable class.
The server-side code that registers the remoteable class for remote activation contains one event handler and one configuration file:
- Start Server
Button.Click
Registers the remoteable class
RemoteClass
for remote activation.- Server-side configuration file
Contains parameters used to register the class and the channel on the server so that the class can be activated from another application domain.
The remoteable class code contains two methods:
LoadOrders( )
Creates and returns a
DataSet
containing the Orders and Order Details tables from Northwind and aDataRelation
between those tables.UpdateOrders( )
Takes a
DataSet
argument containing the changes made to theDataSet
created by theLoadOrders( )
method, creates twoDataAdapter
objects withCommandBuilder
-generated update logic for each, and uses theDataAdapter
objects to update the Orders and Order Details tables in Northwind.
The client-side code contains two event handlers and one configuration file:
Form.Load
Sets up the example by calling the
LoadOrders( )
method in the remote object to populate aDataSet
. The default view of the Orders table is bound to the data grid on the form.- Update
Button.Click
Calls the
UpdateOrders( )
method in the remote object passing aDataSet
containing changes made to theDataSet
since the form was loaded or since the last time theUpdateOrders( )
method was called.- Client-side configuration file
Contains parameters used to register the remote class and channel on the client so that the remote class can be instantiated by the client.
The C# server-side code that registers the remoteable class for activation is shown in Example 4-27.
Example 4-27. File: NorthwindServerCS\MainForm.cs
// Namespaces, variables, and constants using System; using System.Configuration; using System.Windows.Forms; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Tcp; using ADOCookbookCS.NorthwindRemoteCS; private bool isStarted = false; // . . . private void startServerButton_Click(object sender, System.EventArgs e) { if (!isStarted) { // config file RemotingConfiguration.Configure( "NorthwindServerCS.exe.config"); serverResultTextBox.Text += "Remote server started." + Environment.NewLine + " ApplicationName = " + RemotingConfiguration.ApplicationName + Environment.NewLine + " ApplicationId = " + RemotingConfiguration.ApplicationId + Environment.NewLine + " ProcessId = " + RemotingConfiguration.ProcessId; isStarted = true; } else { serverResultTextBox.Text += "Remote Server already started." + Environment.NewLine; } }
Example 4-28. File: NorthwindServerCS\NorthwindServerCS.exe.config
<configuration> <system.runtime.remoting> <application name="RemoteClass (WellKnownServiceType)"> <service> <wellknown mode="SingleCall" type="ADOCookbookCS.NorthwindRemoteCS.RemoteClass, NorthwindRemoteCS" objectUri="RemoteClass"> </wellknown> </service> <channels> <channel ref="tcp server" port="1234" /> </channels> </application> </system.runtime.remoting> </configuration>
The C# remoteable class code is shown in Example 4-29.
Example 4-29. File: NorthwindRemoteCS\NorthwindRemoteCS.cs
// Namespaces, variables, and constants using System; using System.Data; using System.Data.SqlClient; // . . . namespace ADOCookbookCS.NorthwindRemoteCS { /// <summary> /// Summary description for NorthwindRemote /// </summary> public class RemoteClass : MarshalByRefObject { public const String SQL_CONNECTIONSTRING = "Data Source=(local);Integrated security=SSPI;" + "Initial Catalog=Northwind;"; // Table name constants private const String ORDERS_TABLE = "Orders"; private const String ORDERDETAILS_TABLE = "OrderDetails"; // Relation name constants private const String ORDERS_ORDERDETAILS_RELATION = "Orders_OrderDetails_Relation"; // Field name constants private const String ORDERID_FIELD = "OrderID"; public DataSet LoadOrders( ) { DataSet ds = new DataSet( ); SqlDataAdapter da; // Fill the Order table and add it to the DataSet. da = new SqlDataAdapter("SELECT * FROM Orders", SQL_CONNECTIONSTRING); DataTable orderTable = new DataTable(ORDERS_TABLE); da.FillSchema(orderTable, SchemaType.Source); da.Fill(orderTable); ds.Tables.Add(orderTable); // Fill the OrderDetails table and add it to the DataSet. da = new SqlDataAdapter("SELECT * FROM [Order Details]", SQL_CONNECTIONSTRING); DataTable orderDetailTable = new DataTable(ORDERDETAILS_TABLE); da.FillSchema(orderDetailTable, SchemaType.Source); da.Fill(orderDetailTable); ds.Tables.Add(orderDetailTable); // Create a relation between the tables. ds.Relations.Add(ORDERS_ORDERDETAILS_RELATION, ds.Tables[ORDERS_TABLE].Columns[ORDERID_FIELD], ds.Tables[ORDERDETAILS_TABLE]. Columns[ORDERID_FIELD], true); return ds; } public bool UpdateOrders(DataSet ds) { // Create the DataAdapters for order and order details // tables. SqlDataAdapter daOrders = new SqlDataAdapter("SELECT * FROM Orders", SQL_CONNECTIONSTRING); SqlDataAdapter daOrderDetails = new SqlDataAdapter("SELECT * FROM [Order Details]", SQL_CONNECTIONSTRING); // Use CommandBuilder to generate update logic. SqlCommandBuilder cbOrders = new SqlCommandBuilder(daOrders); SqlCommandBuilder cbOrderDetails = new SqlCommandBuilder(daOrderDetails); // Update parent and child records. daOrderDetails.Update( ds.Tables[ORDERDETAILS_TABLE].Select(null, null, DataViewRowState.Deleted)); daOrders.Update(ds.Tables[ORDERS_TABLE].Select( null, null, DataViewRowState.Deleted)); daOrders.Update(ds.Tables[ORDERS_TABLE].Select(null, null, DataViewRowState.ModifiedCurrent)); daOrders.Update(ds.Tables[ORDERS_TABLE].Select(null, null, DataViewRowState.Added)); daOrderDetails.Update( ds.Tables[ORDERDETAILS_TABLE].Select(null, null, DataViewRowState.ModifiedCurrent)); daOrderDetails.Update( ds.Tables[ORDERDETAILS_TABLE].Select(null, null, DataViewRowState.Added)); return true; } } }
The C# client-side code that activates the remoteable class remotely is shown in Examples Example 4-30 and Example 4-31.
Example 4-30. File: RemotingForm.cs
// Namespaces, variables, and constants using System; using System.Windows.Forms; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Tcp; using System.Data; using ADOCookbookCS.NorthwindRemoteCS; // Table name constants private const String ORDERS_TABLE = "Orders"; private RemoteClass rs; private DataSet ds; // . . . private void RemotingForm_Load(object sender, System.EventArgs e) { Cursor.Current = Cursors.WaitCursor; RemotingConfiguration.Configure("RemotingForm.exe.config"); rs = new RemoteClass( ); // Load the DataSet containing orders and order details. ds = new DataSet( ); ds = rs.LoadOrders( ); // Bind the default view of the orders table to the grid. dataGrid.DataSource = ds.Tables[ORDERS_TABLE].DefaultView; Cursor.Current = Cursors.Default; } private void updateButton_Click(object sender, System.EventArgs e) { Cursor.Current = Cursors.WaitCursor; // Get the changes to the data. DataSet dsChanges = ds.GetChanges( ); // Update the changes to the order and order detail informtation. if (dsChanges != null) rs.UpdateOrders(dsChanges); Cursor.Current = Cursors.Default; }
Example 4-31. File: RemotingForm.exe.config
<configuration> <system.runtime.remoting> <application> <client url="tcp://localhost:1234"> <wellknown type="ADOCookbookCS.NorthwindRemoteCS.RemoteClass, NorthwindRemoteCS" url="tcp://localhost:1234/RemoteClass"> </wellknown> </client> <channels> <channel ref="tcp client" /> </channels> </application> </system.runtime.remoting> </configuration>
A remoteable class, unlike a conventional class, can be used by
clients running outside of application domain of the remoteable
class. All that is required to make a class remoteable is to derive
it from System.MarshalByRefObject
.
When a client creates an instance of a remote class, a proxy is created in the client’s application domain instead of an actual object. The proxy acts exactly like the object, but is actually a reference to the object. This proxy communicates with the remote class through a channel connecting the two application domains.
Note
There are two different activation types for remote servers:
- well known
The server must activate the remote object.
- activated
The client can request to activate the remote object.
This solution uses a well known remote class. Therefore, the remote
server must be started prior to running the solution. This is done by
running the application
NorthwindServerCS.exe
and pressing the
Start
Server
button.
Before the class can be instantiated remotely, a server process must
register it so that it can be activated from another application
domain. This can be done either by calling the
RegisterActivatedServiceType( )
or
RegisterWellKnownServiceType( )
of the
RemotingConfiguration
class with the appropriate
parameters or by calling the Configure( )
method
of the RemotingConfiguration
class with the name
of the configuration file as an argument. The solution uses the
Configure( )
method with the code:
RemotingConfiguration.Configure("NorthwindServerCS.exe.config");
For a well known (server-activated) class, the server registration code would be:
RemotingConfiguration.RegisterWellKnownServiceType( typeof(ADOCookbookCS.NorthwindRemoteCS.RemoteClass), "RemoteClass", WellKnownObjectMode.SingleCall);
For an activated (client-activated) class, the server registration code would be:
RemotingConfiguration.RegisterActivatedServiceType(typeof(RemoteClass));
A server channel also must be created and registered when one of the two methods, for a well known or an activated class, is used. When a configuration file is used, the channel information is specified within the file. The server channel can be either one that accepts TCP connections from the client or one that accepts HTTP connections from the client. The code to register a TCP channel that listens on port 1234 is:
TcpServerChannel channel = new TcpServerChannel(1234); ChannelServices.RegisterChannel(channel);
The code to register a HTTP channel that listens on port 1234 is:
HttpServerChannel channel = new HttpServerChannel (1234); ChannelServices.RegisterChannel(channel);
To create an instance of the remote class, the client must first
register either a TCP or HTTP client channel by creating an instance
of either the TcpClientChannel
or
HttpClientChannel
class and using the
RegisterChannel( )
method of the
ChannelServices
class to register the channel. The
code is similar to the code for registering a server channel.
Next, if the client wants to be able to instantiate the remote object
using the new
operator, the client must register
the remote class in the local application domain using the
RegisterWellKnownClientType( )
or the
RegisterActivatedClientType( )
method of the
RemotingConfiguration
class with the appropriate
parameters or by calling the Configure( )
method
of the RemotingConfiguration
class. The solution
uses the Configure( )
method with the code:
RemotingConfiguration.Configure("RemotingForm.exe.config");
For a well known class, the client registration code would be:
RemotingConfiguration.RegisterWellKnownClientType( typeof(RemoteClass), "tcp://localhost:1234/RemoteClass");
For an activated class, the client registration code would be:
RemotingConfiguration.RegisterActivatedClientType( typeof(RemoteClass), "tcp://localhost:1234");
Once the client and server have performed the necessary registration,
the remote class can be instantiated by the client using the
new
operator as shown here:
rs = new RemoteClass( );
As mentioned earlier, this creates a proxy for the remote object on client that you can use in exactly the same way as if the object were local.
In the solution, the GetChanges( )
method is
called on the client-side DataSet
to create a copy
of the DataSet
containing only the changes. This
DataSet
is passed to the UpdateOrders( )
method of the remote object instead of to the entire
DataSet
to minimize the required bandwidth across
a potentially slow connection.
The UpdateOrders( )
method updates the database
with the changes using method calls with different subsets of the
DataSet
as arguments. This technique, used to
avoid referential integrity problems, is discussed in more detail in
Recipe 4.10.
The solution shows that there is very little difference between
implementing the LoadOrders( )
and
UpdateOrders( )
methods as a local
class or a remoteable class.
Get ADO.NET Cookbook now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.