COM+ Web Services: The Check-Box Route to XML Web Services

 

John Noss and Jonathan Hawkins
Microsoft Corporation

November 2001

Summary: COM+ Web Services adds features that integrate with Microsoft .NET Remoting to provide check box activation of XML Web service publication via SOAP for COM+ components. This document includes several examples and walkthroughs covering basic interoperability, configuration, and deployment of managed and unmanaged COM+ components published as XML Web services on Microsoft Windows Server 2003 and Microsoft Windows XP Professional. (26 printed pages)

Contents

Introduction
Simple Well-Known Object (WKO) Examples
Simple Client-Activated Object (CAO) Examples
Transactional Component Examples
A Beginning, Not an Ending...

Introduction

COM+ Web Services adds features that integrate with Microsoft .NET Remoting to provide check box activation of XML Web service publication via SOAP for COM+ components. This document includes several examples and walkthroughs covering basic interoperability, configuration, and deployment of managed and unmanaged COM+ components published as XML Web services on Microsoft® Windows® Server 2003 and Microsoft Windows XP Professional. Examples of new features available for clients running Windows XP to access XML Web services on remote servers are also provided.

The goal of these features is to facilitate and simplify the migration process as developers start to use .NET Remoting and managed code to complement existing unmanaged COM+ server and client code. During the beta phases of the .NET Framework, there were frequent user questions about how to configure .NET Remoting to do simple cross-machine activations. The COM+ Web Services solution is to automatically configure both server (Microsoft Windows Server 2003) and client (Microsoft Windows XP Professional) machines to use .NET Remoting to provide SOAP as an alternative to DCOM.

Two of the biggest software releases of this year will be Microsoft Windows XP and the Microsoft .NET Framework. The goal of each is to simplify and extend the capabilities of software developers, so it is a natural step to leverage the two products and provide an integrated, easy-to-use solution that incorporates the strengths of both products. COM+ Web Services provides a simple way to publish COM+ components as XML Web services and also provides new integrated capabilities to access XML Web services from a client computer. This ease of use can be seen from the following Microsoft Visual Basic Scripting Edition (VBScript) example, which determines the current temperature in Fairbanks, Alaska. This sample runs on Windows XP (with the .NET Framework installed) or on Windows Server 2003 (for which the .NET Framework Release Candidate or later build is required):

set SoapObj = GetObject
   ("soap:wsdl=http://www.xmethods.net/sd/TemperatureService.wsdl")
WScript.Echo "Fairbanks Temperature = " & SoapObj.getTemp("99707")

In the preceding example, the server is an Apache SOAP Server running on Linux, although it could be any SOAP version 1.1 server with a standard Web Services Description Language (WSDL) description.

Note   If you get a "server not found" error, you need to manually configure your firewall settings in Internet Options in Control Panel.

One of the advantages of using SOAP as the communications protocol between machines is that it increases the variety of computers that can interoperate. .NET Remoting has the following two basic modes of operation:

  • Well-known object (WKO)   WKO is the most common XML Web Services model supported by SOAP version 1.1. It permits interoperability with other computers running SOAP version 1.1–compliant stacks. Servers and clients can range from non-Windows servers running Apache SOAP to Pocket PCs running pocketSOAP, in addition to Windows-based servers and clients. The only requirement is that a server must have a WSDL version 1.1–compatible description available so that an appropriate proxy can be generated. This proxy is generated at run time, with no user intervention on first use of a WSDL moniker.
  • Client-activated object (CAO)   CAO provides a richer development environment, including stateful, persistent connections more like the DCOM model than the typical XML Web services model, but it requires a version of the .NET Framework on both ends.

With COM+ Web Services, the WKO and the CAO activation models are both available, and all server applications are published to provide both WKO and CAO endpoints. By using this combination of activation models, XML Web services, and .NET Remoting, a developer can easily mix and match managed and unmanaged clients and servers. Examples of supported scenarios for each activation model are shown in the following tables.

Table 1. Supported Scenarios for WKO Model

WKO Client WKO Server
Visual Basic 6.0 or unmanaged C++ Visual Basic 6.0 or unmanaged C++
Visual Basic 6.0 or unmanaged C++ Visual Basic .NET or C#
Visual Basic 6.0 or unmanaged C++ SOAP v1.1 (described in WSDL)
Visual Basic 6.0 or unmanaged C++ Microsoft SOAP (ATL Server, SOAP TK)
C# or Visual Basic .NET SOAP v1.1 (described in WSDL)
C# or Visual Basic .NET Visual Basic 6.0 or unmanaged C++
C# or Visual Basic .NET Visual Basic .NET or C#
C# or Visual Basic .NET Microsoft SOAP (ATL Server, SOAP TK)
Microsoft SOAP Toolkit V2.0 Visual Basic 6.0 or unmanaged C++
Microsoft SOAP Toolkit V2.0 C# or Visual Basic .NET
SOAP v1.1 Visual Basic 6.0 or unmanaged C++
SOAP v1.1 C# or Visual Basic .NET

Table 2. Supported Scenarios for CAO Model

CAO Client CAO Server
C# or Visual Basic .NET (early bound) Visual Basic 6.0 or unmanaged C++
Visual Basic 6.0 or unmanaged C++ Visual Basic 6.0 or unmanaged C++
Visual Basic 6.0 or unmanaged C++ C# or Visual Basic .NET
C# or Visual Basic .NET C# or Visual Basic .NET

