Calling a Remote Service

Let’s see what a client must do to call a method on a remote object. To begin, add a project reference to the assembly that contains the remote object by right-clicking References in Solution Explorer, choosing Add Reference from the shortcut menu, and traversing the network to locate the target assembly. The project reference lets the client application know about the types defined in the assembly.

Note

Even if your remotable object is hosted by IIS, when you reference the assembly from a remoting client, choose the Add Reference option. The Add Web Reference command on the same shortcut menu is reserved for Web services and, more importantly, starts a completely different linking procedure. (More on this in Chapter 13.)


Referencing a remote assembly is only the first step to being able to call any of its methods.

Configuring the Caller

The remote object must be registered with the local application before you can successfully use it. The .NET Remoting system must be aware that objects of certain types represent instances of remote objects. In this way, ad hoc code can be generated to obtain the necessary proxy.

You configure the client application either through a configuration file or programmatically by calling the RegisterWellKnownClientType method on the static RemotingConfiguration object, as shown here:

RemotingConfiguration.RegisterWellKnownClientType( 
    typeof(ServiceSalesProvider), 
    "http://www.contoso.com/SalesReport/ServiceSalesProvider.rem");

To register a well-known type, you pass in the type and object URI. If the object is not server-activated, and therefore is not a well-known object, you use the RegisterActivatedClientType instead, as follows:

RemotingConfiguration.RegisterActivatedClientType( 
    typeof(ServiceSalesProvider), 
    "http://www.contoso.com/SalesReport");

In this case, you don’t need to pass an explicit object URI. However, you still need to indicate the remote path for the target object. Because we are working with IIS as the host, the remote path must be the URL of the virtual directory. If a custom host is used, instead of the URL, you use a TCP address and the port, as shown here:

RemotingConfiguration.RegisterActivatedClientType(
    typeof(ServiceSalesProvider),
    "tcp://192.345.34.1:8082");

You can also direct the caller application to read setup information from a configuration file located in the same path as the executable. In this case, the convention is to give the file the same name as the executable plus a .config extension. You then pass the file name to the Configure method, as shown here:

RemotingConfiguration.Configure("MyClient.exe.config");

The following script shows the layout of a client configuration file:

<configuration>
  <system.runtime.remoting>
    <application name="MyClient" >
      <client>
        <wellknown 
          type="XmlNet.CS.ServiceSalesProvider, ServiceSalesProvider" 
          url="http://server/SalesReport/ServiceSalesProvider.rem" /> 
      </client>
      <channels>
        <channel ref="http" />
      </channels>         
    </application>
  </system.runtime.remoting>
</configuration> 

As you can see, the differences between the client and the server-side configuration files are minimal and are all related to the use of the <client> tag instead of <service>.

The server object publishes the list of supported channels, and based on that list, the client can decide which channel to use. Note that servers must register at least one channel. Clients are not required to indicate a channel. If a client doesn’t indicate a channel, the .NET Remoting system uses one of the default channels. On the other hand, a client that plans to use a given channel must first register with it. The application can run the channel registration procedure personally or let it run by default under the control of the RemotingConfiguration object.

Channels are registered on a per-AppDomain basis and must have unique names in that context. On physical machines, however, only one channel can listen to a given port. In other words, at any time you can’t have more than one channel registered to work on a given port on a given machine.

A client enabled to make remote calls on a remote object simply creates an instance of the desired class using the language-specific operator for instantiation—new in C# and Visual Basic. Alternatively, the client can use the System.Activator object—a managed counterpart of the VBScript CreateObject and GetObject functions.

Writing the Client Component

Figure 12-6 shows the initial user interface of the client application we’ll use to query for sales reports and bar charts. You select the year of interest and click one of the two buttons—Get Data to display sales information as a DataSet object, or Get Chart to display the information as a bar chart saved as a JPEG image. The form contains a DataGrid control (invisible by default) and a PictureBox control. Needless to say, the DataGrid object will display the contents of the DataSet object, whereas the PictureBox object will show the image.

Figure 12-6. The sample application in action, waiting for user input.


Accessing the Raw Data

Once the remote assembly has been referenced by the project and the remote type configured in the form’s Load event, you can write the client application and use the remote type as if it were a local type. The following code shows what happens when you click to get raw data:

private void ButtonGetData_Click(object sender, System.EventArgs e)
{
    // Get the year to process
    int theYear = Convert.ToInt32(Years.Text);

    // Instantiate the object and issue the call
    ServiceSalesProvider ssp = new ServiceSalesProvider();
    DataSet ds = ssp.GetSalesReport(theYear);

    // Turn on and fill the DataGrid control 
    // Also and turn off the picture box
    PictureContainer.Visible = false;
    Data.Visible = true;
    Data.DataSource = ds.Tables[0];

    // Update the UI
    Title.Text = "Sales Report for " + theYear.ToString();
}

