Designing Enterprise Applications with Microsoft Visual Basic .NET
Authors: Oliver R.I.
Published year: 2002
|I l @ ve RuBoard|
.NET Remoting is an extremely flexible interapplication communication technology provided by the .NET Framework. It provides a tightly coupled communication model that also supports connecting to distributed systems. One major difference between remoting and Web services is that you can pass objects by reference to the target of a remoting call, which enables the target to actively communicate back to the originator of the call. This known as bidirectional communication . Remoting provides several specific features:
The general concept behind remoting is you have the client and a service. The service registers types to be available to clients. Clients register types in order to be able to bind to types provided by the service. Type registration applies only to an application instance; to host a remoting service, you must have a process. You then have two choices: create your own process (application or service) to host your remoting service, or host your remoting service in IIS (more on this later).
The client and service do not need to be on separate machines. Remoting can be used in a distributed environment, but it is a generic interprocess communication technology. Much like the DCOM technologies it replaces , remoting lets you create and invoke remote objects and marshal data, objects, and references.
Remoting is most useful for communication between machines on the same network. It is not really intended as an Internet communication technology. For that purpose, Microsoft recommends XML Web services.
The key to providing a class through remoting is the RemotingConfiguration class. This class provides all of the necessary methods to configure and monitor the remoting services provided and consumed by a process. Table 5-1 lists the properties supported by this class that provide information specific to the current application and process.
Table 5-1. Public Shared Properties of the RemotingConfiguration Class
The methods that control the registration of service and client types are listed in Table 5-2. These methods give a process control over how services provided by that application behave. In addition to the ability to register types, you can also find out whether a type has been registered, which types have been registered (for both service and client), and which types have been activated on the client.
Table 5-2. Public Shared Methods of the RemotingConfiguration Class
To have an application host a service through remoting, you must do the following:
For a client to consume a service through remoting, the following must happen:
Picking the Right Channel
Remoting offers a performance advantage over XML Web services because it provides a choice of communication channels. The two channel classes provided with the .NET Framework are TcpChannel and HttpChannel .
The TcpChannel Class
TcpChannel offers by far the best performance. In an intranet scenario, where security is not an issue, this is the best-performing option. A remoting call through TcpChannel is generally five to seven times faster than an equivalent call through HttpChannel .
You cannot use TcpChannel when a remote object is hosted in IIS, so TcpChannel is a remoting-only solution. You must create your own process to host objects with TcpChannel .
The HttpChannel Class
HttpChannel has benefits as well. The overhead for a remoting call through HttpChannel is greater than that for TcpChannel , but if you host the remote object in IIS (a requirement if you are using HttpChannel ), you gain a whole host of scalability benefits. In other words, by hosting your remote objects in IIS, you can take advantage of features such as clustering, robust scaling, session state, and farming.
Sometimes you'll have to use HttpChannel , which is less efficient than TcpChannel . To make things less painful, you can force the use of binary encoding for all object marshaling instead of the default marshaling.
When you pass parameters to or receive values from remote methods, you're marshaling data (in much the same way that Web services work). The important twist for remoting is the ability to pass variables by value or, more significantly, by reference. The default, as for Visual Basic .NET in general, is to marshal all of your parameters and return types by value.
Marshaling Types By Value
The fact that the default marshaling of types is by value is not a major problem. In fact, you don't even need to think twice about it when you're using standard types that support serialization. If, on the other hand, you want to marshal a custom type, you must implement a form of serialization (as described earlier).
Marshaling Types By Reference
If you want a class to be passed by reference instead of by value, you must take a different approach. A class that supports by reference marshaling through remoting must derive from the MarshalByRef class. This class provides all of the functionality required to allow manipulation of a remote object. Objects that are marshaled by reference can support remote events and calls to methods and properties. This is pretty slick, and you get it virtually for free just by deriving a class from MarshalByRef .
To support having a service call a method on a client object passed by reference, the client must also register a communications channel. Otherwise, any method invocation on the passed object will fail. (The client would essentially not be listening for a request.)
Bidirectional communication using pass-by-reference is a great feature but generally should not be used in a distributed application environment. The overhead of calling back to the client just to get the value of properties is a complete waste of resources. You're better off serializing the data. Pass-by-reference is most useful when you want remote systems to be able to trigger events or perform some other specific operation.
Using a Separate Interface
You have a choice when designing your remoting implementation. You can remote a class and provide the client with a copy of that class ”you need a reference to the type you want to consume, after all ”or you can provide a reference to an interface supported by your remote class. Which one do you choose? Of course, it depends.
Remoting an interface instead of a class has certain advantages. You can put the interface into a separate assembly from the class that implements it. Designing in this manner allows the publisher to distribute an assembly that allows clients to talk to their remoting objects, without publishing any real code. If you were to remote a class, you'd have to provide all clients with a definition of that class (or at least a base class). Unfortunately, that class would contain all of the implementation code for the remoted class.
It is often desirable to have the client application be completely unaware of the implementation details of your remoted object. By using an interface (that's available, say, in a separate assembly, as shown in Figure 5-3), you can allow the client to consume a service, without providing any of the implementation details.
Figure 5-3. Using a common interface to hide implementation details.
Remember that if you ship out an assembly containing a remoted class, any customer can run ILDASM on the assembly (or one of the other reverse-engineering tools) and extract the implementation code. If you distribute only an interface, there's nothing to reverse-engineer .
You've seen a remoting method that results in new object instances being created on the server for each incoming client request. This is not always desirable. Sometimes you might want to allow only a single object to exist ”a singleton.
Remoting an object as a singleton is fairly simple. In the same way that we registered other types through remoting, we can also register singletons. Unlike with true singletons, you must define your class with a public constructor because the remoting infrastructure is responsible for creating and maintaining the singleton throughout its lifetime and will only allow a single instance of the class to be created through the remoting channel.
The only thing you really need to worry about is when your singleton is targeted for garbage collection. That's where lifetime leases come into play.
When you define a singleton for remoting, you must carefully consider how the object is going to be used. More specifically , you need to know whether it is acceptable for the singleton object to be recycled ( garbage-collected and then re-created) even if there are connected clients. Every remote object has a lifetime lease. A singleton can, and most likely will, exceed the default lifetime lease and be destroyed . This is not normally desirable behavior for a singleton ”it should last the entire lifetime of the application. If you don't want to deal with this, you must change the default lifetime lease to something more appropriate. In this case, an infinite lifetime lease.
Creating a infinite lifetime lease is simple and straightforward. The MarshalByRef class defines a method called InitializeLifetimeService for retrieving the lifetime service object that controls the lifetime of the current class's instance. By overriding InitializeLifetimeService , you can provide and control your own lease information. The simplest case, returning a reference to Nothing , results in a remoting object with an infinite lifetime lease. You can thus implement a true singleton through remoting. The following example illustrates how to do this:
PublicOverridesFunctionInitializeLifetimeService()AsObject ReturnNothing'Specifyaninfinitelifetimelease EndFunction
That's all there is to it. You can see similar code at work in the remoting example toward the end of the chapter.
Remoting itself provides no features that directly support secure communication. Instead, three general approaches are available for securing your communication: securing the communication channel itself, securing the message's content, or both. We'll look at each of these options in turn .
Generally, as more security features are implemented, an application's performance will get worse . And although absolute security is a desirable goal, it is never practical technically or financially unless you bury your application in a bunker! Compromises are commonplace, and you must balance the need to provide security with the overall cost of implementing it.
Securing the Communications Channel
As you know, only two remoting channels are available out of the box: HttpChannel and TcpChannel . TcpChannel , a channel implementation over the low-level TCP protocol, does not natively provide support for secure communications. You can use TcpChannel if the network environment your application is running in supports wire-level protection (such as IPSec). Unfortunately, this is not common. It generally leaves TcpChannel as a high-speed but insecure communications channel.
The alternative, of course, is HttpChannel . Recall that remoting hosts that use HttpChannel must reside in IIS. IIS provides a lot of goodies for free (although features are rarely free where performance is concerned ), including SSL and user authentication. In fact, hosting remote objects in IIS allows you to support complex security solutions with little coding required on your part.
Securing the Message's Content
You secure a message's content by encrypting it. In Chapter 9, I'll explain how to use the .NET Framework's CryptoService providers to properly encrypt data. However, you'll need to consider some issues with this approach. First, you must manually encrypt and decrypt the contents of your messages. This requires a lot of additional work but might make sense if you're using the remoting TcpChannel . You'll take a performance hit, but probably less of one than if you use HttpChannel over SSL (as discussed in the previous section).
Ultimately, the choice is up to you. Depending on your performance requirements, you might try several different combinations just to see what fits. Remember that implementing your own security strategy is not for the faint-hearted. More often than not, you're far better off taking advantage of built-in technologies that free you from many of the implementation details. Using IIS to host your remote objects is an excellent example. The services provided by IIS ”including user authentication, secure sockets, and a scalable performance architecture ”are nothing to sniff at.
Tying It All Together
To tie together most of the topics discussed so far, I created a remoting sample that does a little bit of everything. The sample, which is simply called Remoting, does the following:
To start with, we'll define the following custom marshaling types in a common assembly:
PublicClassMyClient InheritsMarshalByRefObject PublicSubPostMessage(ByValmsgAsString) MsgBox(msg) EndSub EndClass <Serializable()>PublicStructureMachineData PublicNameAsString PublicProcessIDAsInteger PublicIPAddresses()AsSystem.Net.IPAddress EndStructure
Next, we'll define an interface, also in the shared common assembly:
PublicInterfaceIMachine ReadOnlyPropertyName()AsString ReadOnlyPropertyProcessID()AsInteger ReadOnlyPropertyIPAddresses()AsSystem.Net.IPAddress() FunctionGetMachineData()AsMachineData SubSendMeAMessage(ByValcAsMyClient) EndInterface
In the server application, we'll implement the IMachine interface with the Machine class. To simplify debugging and to illustrate how this works, we'll include a Console.WriteLine call in each of the methods. When you run the host application, you'll see printed statements documenting all of the remoting activities:
ImportsRemoteInterfaces ImportsSystem.Collections ImportsSystem.Net PublicClassMachine InheritsMarshalByRefObject ImplementsIMachine PublicSubNew() MyBase.New() Console.WriteLine("Machineobjectcreated") EndSub PublicReadOnlyPropertyName()AsStringImplementsIMachine.Name Get Console.WriteLine("Machine.Namecalled") ReturnDns.GetHostName() EndGet EndProperty ReadOnlyPropertyIPAddresses()AsIPAddress()_ ImplementsIMachine.IPAddresses Get Console.WriteLine("Machine.IPAddressescalled") ReturnDns.GetHostByName(Dns.GetHostName()).AddressList EndGet EndProperty PublicReadOnlyPropertyProcessID()AsIntegerImplements_ IMachine.ProcessID Get Console.WriteLine("Machine.ProcessIDcalled") ReturnSystem.Diagnostics.Process.GetCurrentProcess.Id EndGet EndProperty PublicFunctionGetMachineData()AsMachineDataImplements_ IMachine.GetMachineData Console.WriteLine("Machine.GetMachineDatacalled") DimmdAsNewMachineData() md.Name=Dns.GetHostName() md.IPAddresses=Dns.GetHostByName(Dns.GetHostName()).AddressList md.ProcessID=System.Diagnostics.Process.GetCurrentProcess.Id Returnmd EndFunction PublicSubSendMeAMessage(ByValcAsMyClient)Implements_ IMachine.SendMeAMessage c.PostMessage("Hello") EndSub PublicOverridesFunctionInitializeLifetimeService()AsObject ReturnNothing EndFunction EndClass
Once we've implemented the Machine class, all we need to do is register it. In this case, we want to register the class as a singleton ”because there's no need for more than one copy of the class to exist. In this case, the decision was simple. To see how the host process is set up, check out the following example:
ImportsSystem.Runtime.Remoting ImportsSystem.Runtime.Remoting.Channels ImportsSystem.Runtime.Remoting.Channels.Tcp ImportsSystem.Runtime.Remoting.Channels.Http ModuleModule1 PublicSubMain() DimchannelAsTcpChannel channel=NewTcpChannel(8085) ChannelServices.RegisterChannel(channel) RemotingConfiguration.RegisterWellKnownServiceType(_ GetType(Machine), "Machine",WellKnownObjectMode.Singleton) Console.WriteLine("Remotingsetupcomplete") Console.ReadLine() EndSub EndModule
We now have a host process for our remote object. (See Figure 5-4.) To enable our client, all we need to do is create a TcpChannel (because we want to support bidirectional communication ”it's not necessary on the client otherwise), register the IMachine type, and use the Activator.GetObject method to get instances of our remote object.
Figure 5-4. The remoting client example's form.
The implementation code for this form looks like the following:
ImportsRemoteInterfaces ImportsSystem.Runtime.Remoting ImportsSystem.Runtime.Remoting.Channels ImportsSystem.Runtime.Remoting.Channels.Tcp PublicClassForm1 InheritsSystem.Windows.Forms.Form #Region " WindowsFormDesignergeneratedcode " PrivateSubForm1_Load(ByValsenderAsObject,_ ByValeAsSystem.EventArgs)HandlesMyBase.Load DimcAsNewTcpChannel(8086) ChannelServices.RegisterChannel(c) RemotingConfiguration.RegisterWellKnownClientType(_ GetType(IMachine),_ "tcp://localhost:8085/Machine") EndSub PrivateSubMachineButton_Click(ByValsenderAsSystem.Object,_ ByValeAsSystem.EventArgs)_ HandlesMachineButton.Click DimmAsIMachine=_ Activator.GetObject(GetType(IMachine),_ "tcp://localhost:8085/Machine") MsgBox(m.Name) EndSub PrivateSubAddressButton_Click(ByValsenderAsSystem.Object,_ ByValeAsSystem.EventArgs)_ HandlesAddressButton.Click DimmAsIMachine=_ Activator.GetObject(GetType(IMachine),_ "tcp://localhost:8085/Machine") MsgBox(m.IPAddresses) EndSub PrivateSubProcessButton_Click(ByValsenderAsSystem.Object,_ ByValeAsSystem.EventArgs)_ HandlesProcessButton.Click DimmAsIMachine=_ Activator.GetObject(GetType(IMachine),_ "tcp://localhost:8085/Machine") MsgBox(m.ProcessID) EndSub PrivateSubEverythingButton_Click(ByValsenderAsSystem.Object,_ ByValeAsSystem.EventArgs)_ HandlesEverythingButton.Click DimmAsIMachine= Activator.GetObject(GetType(IMachine),_ "tcp://localhost:8085/Machine") MsgBox(m.GetMachineData().ToString()) EndSub PrivateSubMessageButton_Click(ByValsenderAsSystem.Object,_ ByValeAsSystem.EventArgs)_ HandlesMessageButton.Click DimmAsIMachine=_ Activator.GetObject(GetType(IMachine),_ "tcp://localhost:8085/Machine") DimcAsNewMyClient() m.SendMeAMessage(c) EndSub EndClass
To look further at the remoting technology, start with the .NET Framework SDK quick-start samples and tutorials.
|I l @ ve RuBoard|
Designing Enterprise Applications with Microsoft Visual Basic .NET
Authors: Oliver R.I.
Published year: 2002