.NET Remoting Fundamentals

In the world of .NET Remoting, there are essentially three types of classes:

  • Serializable classes

    These classes, which are marked with the <Serializable> attribute, can be sent between processes, applications, and computers.

  • Remotable classes

    These classes derive from System.MarshalByRefObject (or indirectly through another class that derives from MarshalBy­RefObject, such as Component). This provides them with the innate capability to be invoked remotely (outside of their application domain).

  • Ordinary classes

    On their own, these classes can't be used in a .NET Remoting scenario for communication or for distributed execution. They might be used as a local part of an application, but that topic doesn't interest us in this chapter.

Figure 4-1 shows the role the remotable and serializable types play.

Figure 4-1. Remotable and serializable types

graphics/f04dp01.jpg

Serializable Classes

A 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.

What Is an Application Domain?

Every .NET program consists of one or more threads inside an application domain (also called an app domain). The concept of an application domain is similar to the idea of Windows processes: application domains are isolated containers that can't communicate directly. In addition, if an application crashes in one application domain, it won't affect an application in another domain. Essentially, application domains have all the benefits of separate processes but have reduced overhead. Creating a separate application domain is handled by the .NET common language runtime (CLR) and doesn't require as much work as spawning a new process. For that reason, application domains are often described as "lightweight" processes.

Whenever you want to communicate with an object in another application domain, you need .NET Remoting. This is true regardless of whether the application domains are in separate computers or are hosted inside the same Windows process. You can retrieve a reference to an AppDomain object that represents the current application domain by using the static AppDomain.CurrentDomain property.

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 

Table 4-1. Key Serializable Types

Category

Types

Simple value types

Boolean, Byte, Char, DateTime, Decimal, Double, Enum, Guid, Int16, Int32, Int64, Sbyte, Single, TimeSpan, UInt16, UInt32, and UInt64

Core classes

Array, EventArgs, Exception, Random, String, and almost all collection classes

Data classes

DataSet, DataRow, DataRelation, DataTable, and the native SQL Server specific types

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:

  • For the <Serializable> attribute to work, every member and property must also be serializable.

    If you create a class that contains another custom class, this custom class must be serializable. If you derive from a parent class, this class must also be serializable.

  • When a class is serialized, every other object it references is also serialized.

    This means, for example, that if you serialize the first item in a linked list, you actually end up transmitting the entire list across application domains (because the first item points to the second, which points to the third, and so on). This can have unintended consequences for network bandwidth.

  • If there is any member variable you don't want copied, add the <NonSerialized> attribute just before it.

    That way, the variable is reinitialized to an empty value when the object is copied. This proves useful if an object contains references that might not be valid in another application domain, such as a file handle.

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 Classes

Every 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 CustomerDB
 Public 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

graphics/f04dp02.jpg

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

graphics/f04dp03.jpg

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 Host

There'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 dedicated Windows service, which starts automatically when the server is switched on and requires no user intervention.

  • A console application, which allows simple diagnostic or log information to be displayed on the screen.

  • A full-fledged Windows application, which can provide a more sophisticated interface. Remember, however, that your remoted components probably won't interact with their component host. If you want to display information such as the currently connected clients, you might want to add a user interface class to the component itself or devise a more sophisticated logging strategy.

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

graphics/f04dp04.jpg

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.




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