Sunday, November 7, 2004

It Works!

I've won my fight with .NET Remoting! Thank goodness for Ingo Rammer and Google, as those two resources provided the answers to all of the "gotchas" that bit me on this. My final solution involves a shared base class assembly, which defines the abstract base class for each of the objects exposed by my web service. Next, there is the server implementation, which is composed of a server activated class factory serving up the client activated remoted objects. And finally, there is the client piece, which first gets an instance of the class factory, and uses that reference to get instances of the client activated objects.


First, let's talk about the shared assembly. This is the easiest one. I needed to create, at a minimum, two abstract classes in my shared assembly. The first abstract class describes my class factory.

public abstract class ClassFactoryBase : MarshalByRefObject
{
   public abstract GetCAO();
}

So far so good. Now comes the first gotcha I experienced. My client activated object is exposing the functionality of an existing COM object on the server. So my first idea was to simply implement interface exposed by this COM object. This was a bad idea, and caused remoting to barf big time. So instead, my base class for the client activated implements the interface, but doesn't put it in the declaration.

public abstract class CAOBase : MarshalByRefObject
{
   public abstract bool COMObjectMethod();
}

All of this code went into a file I named Shared.cs, and I compiled it to an assembly, Shared.dll.
Next, I needed to implement the factory and the client activated object.

public class ClassFactory : ClassFactoryBase
{
   // Generic constructor required for Remoting
   public ClassFactory()
   {
   }

   public override GetCAO()
   {
      return new CAO();
   }
}

public class CAO : CAOBase
{
   internal Interop.ComObject comroot;

   // Generic constructor required for Remoting
   public CAO()
   {
      comroot = new Interop.ComObject();
   }

   public override COMObjectMethod()
   {
      return comroot.COMObjectMethod()
   }
}

Here, the ClassFactory creates a new instance of the CAO object on demand and passes it back. The CAO object creates an instance of the COM object to be exposed. The methods of the CAO object then pass through the COM object layer. These classes I put in a file called server.cs and compiled to an assembly named Server.dll

I wanted to use IIS as the host for my remote classes, so I needed to do a couple of things to enable this. First, I created a virtual directory in the IIS admin. In that virtual directory, I created a bin directory. I copied my Shared.dll and Server.dll into the bin directory, along with the interop.ComObject.dll. Finally, I created a web.config file and placed this in the root of the virtual directory. The web.config looked like this:

<configuration>
   <system.runtime.remoting>
      <application>
         <service>
            <wellknown
               mode="SingleCall"
               type="ClassFactory,Server"
               objectUri="ClassFactoryURI.soap" />
            </service>
         </application>
      </system.runtime.remoting>
   <system.web>
</configuration>

This was another stumbling block for me. Originally, I had excluded the ".soap" extension from my objectURI. I had read something that indicated that it was unnecessary when hosting your object in IIS. This was dead wrong. Unless your object URI ends with ".soap" or ".rem", IIS will not pass the method calls on to the remoted object. This took me a while to figure out, so don't make the same mistake. Fortunately, everything else was cake from this point, as IIS takes care of all of the nastiness of load balancing, connection pooling, and security for connecting to your remote object.

Finally, it's time to implement the client. Here is my quick and dirty client code:

public class Client
{
   public static void Main( string[] args )
   {
      ClassFactoryBase factory =
         (ClassFactoryBase)Activator.GetObject(
            typeof(ClassFactoryBase),
            "http://remotinghostserver/VirtualDirectory/ClassFactoryURI.soap" );
      CAOBase cao = factory.GetCAO();
      if ( cao.COMObjectMethod() )
      {
         MessageBox.Show( "Success!" )
      }
   }
}

This code goes in Client.cs, compiles to Client.exe, and is deployed with only the Client.exe and Shared.dll. I didn't even need a config file! This is because I'm using the Activator.GetObject method to create an instance of my object. Why am I doing this? Well, another option would be to use soapsuds.exe to generate the metadata for my remoted object and reference this when compiling my client. When done this way, the client can simply use the new keyword when appropriate remoting configuration information is in the app.config file. Very convenient for the code. Unfortunately, soapsuds.exe is broken. For some reason, when you host your remoting classes in IIS, soapsuds makes mistakes when attempting to generate the metadata. The result is that you will successfully expose your class factory, but will get a type case exception when attempting to get an instance of your CAO. This is seriously bad news for the solution I needed to make. Another option would be to use share interfaces (as opposed to share base abstract classes). When using share interfaces, you can again configure the app.config file to allow for the new keyword to be used. However, there is another gotcha here. When using shared interfaces, the CAOs instances can not be passed as arguments to other methods on other remote objects. For our solution, several of the CAOs need to interact with each other. Using abstract base classes allows us to pass these references as arguments to our other remote objects. The only drawback here is that we are forced to use the Activator.GetObject() call to instantiate our remote object rather than the new keyword. It's a small price to pay I think.


So what do I do from here? The next thing I need to do is verify that the object lifetime is being managed properly. I don't want to strand a bunch of instances of my remote objects on the server. So my next effort will be to investigate CAO object lifetime leases. Once I get there and interesting information to pass on, I'll be sure to post it here.

No comments:

Post a Comment