The code in boldface demonstrates that, at this point, using the remote object is in no way different from using any other local, or system, class.

Figure 12-7 shows the sales information displayed in DataSet format.

Figure 12-7. The sample application displaying downloaded sales data in DataSet format.


Accessing BinHex-Encoded Images

Calling the GetSalesReportBarChart method is not all that different from calling the GetSalesReport method, but more work is needed to make the downloaded data usable. As mentioned, the GetSalesReportBarChart method draws a bar chart, converts it to JPEG, encodes the image as a BinHex string, and packs everything into an XML document. The content of the document is then returned as a string, as shown here:

ServiceSalesProvider ssp = new ServiceSalesProvider();
string encImage = ssp.GetSalesReportBarChart(theYear);

The next step is transforming the string into a bitmap and displaying it in the PictureBox control. The following procedure takes the BinHex image description and creates an equivalent Bitmap object. Because the string is an XML document, an XmlTextReader object is needed to parse the contents and then decode the BinHex data.

private Bitmap EncodedXmlToBitmap(string encImage)
{
    Bitmap bmp = null;

    // Parse the XML data using a string reader
    StringReader buf = new StringReader(encImage);
    XmlTextReader reader = new XmlTextReader(buf);
    reader.Read();
    reader.MoveToContent();

    // The root node of the document is <jpeg>
    if (reader.LocalName == "jpeg")
    {
        // Get the size of the BinHex data
        int encodedSize = Convert.ToInt32(reader["Size"].ToString());

        // Read and decode the BinHex data
        byte[] img = new byte[encodedSize];
        reader.ReadBinHex(img, 0, encodedSize);

        // Transform the just read bytes into an Image object
        MemoryStream ms = new MemoryStream();
        ms.Write(img, 0, img.Length);
        bmp = new Bitmap(ms);
        ms.Close();

        reader.Close();
        return bmp;
    }
}

You decode the image data using the ReadBinHex method on the XmlTextReader class. Next you copy the resultant array of bytes into a temporary memory stream. This step is necessary because a Bitmap object can’t be created directly from an array of bytes.

Finally, the returned Bitmap object is bound to the PictureBox control in the form, as shown in the following code:

PictureContainer.SizeMode = PictureBoxSizeMode.StretchImage;
PictureContainer.Image = bmp;

Figure 12-8 shows the results.

Figure 12-8. The sample application displaying an encoded bar chart.


The client can easily create a local copy of the JPEG file. The following code snippet shows how to proceed:

// img is the array of bytes obtained from ReadBinHex
FileStream fs = new FileStream(fileName, FileMode.Create);
BinaryWriter writer = new BinaryWriter(fs);
writer.Write(img); 
writer.Close();

Tip

When converting a Bitmap object to JPEG, you can control the compression ratio to obtain a better image. However, JPEG is not a compression scheme designed for text and simple figures like bar charts. In fact, JPEG was originally designed to effectively compress photographic images. To ensure a better image, you might want to use the GIF format or control the compression ratio of the final JPEG image. You can do that by using one of the overloads of the Bitmap object’s Save method.


Using the System.Activator Class

A remoting client can obtain a proxy to make calls to a remote object in two ways: by using the new operator or by using methods of the System.Activator class. The Activator class provides two methods—CreateInstance and GetObject. Clients of well-known objects use GetObject, whereas clients of client-activated objects use CreateInstance.

GetObject returns a proxy for the well-known type served at the specified URL location, as shown in the following code. GetObject is a wrapper placed around the global RemotingServices.Connect method. The proxy is built on the client from the remote object metadata and exposed to the client application as the original type.

ServiceSalesProvider ssp;
ssp = (ServiceSalesProvider) Activator.GetObject(
    typeof(ServiceSalesProvider), 
    "http://www.contoso.com/SalesReport");

From this relatively simple explanation, it should be clear that .NET Remoting is no less quirky than DCOM, but unlike DCOM, the .NET Framework successfully hides a great wealth of low-level details.

CreateInstance differs from GetObject in that it actually creates a new remote instance of the object, as shown here:

// Set the URL of the remote object
object[1] attribs;
attribs[0] = new Activation.UrlAttribute(url);

// Create the instance of the object
ServiceSalesProvider ssp;
ssp = (ServiceSalesProvider) Activator.CreateInstance(
    typeof(ServiceSalesProvider), null, attribs);

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.224.57.79