This chapter gets you started with your first remoting application. Before going directly into the code, I present the differences between .NET Remoting and other distributed application frameworks. I then introduce you to the basic types of remote objects, server-activated objects and client-activated objects, and show you how to pass data by value. I also give you some basic information about lifetime management issues and the generation of metadata, which is needed for the client to know about the interfaces of the server-side objects.
As you've seen in the previous chapter, several different architectures for the development of distributed applications already exist. You might therefore wonder why .NET introduces another, quite different way of developing those kinds of applications. One of the major benefits of.NET Remoting is that it's centralized around well-known and well-defined standards like HTTP and SOAP.
Comparing .NET Remoting to other remoting schemas is like comparing COM development in Visual Basic to C++. Visual Basic 6 allowed developers to concentrate on the business needs their applications had to fulfill without having to bother with the technical details of COM. The C++ programmers had to know the exact specifications of COM (at least before the introduction of ATL) and implement truckloads of code for supporting them.
With .NET this concept of absolute ease of implementation has been extended to the development of distributed applications. There are no proxy/stub-compilation cycles as in Java RMI. You don't have to define your interfaces in an abstract language as you would with CORBA or DCOM. A unique feature is that you don't have to decide up front on the encoding format of remoting requests; instead, you can switch from a fast binary format to SOAP by changing one word in a configuration file. You can even provide both communication channels for the same objects by adding another line to the configuration. You are not fixed on one platform or programming language as with DCOM, COM+, and Java EJB. Configuration and deployment is a lot easier than it was in DCOM.
Even though .NET Remoting provides a lot of features, it doesn't lock you in. Quite the contrary: it can be as easy as you like or as complex as you need. The process of enabling remoting for an object can be as straightforward as writing two lines of code or as sophisticated as implementing a given transfer protocol or format on your own.
.NET Remoting offers the developer and administrator a vastly greater choice of protocols and formats than any of the former remoting mechanisms. In Figure 2-1, you can see a simplified view of the .NET Remoting architecture. Whenever a client application holds a reference to a remote object, it will be represented by a TransparentProxy object, which "masquerades" as the destination object. This proxy will allow all of the target object's instance methods to be called upon it. Whenever a method call is placed to the proxy, it will be converted into a message, and the message will pass various layers.
Figure 2-1: The .NET Remoting architecture (simplified)
The message will pass a serialization layer-the formatter-which converts it into a specific transfer format such as SOAP. The serialized message later reaches a transport channel, which transfers it to a remote process via a specific protocol like HTTP or TCP. On the server side, the message also passes a formatting layer, which converts the serialized format back into the original message and forwards it to the dispatcher. Finally, the dispatcher calls the target object's method and passes back the response values through all tiers. This architecture is shown in detail in Chapter 7.
In contrast to other remoting architectures, most layers can either be extended or completely replaced, and additional layers can be chained to the baseline .NET Remoting Framework to allow for custom processing of messages. (More about this in Chapters 7, 8, and 9.)
You can easily switch between implementations of the different layers without changing any source code. A remoting application that's been written using a binary TCP-based protocol can be opened for third parties using a SOAP/HTTP-based protocol by changing some lines in a configuration file to replace the .NET Remoting transport channel.
Most remoting systems like DCE/RPC, RMI, and J2EE demand a manual creation of so-called proxy/stub objects. The proxy encapsulates the connection to the remote object on the client and forwards calls to a stub object on the server, which in turn passes them on to the "real" object. In most of these environments (at least in CORBA, DCE/RPC, and DCOM) the "source code" for generating these objects has to be written in an abstract Interface Definition Language and precompiled to generate implementation headers for a certain programming language.
In comparison to this traditional approach, .NET Remoting uses a generic proxy for all kinds of remote objects. This is possible because .NET is the first framework that has been designed with remoting in mind; on other platforms these capabilities have been retrofitted and therefore have to be integrated into the given architecture and programming model.
Such ease of remoting poses the potential problem of your using an incorrect design. This book will help you to make the right architectural decisions. For example, even though you don't have to write any interface definitions in IDL, you still should separate interface from implementation; you can, however, write both in the same language-in any .NET programming language. .NET Remoting provides several different ways of defining those interfaces, as discussed in the following sections.
In this case, the server-side object's implementation exists on the client as well. Only during instantiation is it determined whether a local object or an object on the remote server will be created. This method allows for a semitransparent switch between invoking the local implementation (for example, when working offline) and invoking server-side objects (for example, to make calculations on better-performing servers when connected to the network).
When using this method with "conventional" distributed applications that don't need to work in a disconnected scenario, you need to use a lot of care, because it poses some risks due to programming errors. When the object is mistakenly instantiated as a local object on the client and passed to the server (as a method's parameter, for example) you might run into serious troubles, ranging from InvalidCastExceptions to code that works in the development environment but doesn't work in the production environment due to firewall restrictions. In this case the client has in reality become the server, and further calls to the object will pass from the server to your clients.
When creating a distributed application, you define the base classes or interfaces to your remote objects in a separated assembly. This assembly is used on both the client and the server. The real implementation is placed only on the server and is a class that extends the base class or implements the interface.
The advantage is that you have a distinct boundary between the server and the client application, but you have to build this intermediate assembly as well. Good object-oriented practices nevertheless recommend this approach! Unfortunately, as you will see in later chapters, this approach doesn't work in all .NET Remoting scenarios.
This approach is one of the more elegant ones. You develop the server in the same way as when using the shared assemblies method. Instead of really sharing the DLL or EXE, you later extract the necessary metadata, which contains the interface information, using SoapSuds.
SoapSuds will either need the URL to a running server or the name of an assembly as a parameter, and will extract the necessary information (interfaces, base classes, objects passed by value, and so on). It will put this data into a new assembly, which can be referenced from the client application. You can then continue to work as if you'd have separated your interfaces right from the beginning. For certain scenarios-especially when using client-activated objects-this is the way to go.
With the exception of earlier TCP/IP RPC implementations, in which you even had to worry about little-endian/big-endian conversions, all current remoting frameworks support the automatic encoding of simple data types into the chosen transfer format. The problem starts when you want to pass a copy of an object from server to client. Java RMI and EJB support these requirements, but COM+, for example, did not. The commonly used serializable objects within COM+ were PropertyBag and ADO Recordsets-but there was no easy way of passing large object structures around.
In .NET Remoting the encoding/decoding of objects is natively supported. You just need to mark such objects with the [Serializable] attribute or implement the interface ISerializable and the rest will be taken care of by the framework. This even allows you to pass your objects cross-platform via XML.
The serialization mechanism marshals simple data types and subobjects (which have to be serializable or exist as remote objects), and even ensures that circular references (which could result in endless loops when not discovered) don't do any harm.
In distributed applications there are generally three ways of managing lifetime. The first is to have an open network connection (for example, using TCP) from the client to the server. Whenever this connection is terminated, the server's memory will be freed.
Another possibility is the DCOM approach, where a combined reference counting and pinging mechanism is used. In this case the server receives messages from its clients at certain intervals. As soon as no more messages are received, it will free its resources.
In the Internet age, in which you don't know your users up front, you cannot rely on the possibility of creating a direct TCP connection between the client and the server. Your users might be sitting behind a firewall that only allows HTTP traffic to pass through. The same router will block any pings the server might send to your users. Because of those issues, the .NET Remoting lifetime service is customizable as well. By default an object will get a lifetime assigned to it, and each call from the client will increase this "time to live." The .NET Framework also allows a sponsor to be registered with a server-side object. It will be contacted just before the lifetime is over and can also increase the object's time to live.
The combination of these two approaches allows for a configurable lifetime service that does not depend on any specific connection from the server to the client.
When you use remote objects (as opposed to using copies of remotely generated objects that are passed by value), .NET automatically keeps track of where they originated. So a client can ask one server to create an object and safely pass this as a method's parameter to another server.
The second server will then directly execute its methods on the first server, without a round-trip through the client. Nevertheless, this also means there has to be a direct way of communication from the second server to the first one-xsthat is, there must not be a firewall in between, or at least the necessary ports should be opened.
This is partly the same as it was in Visual Basic 6. VB 6 allowed you to create applications without a lot of up-front design work. This often led to applications that were hardly maintainable in the long run.