Metadata Dependency Issues

Metadata Dependency Issues

Throughout this chapter, we’ve taken the easy approach to metadata dependency issues in that both the JobClient and JobServer applications depend on each other’s metadata. Specifically, the JobClient needs the JobServerImpl class’s metadata, along with its supporting types. Likewise, because the client subscribes to the JobEvent, the JobServer needs the metadata for the Form1 class defined in the JobClient application. In essence, the JobClient acts as a server by receiving the callback from the JobEvent. This type of architecture is sometimes known as server-to-server.

Providing the required metadata in this fashion is fine for the sample application, but in the real world, you might not want to provide either the client’s metadata to the server or the server’s metadata to the client. In this section, we’ll look at several strategies you can use in your projects to handle these scenarios.

Removing the JobServer’s Dependency on the JobClient’s Metadata

First, it’s important that you understand why the JobServer application depends on the JobClient’s metadata. The following line of code in the Form1 constructor causes this dependency:

m_IJobServer.JobEvent += new JobEventHandler(this.MyJobEventHandler);

The only reason the JobServer application depends on the JobClient application’s metadata is that the Form1 class defined in the JobClient application subscribes to JobServerImpl.JobEvent. Obviously, one way to break this dependency is to avoid subscribing to JobEvent in the first place by utilizing a polling method. We did that in the “Exposing the JobServerImpl Class as a Web Service” section when we modified the JobClient to interact with the Web Service. Let’s assume that this solution is unacceptable.

In this case, we need a type that acts as a link between the Form1.MyEventHandler and the JobServerImpl.JobEvent event. The following code defines a class that does just that:

public class JobEventRepeater : MarshalByRefObject
{
    //
    // Event to which clients subscribe
    public event JobEventHandler JobEvent;

    //
    // Handler method for the IJobServer.JobEvent
    public void Handler(object sender, JobEventArgs args)
    {
        if (JobEvent != null)
        {
            JobEvent(sender, args);
        }
    }

    //
    // Prevent lifetime services from destroying our instance.
    public override object InitializeLifetimeService() 
    {
        return null;
    }
}

The JobEventRepeater class acts as a repeater for the JobEvent. The class provides a JobEvent member and a RepeatEventHandler method that fires the JobEvent. To use this class, the client code creates a new instance of JobEvent­Repeater and subscribes to its JobEvent event instead of to the JobServerImpl.JobEvent event. The client then subscribes the JobEventRepeater instance to the JobServerImpl.JobEvent event so that when the server fires its JobEvent, it invokes the JobEventRepeater.RepeatEventHandler method.

Assuming you add a member of type JobEventRepeater named m_JobEventRepeater to the Form1 class, you can modify the Form1 constructor to make use of the JobEventRepeater class by using the following code:

m_JobEventRepeater = new JobEventRepeater();
m_JobEventRepeater.JobEvent += 
        new JobEventHandler(this.MyJobEventHandler);
m_IJobServer.JobEvent += 
        new JobEventHandler(m_JobEventRepeater.Handler);

Figure 3-8 shows the relationship between the Form1 instance, the JobEvent­Repeater instance, and the JobServerImpl instance.

figure 3-8 the jobeventrepeater instance acts as a link between the form1 event handler and jobserverimpl.jobevent.

Figure 3-8. The JobEventRepeater instance acts as a link between the Form1 event handler and JobServerImpl.JobEvent.

Now when the server fires JobServerImpl.JobEvent it invokes the handler for the JobEventRepeater instance. The JobEventRepeater instance, in turn, fires its JobEvent, which repeats (or forwards) the callback to the Form1.MyJobEvent­Handler method.

Of course, you’ll also need to change the way that the Form1 unsubscribes from the server’s JobEvent. You can do so by replacing the original line of code in the Form1.OnClosed method with the following code:

// Make sure we unsubscribe from the JobEvent.
m_IJobServer.JobEvent -= new JobEventHandler(m_JobEventRepeater.Handler);
m_JobEventRepeater.JobEvent -= new JobEventHandler(this.MyJobEventHandler);