The target audience for this new COM+ Web service includes the following users:

  1. Current COM+ customers who have Microsoft® Visual Basic® 6.0 or unmanaged Microsoft Visual C++® COM+ applications that need to go through a firewall for some activations. (Use of SOAP does not preclude accessing the same component on a server via DCOM—the choice of protocol is left to the client machine.) For these customers, using client proxy export and the CAO model should require no changes in either client or server applications in order to use SOAP instead of DCOM. You just need to enable SOAP on the server application, export it as a client proxy, and install the proxy on the Windows XP computers that you wish to use as SOAP clients.
  2. Companies moving entirely to managed code on Windows XP and Windows Server 2003. COM+ Web Services facilitates setting up remoting endpoints on both ends of the connection.
  3. Developers who need to mix and match various services within the preceding two scenarios and who are writing either managed server components or managed client applications with unmanaged server components. In the latter case, developers can use COM+ Web Services to maximize the use of older unmanaged components before replacing them with managed code.

Simple Well-Known Object (WKO) Examples

In addition to providing SOAP support to Linux and Apache, using COM+ Web services with other Microsoft products, such as an ATL Server Web service, is also very simple. Just use Microsoft Visual Studio® .NET to generate, compile, and deploy a default ATL Web service on a server. The client code to access it would be as follows (replacing MyServer with the name of the Web server hosting the ATL Server application and replacing JALTServer with the name of your ATL Server DLL):

mon="soap:wsdl=http://MyServer/JALTServer/JALTServer.dll?
   Handler=GenJALTServerWSDL"
set c = GetObject(mon)
WScript.Echo c.HelloWorld("COM+ Web Services") 

The preceding example provides a simple illustration of one of the new SOAP monikers included with Microsoft Windows XP and the Microsoft Windows Server 2003 family. To use the moniker to consume an ASP.NET Web service, the Web service must have the System.Web.Services.Protocols.SoapRpcService class attribute.

Data Publication

What if you want to provide data instead of consuming it? You can simply select a check box and enter a value for an IIS Virtual Root name. To create a complete COM+ Web Service, perform the following steps:

With Visual Basic 6.0, you create a simple Microsoft ActiveX® DLL and type in the following code:

Function Add(ByVal Value1 As Double, ByVal Value2 As Double) As Double
 Add = Value1 + Value2
End Function

On the General tab of the Visual Basic project Properties page, set Unattended Execution and Retained in Memory, and on the Component tab, select Remote Server Files. Build this DLL using the Visual Basic development environment.

After creating the Visual Basic application, it needs to be registered as a COM+ application. Start the Component Services administrative tool and create a COM+ application on Windows XP. (In this example, the application is named VB6Soap.) Import the DLL you created as a component. Then navigate to the Activation tab of the COM+ application Properties page, select Uses SOAP, enter a SOAP Vroot such as VB6Soap, and click OK, as shown in Figure 1.

Figure 1. The VB6Soap COM+ application Properties page

The application is now published as an XML Web service and can be activated using SOAP. Using Internet Explorer, you can navigate to https://localhost/VB6Soap/default.aspx, where you will find a hyperlink on the aspx page to the WSDL produced by your component. The following VBScript will activate your component:

set c = GetObject
   ("soap:wsdl=https://localhost/VB6Soap/VB6Soap.Calc.soap?WSDL")
for i = 1 to 10
 WScript.Echo i & " " & c.Add(i,i) & " " & Time
next 

If you substitute your server name for localhost in the above script, it will work equally well from a remote client machine. The page reference (in this example VB6Soap.Calc.soap) is the component ProgID followed by the .soap suffix.

To access the same endpoint through the SOAP Toolkit that ships as part of Windows XP Professional and does not use .NET Remoting, run the following VBScript:

set c = CreateObject("MSSOAP.SOAPClient")
c.mssoapinit("https://localhost/VB6Soap/VB6Soap.Calc.soap?WSDL")
for i = 1 to 10
 WScript.Echo i & " " & c.Add(i,i) & " " & Time
next 

For an even simpler approach to SOAP publication on a server, you can use Microsoft C#™ or Visual Basic .NET and inherit from ServicedComponent. Following is a managed code sample for a simple managed component (These ServicedComponent samples will work on .NET Server Beta 3 or later build, and with the .NET Framework Release Candidate or later build. The SoapVRoot functionality will be brought to Windows XP Pro in a future Service Pack):

using System;
using System.Reflection;
using System.Runtime.InteropServices;
using System.EnterpriseServices;

[assembly: ApplicationName("CSSoap")]
[assembly: ApplicationActivation(ActivationOption.Server, 
   SoapVRoot="CSSoap")]
[assembly: AssemblyKeyFile("CSSoap.snk")]
namespace CSSoap
{
  public interface ICalc
  {
   double Add (double Value1, double Value2);
  }

  [ClassInterface(ClassInterfaceType.AutoDual)]
  [TransactionAttribute(TransactionOption.None)]
  public class Calc : ServicedComponent, ICalc
  {
   public double Add (double Value1, double Value2);
   {
      return (Value1 + Value2);
   } 
  }
}

The interesting thing about the preceding example is the ApplicationActivation attribute:

[assembly: ApplicationActivation(ActivationOption.Server, 
   SoapVRoot="CSSoap")]

After building this C# component, installing it in the global assembly cache, and running regsvcs.exe to register it as a COM+ application, it is published as an IIS virtual root and as a SOAP endpoint. For remoting to work successfully with this ServicedComponent, you also need to put this compiled assembly in the global assembly cache (GAC) using either gacutil.exe or the .NET Framework user interface. To access this SOAP endpoint via WSDL, use the following VBScript:

