A Simple Remoting Server

So far we've explored a fair bit of information about the .NET Remoting architecture and its capabilities. It's time to dive into a complete example to see how it works in practice. This example requires three ingredients: the client application, the remote object, and the component host. You can develop and test the design on a single computer and modify the configuration later when you deploy it.

The easiest way to develop these three ingredients is to use separate Microsoft Visual Studio .NET projects. You can group them together into a single solution if you would like, but it's not required. In fact, you might want to try creating them in separate Visual Studio .NET instances first, which will help you understand each project's role and dependencies.

The Remote Object

First of all, we create a simple remotable class that provides a single method, named GetActiveDomain, which returns a string with the information about the current application domain where execution is taking place. (See Listing 4-5.) This class must be compiled into a DLL assembly, so if you're using Visual Studio .NET, you'll create this component as a class library project.

Listing 4-5 A remotable object
 Public Class RemoteObject     Inherits MarshalByRefObject     ' Return the name of the current application domain.     Public Function GetActiveDomain() As String         Return AppDomain.CurrentDomain.FriendlyName     End Function End Class 

That's it! You should now compile the remotable component before continuing.

The Component Host

The component host just creates the channel that listens for client requests. In .NET Remoting, there are two ways to configure and create a channel: directly in code or by loading a configuration file. In this example, we use a configuration file, which allows the settings to be modified without requiring a recompile. The settings are loaded using the shared helper method RemotingConfiguration.Configure. In this example, the component host itself is a Windows application named SimpleServer.exe, and the listening channel is configured when the HostForm Windows Form first loads (as shown in Listing 4-6).

Listing 4-6 A Windows Forms component host
 Imports System.Runtime.Remoting Public Class HostForm     Inherits System.Windows.Forms.Form     ' (Designer code omitted.)     Private Sub HostForm_Load(ByVal sender As System.Object, _      ByVal e As System.EventArgs) Handles MyBase.Load         ' Start listening for client requests, according to the          ' information specified in the configuration file.         RemotingConfiguration.Configure("SimpleServer.exe.config")     End Sub End Class 

The .NET Remoting configuration file is the same configuration file used for assembly dependencies and probing, as detailed in Chapter 2. This isn't required (you can actually use any filename), but it is recommended for simplicity. In a more sophisticated scenario, you might use several configuration files with different settings. The application then decides which one to use at run time or queries it directly from a database or an XML Web service.

