In the world of .NET Remoting, there are essentially three types of classes:
Figure 4-1 shows the role the remotable and serializable types play. Figure 4-1. Remotable and serializable types
Serializable ClassesA serializable class generally plays the role of the "information package" (introduced in Chapter 2). .NET knows enough about a serializable class to convert all its information to a stream of bytes and reconstruct it in another process. In essence, a serializable object can be copied into any other application domain.
Many key .NET types are automatically serializable. These include everything from basic structures (including integers, floating-point numbers, and dates and times) to more sophisticated classes (such as strings, exceptions, and the DataSet). Table 4-1 provides a quick overview of some key serializable types in the .NET Framework. This is only a small fraction of the total number of serializable types available in the class library. To determine whether any other .NET type is serializable, look it up in the class library reference and check whether the type definition is preceded with the <Serializable> attribute, as shown here: <Serializable()> _ Public Class Random
Keep in mind that when you serialize arrays or collections, the contained content must also be serializable. For example, you can serialize an array of DataSet objects but not a collection of Buffer objects (because the System.Buffer class is intended to represent direct access to unmanaged memory and therefore isn't serializable). You can also create your own serializable types by simply applying the <Serializable> attribute. To re-create the serializable object in the new application domain, however, you must be sure that both the sender and recipient have a reference to the assembly that defines your custom type. Otherwise, the recipient will have a stream of bytes and no way to interpret it, which will lead to a run-time exception. You should be aware of a few other serialization rules:
Listing 4-1 shows the CustomerDetails information package from Chapter 1 recast as a serializable type. Listing 4-1 A serializable class<Serializable()> _ Public Class CustomerDetails Private _ID As Integer Private _Name As String Private _Email As String Private _Password As String ' (Remainder of code omitted.) End Class Listing 4-2 shows how to write the CustomerDetails class if we decide we shouldn't serialize the password for security reasons. It defaults to a blank string when transmitted across an application domain. Listing 4-2 A partially serializable class<Serializable()> _ Public Class CustomerDetails Private _ID As Integer Private _Name As String Private _Email As String <NonSerialized()> Private _Password As String ' (Remainder of code omitted.) End Class Note Serializable classes aren't used only in .NET Remoting. You can also use them programmatically to send a class to stream or a file. In these cases, you use the methods of the Binary Formatter (in the System.Runtime.Serialization.Formatters.Binary namespace) or the SOAP Formatter (in the System.Runtime.Serialization.Formatters.Soap namespace). Note that the Binary Formatter is part of the core mscorlib.dll assembly and is always available, whereas the SOAP Formatter requires a reference to the System.Runtime.Serialization.Formatters.Soap.dll assembly. Remotable ClassesEvery public property, method, and member variable in a class that derives from System.MarshalByRefObject can be used remotely. For example, Listing 4-3 shows how you might recast the CustomerDB service provider class so that it can be accessed from another computer using .NET Remoting. Listing 4-3 A remotable CustomerDBPublic Class CustomerDB Inherits System.MarshalByRefObject ' (The class code is unchanged.) End Class With .NET Remoting, the communication process is governed through a proxy object. This approach is conceptually similar to the way that other distributed technologies work, including XML Web services and traditional COM/DCOM programming. With proxy communication, your local code talks directly to a proxy object that "mimics" the remote object. When you call a method on the proxy object, it calls the remote object behind the scenes, waits for the response, and then returns the appropriate information to your code. This interception mechanism is a marvelous innovation for the application programmer, who doesn't need to worry about messy infrastructure details such as cross-domain or cross-process calls, network transport, or message formatting. Instead, the application communicates with a proxy object contained in its application domain. Even better, this proxy object behaves just like the original object would if it were instantiated locally. In reality, the .NET Remoting architecture is a little more sophisticated. To start with, .NET Remoting proxies are slightly more complicated because there are actually two proxy layers (the transparent proxy that you communicate with directly and the real proxy that .NET uses to dispatch the message when it decides remote communication is necessary). However, this bit of plumbing isn't terribly significant. It's more important to understand how .NET Remoting divides the work among different types of services. When communicating with a remote object, the proxy class doesn't actually perform all the required work. Instead, it communicates with a formatter that packages the client request or server response in the appropriate format. The formatter then communicates with a transport channel that transmits the information using the appropriate protocol. This multilayered approach enables developers to create their own formatters and channels and still use the same .NET Remoting model. In fact, you can change the channel and formatter used by an application without even recompiling the code. All you need to do is tweak a few configuration settings in an XML file. Figure 4-2 shows how a method invocation is packaged on the client side and then unpacked on the server side. In this case, the information flows from the client to the server, but the return value also flows back to server to the client along the same path. Note that this diagram shows more detail than Figure 4-1, which ignores the proxy layer for the sake of simplicity. Figure 4-2. The multilayered .NET Remoting architecture
The formatter serializes the message to a binary or SOAP representation before it is sent. Incidentally, the formatter is only one possible channel sink that a message can pass through on its way to another process. If you like, you can create your own custom sinks and place them in the channel sink chain. These classes can intercept the communication (which you can manipulate using the IMessage interface) and provide additional services such as encryption, logging, or compression. The key concept is that channel sinks take effect on both the client and server. As you can see in Figure 4-2 and Figure 4-3, the server-side sinks are a mirror image of the client-side sinks. Figure 4-3. Interception with a custom sink
The last sink in the client side of the chain is always the transport sink, which is responsible for transmitting the message to the remote application domain. Figure 4-3 shows one example of how you might insert a custom sink into the chain. We won't discuss the creation of custom sink objects in this book because it's a low-level task best left to experienced infrastructure programmers (such as those at Microsoft). The Remote Component HostThere's one other simplification that I've used so far. As described earlier, all MarshalByRefObject instances can be invoked remotely. However, just defining a MarshalByRefObject doesn't make it available to the client. What's needed is a dedicated server application a program that listens for client requests and creates the appropriate objects. Technically, it is possible to create a MarshalByRefObject that is also a component host, but this is likely to lead to a good deal of confusion. A better approach is to create a dedicated server-side application. This application can take one of several forms:
A component host needs to perform only two tasks. First, it creates the server-side channels that listen for client requests. When a client request is received, the .NET Remoting infrastructure automatically creates the required remote object in the same application domain as the component host. After the component has been created, the component host also facilitates the communication between the object in its application domain and the remote client. However, the client never interacts directly with the component host. Figure 4-4 shows a simplified diagram of this relationship. Figure 4-4. The role of the component host
In this chapter, we use a straightforward Windows application for a component host. Note The .NET Remoting infrastructure uses a thread pool to listen for client requests. That means that more than one client can create or use an object at the same time. If you design components that are shared between multiple clients, you must use the threading techniques discussed in Chapter 6 and Chapter 7. |