set c = GetObject
   ("soap:wsdl=https://localhost/CSSoap/CSSoap.Calc.soap?WSDL")
for i = 1 to 10
 WScript.Echo i & " " & c.Add(i,i) & " " & Time
next 

As a simple example of SOAP interoperability, the SOAP Toolkit ships with Windows XP Professional and the following VBScript will access the COM+ SOAP endpoint even if the .NET Framework is not installed on the client computer running Windows XP:

set c = CreateObject("MSSOAP.SOAPClient")
c.mssoapinit("https://localhost/CSSoap/CSSoap.Calc.soap?WSDL")
for i = 1 to 10
 WScript.Echo i & " " & c.Add(i,i) & " " & Time
next 

For brevity, the preceding examples have all used VBScript to access these Web services. The samples could have been written in Visual C++, Visual Basic 6.0, Visual Basic .NET, or C#, using the soap WSDL moniker. For example, Visual Basic .NET will also access the same object using compiled managed code, as shown in the following example:

Imports System
Imports System.Runtime.InteropServices
Module WKOClient
 Sub Main()
    Dim WSDLMoniker = 
      "soap:wsdl=https://localhost/CSSoap/CSSoap.Calc.soap?WSDL"
      Dim obj as Object
   obj = Marshal.BindToMoniker(WSDLMoniker)
   Console.WriteLine(obj.Add(1,2))
 End Sub
End Module

The point in using VBScript is to show that either managed or unmanaged clients can access COM+ components published as COM+ Web Services. In large organizations or applications, it is difficult to switch over all parts at once. COM+ Web Services permits portions of applications to be converted to managed code without requiring an immediate full-scale rewrite of your existing applications.

Simple Client-Activated Object (CAO) Examples

COM+ Web Services publication on the server publishes as both WKO and CAO for every component, so no additional server configuration is necessary. The only step needed on the server is to export the COM+ application as a proxy application after selecting the Uses SOAP check box (on the Activation tab of the COM+ application Properties page) and entering a value in the SOAP VRoot text box. The following illustration shows the steps necessary to export a proxy application:

  1. Right-click the VB6Soap COM+ application in the Component Services administrative tool, and choose Export, as shown in Figure 2.

    Figure 2. The Component Services administration tool

  2. In the COM+ Application Export Wizard shown in Figure 3, enter the location and name for the proxy .msi file.

    Figure 3. The COM+ Application Export Wizard

  3. On a separate client machine, install the proxy .msi file as a prebuilt COM+ application.

    When installed on a client machine, the proxy will be properly configured to access the correct server and virtual root via SOAP. For client activation, instead of using the WSDL moniker, normal unmanaged COM+ activation can be used (CoCreateInstance, CreateObject, and so on). After an application proxy for the Visual Basic calculator example given above is created on a server and is installed on a separate client computer, the following VBScript will access the server via SOAP:

    set c = CreateObject("VB6Soap.Calc")
    for i = 1 to 10
     WScript.Echo i & " " & c.Add(i,i) & " " & Time
    next 
    

    The same VBScript code would access a server application using DCOM if the proxy application did not have COM+ Web Services enabled.

Transactional Component Examples

Simple calculators are pretty far from heavyweight business applications, so the kind of application that is well suited for COM+ transactional components, with object pooling, will now be considered.

The easiest component to manage and configure is a managed code component derived from ServicedComponent, as illustrated in the following C# example (These ServicedComponent samples will work on .NET Server Beta 3 or later build, and with the .NET Framework Release Candidate or later build. The SoapVRoot functionality will be brought to Windows XP Pro in a future Service Pack):

using System;
using System.Reflection;
using System.Runtime.InteropServices;
using System.EnterpriseServices;
using System.Data;
using System.Data.SqlClient;

[assembly: ApplicationName("SCTrans")]
[assembly: ApplicationActivation(ActivationOption.Server, 
   SoapVRoot="SCTrans")]
[assembly: AssemblyKeyFile("SCTrans.snk")]
namespace SCTrans
{
  public interface ISCTrans
  {
   string CountUp (string Key);
  }

  [ObjectPooling(MinPoolSize=0, MaxPoolSize=25)]
  [JustInTimeActivation(true)]
  [ClassInterface(ClassInterfaceType.AutoDual)]
  [TransactionAttribute(TransactionOption.RequiresNew)]
  public class SCTransSQLNC : ServicedComponent, ISCTrans
  {
   [AutoComplete]
   public string CountUp (string Key)
   {
      _command = new SqlCommand("", _connection);
      _command.CommandType = CommandType.Text;
      _command.Connection.Open();
     _command.CommandText = "UPDATE CallCount WITH (ROWLOCK) SET 
      CallCount = CallCount + 1 WHERE Machine='" + Key + "'";
     _command.ExecuteNonQuery();
      _command.Connection.Close();
     _numcalls++;
     return (_numcalls + " NC " + _guid);
   } 

