事务性组件示例
简单的计算器远算不上工作量繁重的业务应用程序,因此我们现在考虑带有对象池的适于 COM+ 事务性组件的应用程序。
最容易管理和配置的组件是由
ServicedComponent 导出的托管代码组件,如以下 C# 示例所示:
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;}}
要建立并运行此 C# 组件,在完成编辑连接值以连接到 Microsoft SQL Server™ 数据库之后,需要使用 sn.exe 生成 sctrans.snk 加强名称关键字文件,然后在
using 语句中使用程序集引用对其进行编译。如果您在服务器上进行部署,应使用 gacutil.exe(如果正在使用 SDK)或通过 .NET 框架用户界面将程序集放入 GAC,然后运行 regsvcs.exe,注册 COM+ 托管组件。Regsvcs.exe 将使用以下属性,将组件发布为服务器上的 SOAP 端点和服务器(进程外)激活:
[assembly: ApplicationActivation(ActivationOption.Server,SoapVRoot="CSSoapSQL")]
此组件在每种方法调用中使用不同的事务,具有一个自动完成方法,并被配置为进行缓冲。使用托管和非托管 COM+ 组件时,对象池和事务将如所预期的那样通过 SOAP 运行。例如,如果使用下列 VBScript 通过 SOAP 访问以下
ServicedComponent:
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 10WScript.Echo i & " " & j & " " & c.CountUp("SCWKONC")nextnext
将显示以下输出内容:
C:\moniker>actscwkoMicrosoft (R) Windows Script Host Version 5.6Copyright (C) Microsoft Corporation 1996-2001. All rights reserved.soap:wsdl=http://jnoss3/sctrans/SCTrans.SCTransSQLNC.soap?WSDL1 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 68 NC af26b53b-4a1f-48c8-8880-518c2b55a7ce 1 79 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
这就是所预期的缓冲的组件:从缓冲池中拖出对象并重新使用。使用客户端激活的缓冲组件的行为都是相同的。
非托管组件的对象池和事务也如所预期的那样运行(虽然 Visual Basic 6.0 组件不支持对象池)。需要为大多数非托管应用程序通过 COM+ 管理工具设置缓冲和事务属性。
传递引用
WKO 与 CAO 模型的一个关键区别在于它们向有状态的对象传递引用的能力。以下是 C#
ServicedComponent 示例,显示了此操作的基本步骤:
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; }}}
此 C# 程序有两个类:
Child 和
Parent。如果运行以下 VBScript 示例,WKO 与 CAO 模型的区别会更加明显:
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()
运行时将显示以下输出内容:
C:\moniker>refpasswkoMicrosoft (R) Windows Script Host Version 5.6Copyright (C) Microsoft Corporation 1996-2001. All rights reserved.none 1none 1none 1none 1none 1none 1none 1none 1none 1none 1
名称和值说明了单一调用已知对象的无状态性质,因为组件是使用不同的方法调用创建的,所以方法调用之间不保留名称或值。
如果导出客户端代理,然后导入到另一台客户端计算机上,并且运行了下面的 VBScript,则 SOAP 激活将是 CAO 而不是 WKO:
'直接创建两个对象set c1=CreateObject("RefPass.Child")set c2=CreateObject("RefPass.Child")'设置第一个对象的名称,并调用数次'以递增对象内部计数器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()'创建父对象set p=CreateObject("RefPass.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)'现在调用存储在父对象内部的子对象dim c9WScript.Echo p.CountUp(c9)'从父对象获取该对象并直接调用Set c3 = p.GetRef()WScript.Echo c3.CountUp()
从命令行运行时,将显示以下输出内容:
C:\moniker>refpassclMicrosoft (R) Windows Script Host Version 5.6Copyright (C) Microsoft Corporation 1996-2001. All rights reserved.C1 1C1 2C1 3C1 4C1 5C2 1C2 2C2 3C2 4C2 5C1 5C2 6C2 7C2 8C2 9C1 6C1 7
即使在通过 SOAP 调用时,CAO 激活也会保留状态,并且允许通过 SOAP 来回传递对象引用。名称和值都保留在服务器上的类实例中,并且引用可以正确工作。这两种脚本都调用相同的编译 C# 组件,只是 .NET Remoting 激活模型不同。
除了使用
CreateObject 调用 CAO 激活外,还可以使用带有 COM+ 的名字对象,它可以提供 CAO 激活来替代 WKO(类型名称和程序集名字对象)。以下脚本:
'直接创建两个对象set c1=GetObject("soap:typename=RefPass.Child,assembly=RefPass")set c2=GetObject("soap:typename=RefPass.Child,assembly=RefPass")'设置第一个对象的名称,并调用数次'以递增对象内部计数器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()'创建父对象set p=GetObject("soap:typename=RefPass.Parent,assembly=RefPass")'将子对象传递到父对象,并从父对象调用子对象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)'现在调用存储在父对象内部的子对象dim c9WScript.Echo p.CountUp(c9)'从父对象获取该对象并直接调用Set c3 = p.GetRef()WScript.Echo c3.CountUp()
将显示以下输出内容:
C:\moniker>refpasscaMicrosoft (R) Windows Script Host Version 5.6Copyright (C) Microsoft Corporation 1996-2001. All rights reserved.C1 1C1 2C1 3C1 4C1 5C2 1C2 2C2 3C2 4C2 5C1 5C2 6C2 7C2 8C2 9C1 6C1 7
这与上面的 VBScript
CreateObject(ProgID) 示例的输出内容相同。因为常规 COM+ 激活路径被 SOAP 代理应用程序截获,所以可以使用
CoCreateInstance、
CreateInstance 以及其他传统的 COM+ 激活方法来调用使用 COM+ Web 服务的客户端激活的对象。
程序集和类型名称名字对象,对于从托管代码客户端远程获取预先配置的客户端激活也很有用,如下例所示:
Imports SystemImports System.Runtime.InteropServicesModule RefPassClSub Main()Dim ChildMoniker = "soap:assembly=RefPass,typename=RefPass.Child"Dim ParentMoniker = "soap:assembly=RefPass,typename=RefPass.Parent"Dim c1,c2,p as Objectc1 = 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 c9Console.WriteLine(p.CountUp(c9))Dim c3 = p.GetRef()Console.WriteLine(c3.CountUp()) End SubEnd Module
编译并运行此 Visual Basic .NET 应用程序,将产生与前面两个 VBScript CAO 示例相同的输出内容。
因为服务器应用程序将组件发布为 CAO 和 WKO 两种形式,所以由远程客户端选择激活方法。虽然可能只对学术研究有意义,但是单一客户端计算机确实可以使用同一组件的两种远程激活方法,访问远程服务器上同一个 SOAP 发布的虚拟根。
……