|
|
As you've seen in this chapter, .NET Remoting applications need to share common information about remoteable types between server and client. Contrary to other remoting schemas like CORBA, Java RMI, and J2EE EJBs, with which you don't have a lot of choice for writing these shared interfaces, base classes, and metadata, the .NET Framework gives you at least four possible ways to do so, as I discuss in the following sections.
The first way to share information about remoteable types is to implement your server-side objects in a shared assembly and deploy this to the client as well. The main advantage here is that you don't have any extra work. Even though this might save you some time during implementation, I really recommend against this approach. Not only does it violate the core principles of distributed application development, but it also allows your clients, which are probably third parties accessing your ERP system to automate order entry, to use ILDASM or one of the upcoming MSIL-to-C# decompilers to disassemble and view your business logic. Unfortunately, this approach is shown in several MSDN examples.
Nevertheless, there are application scenarios that depend on this way of sharing the metadata. When you have an application that can be used either connected or disconnected and will access the same logic in both cases, this might be the way to go. You can then "switch" dynamically between using the local implementation and using the remote one.
In the first examples in this book, I show the use of shared interfaces. With this approach, you create an assembly that is copied to both the server and the client. The assembly contains the interfaces that will be implemented by the server. The disadvantage to using this process of sharing the metadata is that you won't be able to pass those objects as parameters to functions running in a different context (either on the same or another server or on another client) because the resulting MarshalByRefObject cannot be downcast to these interfaces.
Instead of sharing interfaces between the client and the server, you can also create abstract base classes in a shared assembly. The server-side object will inherit from these classes and implement the necessary functionality. The big advantage here is that abstract base classes are, contrary to the shared interfaces, capable of being passed around as parameters for methods located in different AppDomains. Still, this approach has one disadvantage: you won't be able to use those objects without Activator.GetObject() or a factory. Normally when the .NET Framework is configured correctly on the client side, it is possible to use the new operator to create a reference to a remote object. Unfortunately, you can never create a new instance of an abstract class or an interface, so the compiler will block this functionality.
As each of the other approaches has a drawback, let's see what SoapSuds can do for you. This program's functionality is to extract the metadata (that is, the type definition) from a running server or an implementation assembly and generate a new assembly that contains only this meta information. You will then be able to reference this assembly in the client application without manually generating any intermediate shared assemblies.
SoapSuds is a command-line utility, therefore the easiest way to start it is to bring up the Visual Studio .NET Command Prompt by selecting Start → Programs → Microsoft Visual Studio .NET → Visual Studio .NET Tools. This command prompt will have the path correctly set so that you can execute all .NET Framework SDK tools from any directory.
Starting SoapSuds without any parameters will give you detailed usage information. To generate a metadata DLL from a running server, you have to call SoapSuds with the -url parameter:
soapsuds -url:<URL> -oa:<OUTPUTFILE>.DLL -nowp
Note | You normally have to append ?wsdl to the URL your server registered for a SOA to allow SoapSuds to extract the metadata. |
To let SoapSuds extract the information from a compiled DLL, you use the -ia parameter:
soapsuds -ia:<assembly> -oa:<OUTPUTFILE>.DLL -nowp
When you run SoapSuds in its default configuration (without the -nowp parameter) by passing only a URL as an input parameter and telling it to generate an assembly, it will create what is called a wrapped proxy. The wrapped proxy can only be used on SOAP channels and will directly store the path to your server. Normally you do not want this.
Note | This behavior is useful when you want to access a third-party Web Service whose application URL you happen to have. |
I normally recommend using wrapped proxies only when you want to quickly test a SOAP remoting service. As an example, in the next section I show you how to implement a server without previously specifying any shared interfaces or base classes.
The server in this example will be implemented without any up-front definition of interfaces. You only need to create a simplistic SAO and register an HTTP channel to allow access to the metadata and the server-side object, as shown in Listing 3-22.
Listing 3-22: Server That Presents a SAO
using System; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels.Http; using System.Runtime.Remoting.Channels; namespace Server { class SomeRemoteObject: MarshalByRefObject { public void doSomething() { Console.WriteLine("SomeRemoteObject.doSomething() called"); } } class ServerStartup { static void Main(string[] args) { Console.WriteLine ("ServerStartup.Main(): Server started"); HttpChannel chnl = new HttpChannel(1234); ChannelServices.RegisterChannel(chnl); RemotingConfiguration.RegisterWellKnownServiceType( typeof(SomeRemoteObject), "SomeRemoteObject.soap", WellKnownObjectMode.SingleCall); // the server will keep running until keypress. Console.ReadLine(); } } }
To generate a wrapped proxy assembly, use the SoapSuds command line shown in Figure 3-31. The resulting meta.dll should be copied to the client directory, as you will have to reference it when building the client-side application.
Figure 3-31: SoapSuds command line used to generate a wrapped proxy
Assuming you now want to implement the client application, you first have to set a reference to the meta.dll in the project's References dialog box in VS .NET or employ the /r:meta.dll parameter to the command-line compiler. You can then use the Server namespace and directly instantiate a SomeRemoteObject using the new operator, as shown in Listing 3-23.
Listing 3-23: Wrapped Proxies Simplify the Client's Source Code
using System; using Server; namespace Client { class Client { static void Main(string[] args) { Console.WriteLine("Client.Main(): creating rem. reference"); SomeRemoteObject obj = new SomeRemoteObject(); Console.WriteLine("Client.Main(): calling doSomething()"); obj.doSomething(); Console.WriteLine("Client.Main(): done "); Console.ReadLine(); } } }
Even though this code looks intriguingly simply, I recommend against using a wrapped proxy for several reasons: the server's URL is hard coded, and you can only use an HTTP channel and not a TCP channel.
When you start this client, it will generate the output shown in Figure 3-32. Check the server's output in Figure 3-33 to see that doSomething() has really been called on the server-side object.
Figure 3-32: Client's output when using a wrapped proxy
Figure 3-33: The server's output shows that doSomething() has been called.
Starting SoapSuds with the parameter -gc instead of -oa:<assemblyname> will generate C# code in the current directory. You can use this code to manually compile a DLL or include it directly in your project.
Looking at the code in Listing 3-24 quickly reveals why you can use it without any further registration of channels or objects. (I strip the SoapType attribute, which would normally contain additional information on how to remotely call the object's methods.)
Listing 3-24: A SoapSuds-Generated Wrapped Proxy
using System; using System.Runtime.Remoting.Messaging; using System.Runtime.Remoting.Metadata; using System.Runtime.Remoting.Metadata.W3cXsd2001; namespace Server { public class SomeRemoteObject : System.Runtime.Remoting.Services.RemotingClientProxy { // Constructor public SomeRemoteObject() { base.ConfigureProxy(this.GetType(), "http://localhost:1234/SomeRemoteObject.soap"); } public Object RemotingReference { get{return(_tp);} } [SoapMethod(SoapAction="http://schemas.microsoft.com/clr/nsassem/ Server.SomeRemoteObject/Server#doSomething")] public void doSomething() { ((SomeRemoteObject) _tp).doSomething(); } } }
What this wrapped proxy does behind the scenes is provide a custom implementation/extension of RealProxy (which is the base for RemotingClientProxy) so that it can be used transparently. This architecture is shown in detail in Chapter 7.
Fortunately, SoapSuds allows the generation of nonwrapped proxy metadata as well. In this case, it will only generate empty class definitions, which can then be used by the underlying .NET Remoting TransparentProxy to generate the true method calls—no matter which channel you are using.
This approach also gives you the huge advantage of being able to use configuration files for channels, objects, and the corresponding URLs (more on this in the next chapter) so that you don't have to hard code this information. In the following example, you can use the same server as in the previous example, only changing the SoapSuds command and implementing the client in a different way.
As you want to generate a metadata-only assembly, you have to pass the -nowp parameter to SoapSuds to keep it from generating a wrapped proxy (see Figure 3-34).
Figure 3-34: SoapSuds command line for a metadata-only assembly
When using metadata-only output from SoapSuds, the client looks a lot different from the previous one. In fact, it closely resembles the examples I show you at the beginning of this chapter.
First you have to set a reference to the newly generated meta.dll from the current SoapSuds invocation and indicate that your client will be using this namespace. You can then proceed with the standard approach of creating and registering a channel and calling Activator.GetObject() to create a reference to the remote object. This is shown in Listing 3-25.
Listing 3-25: The Client with a Nonwrapped Proxy
using System; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels.Http; using System.Runtime.Remoting.Channels; using Server; namespace Client { class Client { static void Main(string[] args) { HttpChannel chnl = new HttpChannel(); ChannelServices.RegisterChannel(chnl); Console.WriteLine("Client.Main(): creating rem. reference"); SomeRemoteObject obj = (SomeRemoteObject) Activator.GetObject ( typeof(SomeRemoteObject), "http://localhost:1234/SomeRemoteObject.soap"); Console.WriteLine("Client.Main(): calling doSomething()"); obj.doSomething(); Console.WriteLine("Client.Main(): done "); Console.ReadLine(); } } }
When this client is started, both the client-side and the server-side output will be the same as in the previous example (see Figures 3-35 and 3-36).
Figure 3-35: The client's output when using a metadata-only assembly
Figure 3-36: The server's output is the same as in the previous example.
|
|