   protected override bool CanBePooled()
   {
     return true; 
   }
   private int _numcalls = 0;
   private string _guid = Guid.NewGuid().ToString();
   private SqlConnection _connection = 
   new SqlConnection("user id=MyUser;password=My!Password;
   database=SoapTest;server=MyServer");
   private SqlCommand _command; 
   
  }
}

To build and run this C# component, after editing the connection values to connect to your Microsoft SQL Server™ database, you need to use sn.exe to generate an Sctrans.snk strong name key file and then compile it with references to the assemblies in the using statements. The assembly should be put in the GAC either by using gacutil.exe (if you are using the SDK) or through the .NET Framework user interface if you are deploying on a server. Then regsvcs.exe should be run to register the managed component with COM+. Regsvcs.exe will use the following attribute to publish the component as a SOAP endpoint on the server and as a server (out-of-process) activation:

[assembly: ApplicationActivation(ActivationOption.Server, 
   SoapVRoot="CSSoapSQL")]

This component requires a new transaction on each method call, has a single autocomplete method, and is configured to be pooled. Using both managed and unmanaged COM+ components, object pooling and transactions work as expected over SOAP. For example, if the following VBScript is used to access the following ServicedComponent over SOAP:

mon = "soap:wsdl=http://jnoss3/sctrans/SCTrans.SCTransSQLNC.soap?WSDL"
WScript.Echo(mon)
for i = 1 to 2
 set c = GetObject(mon)
 for j = 1 to 10
  WScript.Echo i & " " & j & " " & c.CountUp("SCWKONC") 
 next
next

the following output will appear:

C:\moniker>actscwko
Microsoft (R) Windows Script Host Version 5.6
Copyright (C) Microsoft Corporation 1996-2001. All rights reserved.

soap:wsdl=http://jnoss3/sctrans/SCTrans.SCTransSQLNC.soap?WSDL
1 1 486 NC 6e41f32f-74be-45f0-94c0-989e7e1c5672 
1 2 487 NC 6e41f32f-74be-45f0-94c0-989e7e1c5672 
1 3 488 NC 6e41f32f-74be-45f0-94c0-989e7e1c5672 
1 4 489 NC 6e41f32f-74be-45f0-94c0-989e7e1c5672 
1 5 490 NC 6e41f32f-74be-45f0-94c0-989e7e1c5672 
1 6  8 NC af26b53b-4a1f-48c8-8880-518c2b55a7ce 
1 7  9 NC af26b53b-4a1f-48c8-8880-518c2b55a7ce 
1 8 10 NC af26b53b-4a1f-48c8-8880-518c2b55a7ce 
1 9 494 NC 6e41f32f-74be-45f0-94c0-989e7e1c5672 
1 10 495 NC 6e41f32f-74be-45f0-94c0-989e7e1c5672 
2 1 13 NC af26b53b-4a1f-48c8-8880-518c2b55a7ce 
2 2 14 NC af26b53b-4a1f-48c8-8880-518c2b55a7ce 
2 3 15 NC af26b53b-4a1f-48c8-8880-518c2b55a7ce 
2 4 499 NC 6e41f32f-74be-45f0-94c0-989e7e1c5672 
2 5 17 NC af26b53b-4a1f-48c8-8880-518c2b55a7ce 
2 6 501 NC 6e41f32f-74be-45f0-94c0-989e7e1c5672 
2 7 502 NC 6e41f32f-74be-45f0-94c0-989e7e1c5672 
2 8 19 NC af26b53b-4a1f-48c8-8880-518c2b55a7ce 
2 9 20 NC af26b53b-4a1f-48c8-8880-518c2b55a7ce 
2 10 21 NC af26b53b-4a1f-48c8-8880-518c2b55a7ce 

This is what would be expected for pooled components—objects are pulled from the pool and reused. The behavior for pooled components using client activation is the same.

Object pooling and transactions also work as expected for unmanaged components (although Visual Basic 6.0 components do not support object pooling). The pooling and transaction attributes need be set through the COM+ administrative tool for most unmanaged applications.

Passing References

One of the key differences between the WKO and the CAO models is the ability to pass references to stateful objects. Following is a C# ServicedComponent sample that shows the basics of this operation (These ServicedComponent samples will work on .NET Server Beta 3 or later build, and with the .NET Framework Release Candidate or later build. The SoapVRoot functionality will be brought to Windows XP Pro in a future Service Pack):

using System;
using System.Reflection;
using System.EnterpriseServices;
using System.Runtime.InteropServices;

[assembly: ApplicationName("RefPass")]
[assembly: ApplicationActivation(ActivationOption.Server, 
   SoapVRoot="RefPass")]
[assembly: AssemblyKeyFile("RefPass.snk")]
namespace RefPass
{
  public interface IParent
  {
    string SetRef(object inKid);
    object GetRef();
    string CountUp(object obj); 
  }

  public interface IChild
  {
    string GetValue ();
    string CountUp();
    void SetName(string key);
  }

  [ClassInterface(ClassInterfaceType.AutoDual)]
  public class Parent: ServicedComponent, IParent
  {
    protected Child _kid = null;

    public string SetRef(object inKid)
    {
      _kid = (Child)inKid;
      return _kid.GetValue();
    }

    public object GetRef()
    {
      return (object)_kid;
    }

    public string CountUp(object obj)
    {
      Child kid = (Child)obj;
      if (kid == null) return _kid.CountUp();
      else return kid.CountUp();
    }
 }