The configuration file must be placed in the same directory as the executable. (So if you're testing in Visual Studio .NET, you use the Bin directory.) This configuration file defines the type of channel and identifies the type of object and the location where it can be found. Listing 4-7 shows the configuration file for the SimpleServer host application.

Listing 4-7 The component host configuration file
 <configuration>    <system.runtime.remoting>       <application name="SimpleServer">          <service>             <activated              type="RemoteObjects.RemoteObject, RemoteObjects"/>          </service>          <channels>             <channel ref="tcp server" port="8080" />          </channels>       </application>    </system.runtime.remoting> </configuration> 

In this example, the channel is identified as "tcp server", which is one of the channel aliases defined in the machine.config file. The assigned port number is 8080. If you want to use the HTTP channel instead, you just specify "http server".

The service (or remote object) is a client-activated type. Its fully qualified class name is RemoteObjects.RemoteObject, and it exists in the assembly Remote­Objects. (The .dll extension is assumed.) In this case, the assembly name matches the class name, but it doesn't need to. If the RemoteObject class were contained in an assembly called ServerObjects.dll, for example, you would rewrite the activation tag like this:

 <activated type="RemoteObjects.RemoteObject, ServerObjects"/> 

The configuration file also names the component host application; the client needs to use this name to connect to the listening channel:

 <application name="SimpleServer"> 

The host application has a relatively simple life. Although it's currently created as a Windows Forms application (for the sake of convenience), it doesn't display any user interface. It also isn't notified when a client makes a request or when a remote object is created all these steps take place automatically. However, the client application needs to be active in order to continue listening. As soon as the component host application ends, any current clients are disconnected (and all future clients are refused).

Alternatively, you can create the same component host as a console application, as shown in Listing 4-8.

Listing 4-8 A console component host
 Module ConsoleHost     Sub Main()         Console.WriteLine("About to configure channel.")         RemotingConfiguration.Configure("SimpleServer.exe.config")         Console.WriteLine("Listening has started.")         Console.WriteLine("Press any key to stop listening and exit.")         ' This next line is required to stop the program from ending         ' prematurely. After the user presses a key, execution will         ' resume, and the program will end when it reaches the end         ' of this method.         Console.ReadLine()     End Sub End Module 

One easily overlooked consideration is that .NET Remoting must be able to create the requested type inside the component host's application domain. In other words, if the component host is running on a remote server, the assembly that describes the remote object must be available in the server's global assembly cache (GAC) or in the same directory as the host application. This error is particularly tricky because it results in a run-time error, not a compile-time error. Visual Studio .NET (or the Visual Basic compiler) has no way of identifying that a component host requires the assembly for the remote object because its details are contained in a separate configuration file.

If you're developing in Visual Studio .NET, the easiest way to ensure that the component assembly is available is to add a reference to it. This way, whenever you compile the component host, the latest version of the remote object assembly is placed in the component host directory. If you're compiling your application at the command line, just make sure you manually copy the component assembly to the same directory.

The Client Application

In our example, the client application is also created as a Windows Forms project. Like the component host, the client requires a configuration file that tells it where to find the object. (Technically, because this is a client-activated object, the configuration file doesn't tell the client where to find the object so much as it tells the client on what machine it should instantiate the object. The remote machine must have a copy of the assembly and a listening component host.

The client configuration file is shown in Listing 4-9.

Listing 4-9 The client configuration file
 <configuration>    <system.runtime.remoting>       <application name="SimpleClient">          <client url="tcp://localhost:8080/SimpleServer">             <activated              type="RemoteObjects.RemoteObject, RemoteObjects"/>          </client>          <channels>             <channel ref="tcp client"/>          </channels>       </application>    </system.runtime.remoting> </configuration> 

This time, a client TCP/IP channel is defined:

 <channel ref="tcp client"/> 

The port number (8080) and the name of the component host are incorporated into a URL:

 <client url="tcp://localhost:8080/SimpleServer"> 

The URL actually specifies five pieces of information the protocol, the machine name, the port number, the application name, and the object identifier.

 <client url="[Protocol]://[MachineName]:[Port]/[Application]/[Object]"> 

In the case of a client-activated object, the final object identifier portion is not required, because the client creates the object on its own.

In this example, the machine name is identified only as localhost, which is a loopback alias that always points to the current computer. Alternatively, you can use a machine name:

 <client url="tcp://production:8080/SimpleServer"> 

Or an IP address:

 <client url="tcp://226.116.109.100:8080/SimpleServer"> 

Finally, the configuration file identifies the type and assembly of the remote component:

 <activated type="RemoteObjects.RemoteObject, RemoteObjects"/> 

This configuration file is read and applied with a similar call to the Remoting­Configuration.Configure method:

 RemotingConfiguration.Configure("SimpleClient.exe.config") 

The client application also needs a reference to the component assembly of the remote object. Otherwise, you have no way to call its methods or set its properties in code because .NET won't be able to check the type metadata and validate your statements. Clearly, this requirement makes for a few distribution headaches. In Chapter 11, I'll explain how you can sidestep this problem with interfaces. If you're having trouble keeping this arrangement straight, refer to Figure 4-5, which shows all three projects and the required references. In this example, all projects are part of one Visual Studio .NET solution.

Figure 4-5. Three projects in a .NET Remoting solution

graphics/f04dp05.jpg

Note

Always remember that the client can't create an instance of a remote object unless it knows a bare minimum of information about that object! This point is critical but easily overlooked when you're starting out with .NET Remoting. In this example (which doesn't use interfaces), that means the client needs a reference to the remote object's assembly.


Finally, now that the client's channel is registered, the client can create the remote object as if it were a local object by using the New keyword. The sample program in Listing 4-10 does exactly that and tests the current location of the component.

Listing 4-10 The client test form
 Imports System.Runtime.Remoting Public Class ClientForm     ' (Designer code omitted.)     Private Sub ClientForm_Load(ByVal sender As System.Object, _       ByVal e As System.EventArgs) Handles MyBase.Load         ' Configure the client.         RemotingConfiguration.Configure("SimpleClient.exe.config")         ' Display the current domain.         MessageBox.Show("The client application is executing in: " & _                          AppDomain.CurrentDomain.FriendlyName)         ' Create the remote object, and determine what domain it is         ' executing in.         Dim RemoteObj As New RemoteObjects.RemoteObject()         MessageBox.Show("The remote object is executing in: " & _                          RemoteObj.GetActiveDomain)     End Sub End Class 

Remember that before you can start this program, you need to run the component host. Otherwise, the connection will be refused.

When you run this simple test program, two message boxes display in succession, as shown in Figure 4-6. Together they demonstrate that the remote component is running in a separate application domain.

Figure 4-6. Interacting with a remote component

graphics/f04dp06.jpg

A Remotable Component with User Interface

To bring the point home, you might want to try a different test. This one is particularly interesting if you're testing on separate computers. Replace the GetActive­Domain function with the ShowActiveDomain subroutine shown here:

 Public Class RemoteObject     Inherits MarshalByRefObject     Public Sub ShowActiveDomain()         MessageBox.Show(AppDomain.CurrentDomain.FriendlyName)     End Sub End Class 

Now, when you invoke this method you'll notice two interesting details. First, if you're running the client and component host on separate computers, the second message box appears on the remote computer because it is being displayed in the application domain of the component host. Second, the client application is stalled until you click OK to confirm the message. This example offers further proof that the component is really running remotely, and it hints at some of the reasons that middle-tier components shouldn't get involved in the user interface. If you want to provide tracking or diagnostic information, you'll generally want to design a separate component, as described in Chapter 14.

Testing with Visual Studio .NET

If you change any part of the component's code, you'll need to rebuild each of the three projects. First, change the component and rebuild it. Next, make sure you rebuild the server project (not just rerun it) by right-clicking on the project item and choosing Rebuild. This ensures that the modified component assembly is copied to the component host directory. (Otherwise, the component host will create the old version of the remote object when it receives a client request.) Finally, you can restart the client application.

When you understand .NET Remoting basics, the best choice is to combine projects in one solution. This makes it easy to set breakpoints and variable watches in the server code, client code, or remote object code and have them work transparently. Figure 4-7 shows the solution settings you need. When you start the project, both the component host project and the client project are compiled and executed. If you change the remote object, however, you need to manually recompile it as described earlier.

Figure 4-7. Solution settings for a .NET Remoting project

graphics/f04dp07.jpg

Using a Different Formatter

You might have noticed that the configuration file specifies the channel (the transport protocol for the communication) but not the formatter. That's because the TCP/IP channel defaults to binary format and the HTTP channel defaults to SOAP (XML-based text) communication. Although this is the default, other combinations can make sense. For example, you could use binary communication over an HTTP channel to allow .NET programs to communicate efficiently over the Internet and through a firewall.

In this case, you need to manually specify the formatter in the configuration file by adding a <formatter> tag inside the <channel> tag and set the ref attribute to soap or binary. Listing 4-11 shows how the server configuration file would look for this simple example if it were to use the HTTP channel with the Binary Formatter.

Listing 4-11 A component host that uses the HTTP channel with the Binary Formatter
 <configuration>    <system.runtime.remoting>       <application name="SimpleServer">          <service>             <activated type="RemoteObjects.RemoteObject,                              RemoteObjects"/>          </service>          <channels>             <channel ref="http server" port="8080">                <serverProviders>                  <formatter ref="binary">                </serverProviders>              </channel>          </channels>       </application>    </system.runtime.remoting> </configuration> 

And the client's configuration file would look as shown in Listing 4-12.

Listing 4-12 A client that uses the HTTP channel with the Binary Formatter
 <configuration>    <system.runtime.remoting>       <application name="SimpleClient">          <client url="tcp://localhost:8080/SimpleServer">             <activated              type="RemoteObjects.RemoteObject, RemoteObjects"/>          </client>          <channels>             <channel ref="http client" port="8080">               <clientProviders>                  <formatter ref="binary">               </clientProviders>             </channel>          </channels>       </application>    </system.runtime.remoting> </configuration> 

Using Multiple Channels

You can create a component host that listens for connections on more than one channel. This scenario is most common if you need to support different types of clients. For example, local clients can use the faster binary TCP/IP connection, whereas remote clients connecting over the Internet can use SOAP over HTTP, as in the example shown in Listing 4-13.

Listing 4-13 A component host that supports multiple channels
 <configuration>    <system.runtime.remoting>       <application name="SimpleServer">          <service>             <activated              type="RemoteObjects.RemoteObject, RemoteObjects"/>          </service>          <channels>             <channel ref="http server" port="8080">             <channel ref="tcp server" port="8081">          </channels>       </application>    </system.runtime.remoting> </configuration> 

The client can then choose the channel to use when it specifies the URI for the remote object.

Consider these points when using multiple channels:

  • The only disadvantage to using multiple channels is that the listening process effectively ties them up, preventing any other application from using them. Therefore, if you run more than one component host on the same workstation, take care not to use the same ports. Alternatively, you can configure the servers to register channels programmatically, allowing dynamic port selection (a technique shown in Chapter 11).

  • You can't register more than one channel of the same type using a configuration file (for example, two "tcp server" channels using different ports) because they have the same name. Chapter 11 shows how you can overcome this limitation with dynamic registration.

  • A single component host can allow the creation of several different types of objects if you simply add additional <activated> or <wellknown> elements. However, you can't configure it so that some components are available through certain channels while others are not. To implement this design, you need to create a separate component host or create a component host that creates separate application domains using the CreateDomain and CreateInstance methods of the System.AppDomain class.

  • The number of objects a component host can create isn't in any way linked to the number of channels it uses. .NET handles all the infrastructure, automatically dispatching messages to the appropriate component instance, and a single channel can serve multiple clients.



Microsoft. NET Distributed Applications(c) Integrating XML Web Services and. NET Remoting
MicrosoftВ® .NET Distributed Applications: Integrating XML Web Services and .NET Remoting (Pro-Developer)
ISBN: 0735619336
EAN: 2147483647
Year: 2005
Pages: 174

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