Developing a Stand-In Class to Publish in Place of JobServerImpl Metadata

There might be times when you’re unable or unwilling to provide a client application with the remote object’s implementation. In such cases, you can create what’s known as a stand-in class, which defines the remote object’s type but contains no implementation.

At present, the sample client application depends on the JobServerImpl type’s metadata because it creates a new instance of JobServerImpl in the GetIJobServer method of Form1. The client application references the JobServerLib assembly, which not only contains the definition of the JobServerImpl and IJobServer types but also contains the implementation of the JobServerImpl type.

The following code listing defines such a stand-in class for JobServerImpl:

public class JobServerImpl : MarshalByRefObject, IJobServer
{
    public event JobEventHandler JobEvent;

    public JobServerImpl()
    { throw new System.NotImplementedException(); }
    
    private void NotifyClients(JobEventArgs args)
    { throw new System.NotImplementedException(); }
    
    public void CreateJob( string sDescription )
    { throw new System.NotImplementedException(); }
    
    public void UpdateJobState( int nJobID, string sUser, 
                                string sStatus )
    { throw new System.NotImplementedException(); }
    
    public ArrayList GetJobs()
    { throw new System.NotImplementedException(); }
}

The type name of the stand-in class and its assembly name must be the same as the actual implementation’s type name and assembly name. You can use an assembly containing the stand-in class on the client side, while the server references the assembly containing the actual implementation. This way, the client application has the metadata necessary for the .NET Remoting infrastructure to instantiate a proxy to the remote object but doesn’t have the actual implementation of the remote object. Figure 3-9 shows the dependency relationships between the applications and the JobServerLib assemblies.

figure 3-9 jobserverlib and stand-in assembly dependencies

Figure 3-9. JobServerLib and stand-in assembly dependencies

Remoting the IJobServer Interface

Another way to remove the client’s dependency on the remote object’s implementation is by remoting an interface. The client interacts with the remote object through the interface type definition rather than through the actual remote object’s class type definition. To remote an interface, place the interface definition along with any supporting types in an assembly that will be published to the client. You place the remote object’s implementation of the interface in a separate assembly that you’ll never publish to the client. Let’s demonstrate this with the sample application by remoting the IJobServer interface.

Because the JobServerImpl class implements the IJobServer interface, you can provide the client with an assembly that contains only the IJobServer interface’s metadata. First, you need to move the definition of the IJobServer interface as well as the JobInfo, JobEventArgs, JobEvent, and JobEventHandler type definitions and supporting data structures from the JobServerLib assembly into another assembly that we’ll name JobLib. The JobServerLib assembly will then contain only the JobServerImpl class’s metadata and therefore will be dependent on the new JobLib assembly. Figure 3-10 depicts the new dependencies.

figure 3-10 joblib assembly dependencies

Figure 3-10. JobLib assembly dependencies

Now the JobClient application depends only on the JobLib assembly, which contains the IJobServer interface and supporting data types but doesn’t contain the JobServerImpl definition. The JobClient application doesn’t need the metadata for the JobServerImpl class because it interacts with this metadata through the IJobServer interface. Because you can’t create an instance of an interface, remoting interfaces in this manner requires you to use the Activator.GetObject method. This method allows you to specify the type of an object to be activated at a specific URL endpoint.

The following code snippet uses the Activator.GetObject method to obtain the IJobInterface from the endpoint specified in the configuration file:

WellKnownClientTypeEntry[] ClientEntries =
        RemotingConfiguration.GetRegisteredWellKnownClientTypes();

return (IJobServer)Activator.GetObject( typeof(IJobServer), 
                                        ClientEntries[0].ObjectUrl );

This code assumes the presence of the following entry in the JobClient.exe.config configuration file:

<wellknown type="JobServerLib.IJobServer, JobLib"
  url="http://localhost:4000/JobURI" />