  [ClassInterface(ClassInterfaceType.AutoDual)]
  public class Child : ServicedComponent, IChild
  {
    private int _counter = 0;
    private string _name = "none";
    public string CountUp() { _counter++; return GetValue(); }
    public string GetValue() { return (_name + " " 
   +_counter.ToString()); }
    public void SetName(string key) { _name = key; }
  }
}

This C# program has two classes: Child and Parent. The differences between the WKO and CAO models are fairly clear if the following VBScript samples are run:

set c1 = GetObject
   ("soap:wsdl=http://jnoss4/refpass/RefPass.Child.soap?wsdl")
set c2 = GetObject
   ("soap:wsdl=http://jnoss4/refpass/RefPass.Child.soap?wsdl")
c1.SetName("C1")
WScript.Echo c1.CountUp()
WScript.Echo c1.CountUp()
WScript.Echo c1.CountUp()
WScript.Echo c1.CountUp()
WScript.Echo c1.CountUp()
C2.SetName("C2")
WScript.Echo c2.CountUp()
WScript.Echo c2.CountUp()
WScript.Echo c2.CountUp()
WScript.Echo c2.CountUp()
WScript.Echo c2.CountUp()

When run, the output is as follows:

C:\moniker>refpasswko
Microsoft (R) Windows Script Host Version 5.6
Copyright (C) Microsoft Corporation 1996-2001. All rights reserved.

none 1
none 1
none 1
none 1
none 1
none 1
none 1
none 1
none 1
none 1

The name and value illustrate the stateless nature of single call well-known objects; neither names nor values are retained between method calls because the component is created with each method call.

If a client proxy is exported and then imported on another machine, and the following VBScript is run, the SOAP activation will be CAO instead of WKO:

'Create two objects directly
set c1=CreateObject("RefPass.Child")
set c2=CreateObject("RefPass.Child")
'Set the name of the first object, and call it several times
'to increment the object internal counter
c1.SetName("C1")
WScript.Echo c1.CountUp()
WScript.Echo c1.CountUp()
WScript.Echo c1.Countup()
WScript.Echo c1.CountUp()
WScript.Echo c1.Countup()
'Set the name of the first object, and call it several times
'to increment the object internal counter
c2.SetName("C2")
WScript.Echo c2.CountUp()
WScript.Echo c2.CountUp()
WScript.Echo c2.Countup()
WScript.Echo c2.CountUp()
WScript.Echo c2.Countup()
'Create a parent object
set p=CreateObject("RefPass.Parent")
'Pass the child objects to the parent and call it from the parent
WScript.Echo p.SetRef(c1)
WScript.Echo p.CountUp(c2)
WScript.Echo p.CountUp(c2)
WScript.Echo p.CountUp(c2)
WScript.Echo p.CountUp(c2)
'Now call the child object stored internally in the parent
dim c9
WScript.Echo p.CountUp(c9)
'Get the object back from the parent and call it directly
Set c3 = p.GetRef()
WScript.Echo c3.CountUp()

When run from the command line, the following output is displayed:

C:\moniker>refpasscl
Microsoft (R) Windows Script Host Version 5.6
Copyright (C) Microsoft Corporation 1996-2001. All rights reserved.

C1 1
C1 2
C1 3
C1 4
C1 5
C2 1
C2 2
C2 3
C2 4
C2 5
C1 5
C2 6
C2 7
C2 8
C2 9
C1 6
C1 7

The CAO activation retains state, even when called over SOAP, and permits object references to be passed back and forth over SOAP. Both the names and the values are retained in the class instances on the server, and references work correctly. Both of these scripts are calling the same compiled C# component; they differ only in .NET Remoting activation models.

In addition to invoking CAO activations by using CreateObject, a moniker is provided with COM+ that provides CAO activation instead of WKO—the typename and assembly moniker. The following script:

'Create two objects directly
set c1=GetObject("soap:typename=RefPass.Child,assembly=RefPass")
set c2=GetObject("soap:typename=RefPass.Child,assembly=RefPass")
'Set the name of the first object, and call it several times
'to increment the object internal counter
c1.SetName("C1")
WScript.Echo c1.CountUp()
WScript.Echo c1.CountUp()
WScript.Echo c1.Countup()
WScript.Echo c1.CountUp()
WScript.Echo c1.Countup()
'Set the name of the second object, and call it several times
'to increment the object internal counter
c2.SetName("C2")
WScript.Echo c2.CountUp()
WScript.Echo c2.CountUp()
WScript.Echo c2.Countup()
WScript.Echo c2.CountUp()
WScript.Echo c2.Countup()
'Create a parent object
set p=GetObject("soap:typename=RefPass.Parent,assembly=RefPass")
'Pass the child objects to the parent and call it from the parent
WScript.Echo p.SetRef(c1)
WScript.Echo p.CountUp(c2)
WScript.Echo p.CountUp(c2)
WScript.Echo p.CountUp(c2)
WScript.Echo p.CountUp(c2)
'Now call the child object stored internally in the parent
dim c9
WScript.Echo p.CountUp(c9)
'Get the object back from the parent and call it directly
Set c3 = p.GetRef()
WScript.Echo c3.CountUp()

produces the following output:

C:\moniker>refpassca
Microsoft (R) Windows Script Host Version 5.6
Copyright (C) Microsoft Corporation 1996-2001. All rights reserved.

C1 1
C1 2
C1 3
C1 4
C1 5
C2 1
C2 2
C2 3
C2 4
C2 5
C1 5
C2 6
C2 7
C2 8
C2 9
C1 6
C1 7

This is the same output as the VBScript CreateObject(ProgID) example used above. Because the normal COM+ activation path is intercepted for SOAP proxy applications, CoCreateInstance, CreateInstance, and other traditional COM+ activation methods can be used to invoke client-activated objects using COM+ Web Services.

The assembly and typename moniker is also useful for getting preconfigured client-activated remoting from a managed code client, as shown in the following example:

Imports System
Imports System.Runtime.InteropServices
Module RefPassCl
Sub Main()
    Dim ChildMoniker = "soap:assembly=RefPass,typename=RefPass.Child"
    Dim ParentMoniker = "soap:assembly=RefPass,typename=RefPass.Parent"
    Dim c1,c2,p as Object
    c1 = Marshal.BindToMoniker(ChildMoniker)
    Console.WriteLine(c1.SetName("C1"))
    Console.WriteLine(c1.CountUp())
    Console.WriteLine(c1.CountUp())
    Console.WriteLine(c1.CountUp())
    Console.WriteLine(c1.CountUp())
    Console.WriteLine(c1.CountUp())
    c2 = Marshal.BindToMoniker(ChildMoniker)
    Console.WriteLine(c2.SetName("c2"))
    Console.WriteLine(c2.CountUp())
    Console.WriteLine(c2.CountUp())
    Console.WriteLine(c2.CountUp())
    Console.WriteLine(c2.CountUp())
    Console.WriteLine(c2.CountUp())
    p = Marshal.BindToMoniker(ParentMoniker)
    Console.WriteLine(p.SetRef(c1))
    Console.WriteLine(p.CountUp(c2))
    Console.WriteLine(p.CountUp(c2))
    Console.WriteLine(p.CountUp(c2))
    Console.WriteLine(p.CountUp(c2))
    Dim c9
    Console.WriteLine(p.CountUp(c9))
    Dim c3 = p.GetRef()
    Console.WriteLine(c3.CountUp())
 End Sub
End Module

Compiling this Visual Basic .NET application and running it will produce the same output as the preceding two VBScript CAO samples.

Because the server publication publishes the component as both CAO and WKO, the choice of activation method is made by the remote client. Although probably of only academic interest, a single client machine can access the same SOAP-published virtual root on a remote server using both of the remoting activation methods for the same component.

Limitations and Differences of SOAP and DCOM

One of the goals of .NET Remoting is to provide a rich distributed environment in which a developer can mix and match serialization protocols (formatters) with network protocols (channels). COM+ Web Services in release 1.0 of the .NET Framework supports only one formatter (SOAP) and one channel (HTTP). This does not mean that other channels and formatters will not work with ServicedComponents or COM+, but rather that there is no automatic configuration to provide client and server endpoints for these alternative channels and formatters.

An enormous number of COM+ components have been written using a variety of languages. It would be great if all these components could be enabled as Web services using COM+ Web Services. Just as with the .NET Framework version 1.0, not all existing COM components will work with COM+ Web Services. While the majority of existing components that have type libraries should work fairly well, some components, such as Windows Script Components (WSC) components, are not supported in this release. Some complex type libraries where interfaces have multiple levels of inheritance, or are dependent on multiple type libraries, may not work well. In addition, due to limitations of the type library conversion, only the default interface in a type library will be available as a Web service.

COM+ Web Services is not a complete solution for every existing unmanaged COM+ component. There is a very large number of existing unmanaged COM+ components written in a variety of programming languages. It is not possible to test every possible type library generated by every compiler that supports COM+, so some unmanaged COM+ components will not publish properly using COM+ Web Services. One of the goals of COM+ Web Services is to minimize the time and effort needed to make this assessment. Simply publish your unmanaged COM+ component as a COM+ Web service, and the developer can quickly determine whether there are any problems with the intended use as a Web service. If problems are encountered, there are several alternatives that can be used with existing unmanaged components. These alternatives include writing managed or unmanaged wrappers that provide a compatible interface that can be published as a Web service. In most cases, writing such a wrapper is much less work than rewriting an entire component. This minimizes both development and test effort in putting an application in production as an XML Web service.

When using unmanaged (Visual Basic 6.0 or Visual C++) servers, with managed client applications and SOAP, early binding generally works better than late binding. In some cases, the generated metadata may not work correctly when used as a late bound cross-machine remoting proxy.

Within the realm of using the SOAP formatter over HTTP, depending on the target deployment environment, a number of options can still be used. COM+ Web Services generates XML-based remoting configuration files for both server and CAO client configurations. (URL references for WKO activations are embedded in the generated client proxy, so no configuration file is necessary.) COM+ Web Services generates straightforward, functional configuration files that can be customized by the user to meet any needs beyond straightforward SOAP communication over HTTP. Areas that could be customized include user authentication, as in the following example:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
 <system.runtime.remoting>
  <application>
   <service>
    <wellknown mode="SingleCall" type="SCTrans.SCTransSQL, SCTrans, 
      Version=0.0.0.0, Culture=neutral, 
      PublicKeyToken=9c6052078b454cee" 
      objectUri="SCTrans.SCTransSQL.soap" />
    <activated type="SCTrans.SCTransSQL, SCTrans" />
   </service>
  </application>
 </system.runtime.remoting>
 <identity impersonate="true" />
</configuration>

Adding the highlighted line above causes the user identity to be used in activating COM+ components when called over SOAP. (By default, the IIS virtual root uses standard IIS authentication.) This permits COM+ partitioning (a COM+ Windows Server 2003 feature) to be used with SOAP, where the actual component implementation invoked can vary by user identity.

Another area that could be customized include lifetime management for client-activated objects, as in the following example:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
 <system.runtime.remoting>
  <application>
   <service>
    <wellknown mode="SingleCall" type="SCTrans.SCTransSQL, SCTrans, 
      Version=0.0.0.0, Culture=neutral, 
      PublicKeyToken=9c6052078b454cee" 
      objectUri="SCTrans.SCTransSQL.soap" />
    <activated type="SCTrans.SCTransSQL, SCTrans" />
   </service>
   <lifetime leaseTime="30S" renewOnCallTime="30S" />
  </application>
 </system.runtime.remoting>
</configuration>

Adding the highlighted line to the web.config file will change the lifetime of client-activated objects in this IIS VRoot from 6 minutes to 30 seconds. Changing the SingleCall attribute of the wellknown element to Singleton will change the activation behavior to route all incoming method calls to a single instance of a component instead of activating a new component on each method call.

.NET Remoting (like the rest of the .NET Framework) supports garbage collection instead of reference counting. This means that in a few situations unmanaged transactional COM+ components will behave differently using COM+ Web Services than using DCOM. For a transactional method that is published via a WKO single call, it is essential that either SetComplete be called or automatically done (by selecting the Automatically deactivate this object when this method returns check box in the component method properties page) be selected. This is essential because, until the component is garbage-collected, the component will not be released. When using DCOM, reference counting will usually cause the transaction to commit or abort when the component is released even if this rule is ignored. When moving to the COM+ Web Services environment, this is not guaranteed before the transaction times out in a garbage-collected environment. A failure to call SetComplete or to configure the method to autocomplete will manifest itself in an intermittent failure to commit transactions because they will time out before the component is garbage-collected.

Design Considerations

In COM+ Web Services, when the Uses SOAP check box is selected (using the Component Services administrative tool), two different activation models are exposed on an IIS virtual root: WKO and CAO. Which one is better, and which should you use?

The WKO single call activation model sounds very expensive. With each method call, a new component is created, the method call made, and the component sent to the garbage collector. However, in areas where performance is critical and where WKO is the right model for the business process at hand, pooled ServicedComponents or pooled unmanaged C++ components will alleviate much of the processing of single call activations. When pooled components are used, an object is retrieved from the pool when the WKO activation comes in, the call is made, and the object is returned to the pool. The stateless nature of this protocol and the use of pooling improves the potential for increased scalability. In WKO single call where the object is not pooled, the lifetime of the object is only for the duration of the call.

CAO, on the other hand, offers the performance advantage of a single activation on the server and continued communication with that single instance of a component. The activation overhead is avoided on multiple method calls from a client to a server. If the server component (either ServicedComponent or unmanaged C++ component) is pooled, an object is retrieved from the pool and then returned to the pool when the method call is completed. If the object is not pooled, the object lifetime is dependent on the lease lifetime specified in the web.config file or is set programmatically by the component itself. Lifetime is important because the garbage collector will not release the memory for a component until the lifetime has expired. In a high volume CAO configuration, this will involve some design decisions by a developer.

Under the Covers

If all you want to do is publish or consume a Web service using COM+ Web Services, you can stop reading right here. If, however, you want to customize, extend, or simply understand the process used, keep reading. The information that follows is not necessary to use this feature but could be useful if you want to extend the capabilities manually. COM+ Web Services is a simple wrapper, over a very rich set of services provided by .NET Remoting that can easily be extended by a developer or administrator.

Server IIS Virtual Roots

To make this feature work, no hidden hooks were added to .NET Remoting. Rather, COM+ code was written to do the configuration necessary to publish COM+ endpoints as IIS virtual roots. On the server, this involves creating a physical directory to serve as the virtual root and generating a web.config file to make the components accessible via remoting. If the component is unmanaged (Visual C++ or Visual Basic 6.0), proxy metadata is also generated so that remoting can access the component. If your Windows XP system directory is c:\windows, server configuration files and any generated metadata reside in the following directory tree:

C:\windows\system32\com\SoapVRoots\vrootname\

The following generated files are put in this directory when a SOAP endpoint is published on the server:

  • web.config   The basic remoting configuration file for the vroot, which has a number of options that can be added or edited by the developer or system administrator to tune remoting performance and security.
  • default.disco   A file that can be used with Visual Studio .NET to generate a reference to the published Web service if you are developing a managed code client. This is particularly helpful in an extranet case where your partners want to develop their own clients.
  • default.aspx   A simple Microsoft ASP.NET page that publishes each component as a hyperlink.

All of the above files are generated by default. If you want to remove some of these capabilities, simply edit or delete the appropriate file. (If you delete the web.config file, however, all SOAP publication from the IIS virtual root will stop.)

All generated metadata is placed in the following directory as well as in the GAC:

C:\windows\system32\com\SoapVRoots\vrootname\bin

In .NET Remoting, the bin directory is a special location. When an HTTP request comes in to IIS, assemblies will be searched for in this directory, so in many cases publication in the bin directory is the only step necessary. When publishing a SOAP endpoint, however, the generated assembly is also put in the GAC. This is because the scope of assembly resolution for a virtual root is limited to the bin directory and the GAC. If your code passes a reference to an object from one virtual root to another on the same machine, resolution of the reference in the target virtual root will fail unless the assembly is in the GAC. If you are using generated metadata for unmanaged Visual Basic 6.0 or Visual C++ components, you can remove the generated assembly from the GAC if you are not passing references.

One important note about the .NET Framework in this version is that if an assembly is loaded and System.Reflection is used to access the assembly file, the file will be locked in memory until the process terminates. Reflection is used when WSDL is generated dynamically for generating a proxy, so for an active IIS virtual root being accessed by client processes, there is a good chance the assembly files will be locked. This is unlikely to present a problem in a production environment, but for a developer who is frequently changing a component, it is something that should be kept in mind.

If you are using ServicedComponents with COM+ Web Services, it is also a requirement that the assembly be in the GAC unless you originally place the assembly in the bin directory and run regsvcs.exe on the assembly in that directory. If you have the Microsoft .NET Framework SDK loaded, you can use the gacutil.exe command line utility to put a ServicedComponent in the GAC. If you have Windows Server 2003 with the built-in .NET Framework or have loaded the .NET Framework redistributable on a Windows XP computer, you can use the Microsoft .NET Framework Configuration user interface, accessible from the Administrative Tools menu, to add an assembly to the GAC.

In addition, when using Windows XP or Windows Server 2003, ensure that IIS is installed and configured to provide ASP.NET application services. These settings are necessary to provide the dynamic content necessary to use SOAP.

Generated Proxy Assembly Cache

For an unmanaged COM+ component to be published through .NET Remoting as a SOAP endpoint, a proxy needs to be generated to make the unmanaged components available to the .NET Framework. This is done by programmatically performing the same steps as tlbimp.exe, the .NET Framework SDK tool that is used to convert unmanaged COM+ type libraries to proxy metadata assemblies. For client activation over SOAP to succeed, however, client and server machines must share the same strong-named signed metadata proxies. For this reason, when a managed proxy assembly is generated for an unmanaged COM+ component, a strong name key is also generated and used to sign the proxy assembly.

A strong name key can be generated only once, and there is no concept of a strong name key in unmanaged COM+ components. This means that if you generate the proxy several times, you create different strong name keys. This would have the effect of creating separate managed identities for the same unmanaged COM+ component. To minimize the effects of this, all generated proxy assemblies for unmanaged COM+ components are also written to the following SoapCache directory:

C:\windows\system32\com\SoapCache\componentdirectory\proxymetdata.dll

where componentdirectory is of the form:

ATLTrans.dll_40960_2001_6_27_15_4_16

The directory name is created from file name, file size, and date and time of last compilation. This scheme is based on the assumption that when an unmanaged COM+ component is recompiled, it needs to have a new proxy generated. This, in turn, is based on the assumption that code is recompiled in production environments only when there have been changes to it.

This SoapCache directory exists so that if the same unmanaged component is published in another virtual root on the same machine, instead of generating a proxy assembly, the one in cache will be reused. This is to ensure that the strong name signature (and hence identity) of the component will be shared across virtual roots.

If you export a SOAP-enabled unmanaged COM+ component as a server application and import it to another server, the cached proxy metadata will be carried along, so different servers can share the same managed identity for identical unmanaged assemblies. Alternatively, to generate or write and sign their own proxy, users can simply put the metadata in the appropriate cache directory and it will be used when SOAP publication occurs on the server. The basic rule here is that, to avoid unnecessary proliferation of signed proxies for the same unmanaged component proxy, assemblies are never generated if a file from the cache can be used instead.

Client Configuration

Plumbing is also necessary on the client side. The simplest case (at least in terms of user effort) is the first sample program given in this article:

set SoapObj = 
   GetObject("soap:wsdl=http://www.xmethods.net/sd
                   /TemperatureService.wsdl")
WScript.Echo "Fairbanks  Temperature = " & SoapObj.getTemp("99707")

When the WSDL moniker is processed, the following steps occur:

  1. A check is made to see whether a proxy has been generated for this URL before. If so, it is used again. (Skip to Step 4.)
  2. If the check fails, the WSDL is retrieved from the URL and a C# proxy program is generated, using essentially the same logic that is used by the soapsuds.exe command line utility that ships with the .NET Framework SDK.
  3. The C# program is compiled to a DLL and given a name that is a match for the URL (with illegal characters converted to acceptable characters in a file name).
  4. The generated proxy is then used to communicate via .NET Remoting (WKO) to the remote server specified in the WSDL.

These proxies are generated and stored in the following folder:

C:\windows\system32\com\SoapAssembly\

In the case of client activation, a client proxy import of an exported COM+ application is necessary on the client machine. This application export/import brings along the signed metadata assemblies from the server that are necessary for client activation. During the import process, configuration files are also generated and put in the SoapAssembly directory. A typical client configuration file takes the following form:

<configuration>
 <system.runtime.remoting>
  <application>
   <client url="http://MyServer/VB6Soap">
    <activated type="VB6SoapSoapLib.CalcClass, VB6SoapSoapLib"/>
   </client>
  </application>
 </system.runtime.remoting>
</configuration>

COM+ Web Services reads this configuration file before activating the component, so the activation model could potentially be changed on client machines by modifying or replacing this configuration file.

A Beginning, Not an Ending...

COM+ Web Services was designed with the goal of simplifying some steps in combining .NET Remoting with the COM+ services included in Windows XP and the Windows Server 2003 family. It was not designed to include every option or cover any user contingency, but rather to simplify common tasks. In a manner similar to using a wizard to create a program in Visual Studio .NET, some advanced tasks are left to the user. To permit user extensibility, generated items are rarely completely deleted. In addition, XML classes are used to edit the generated configuration files and if there are already existing configuration files, nodes will be added and deleted from them in response to changes from the Component Service administrative tool or the Microsoft COM+ Administration SDK. COM+ Web Services are designed to make it easy for a user to extend or customize what has been generated.

In sum, COM+ Web Services provides an easy introduction to XML Web services and SOAP for both for existing Visual Basic and Visual C++ COM+ components and for new managed ServicedComponents written in Visual Basic .NET and C#.