Peishu Li-Visual Basic and COM+ Programming by Example (By Example) - Que (2000) PDF
Peishu Li-Visual Basic and COM+ Programming by Example (By Example) - Que (2000) PDF
Acquisitions Editor
Visual Basic and COM+ Programming Gretchen Ganser
by Example Development Editor
Copyright 2001 by Que Sean Dixon
iii
Contents at a Glance
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1
Part I Welcome to COM+ 5
1 What COM+ Is All About . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .7
2 Windows DNA 2000 and COM+ . . . . . . . . . . . . . . . . . . . . . . . . . . .31
3 Introduction to Microsoft Message Queuing Services (MSMQ) . . .71
4 Introduction to Visual Basic COM Programming . . . . . . . . . . . . .111
5 Using the Component Services MMC Snap-In . . . . . . . . . . . . . . .147
Part II Developing COM+ Application Components 171
6 Writing Transactional Components . . . . . . . . . . . . . . . . . . . . . . .173
7 Compensating Resource Manager (CRM) . . . . . . . . . . . . . . . . . . .215
8 COM+ Security . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .257
9 Queued Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .281
10 COM+ Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .317
11 Administering COM+ Applications Programmatically . . . . . . . . .349
12 More on COM+ Programming . . . . . . . . . . . . . . . . . . . . . . . . . . .387
Part III Building Real-World COM+ Services Applications 409
13 Northwind Traders Online: A COM+ Enabled
Windows DNA 2000 Application . . . . . . . . . . . . . . . . . . . . . . . . . .411
14 A Case Study: COM+ and Enterprise Application
Integration (EAI) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .443
Appendix A: COM+ and Related Web Resources . . . . . . . . . . . . . . . .467
Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .470
00 0789724588 fm 10/25/00 5:03 PM Page iv
Table of Contents
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1
About This Book . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1
Who Should Use This Book . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1
This Book’s Organization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .2
Part I Welcome to COM+ 5
1 What COM+ Is All About . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .7
COM: The Component Object Model . . . . . . . . . . . . . . . . . . . . . . . .8
What Is COM? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .8
COM Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .10
The Virtual Function Table (vtable) . . . . . . . . . . . . . . . . . . . .13
COM Threading Models . . . . . . . . . . . . . . . . . . . . . . . . . . . . .14
Distributed COM (DCOM) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .18
In-Process Servers Versus Out-of-Process Servers . . . . . . . . .18
DCOM Architecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .18
Microsoft Transaction Server (MTS) . . . . . . . . . . . . . . . . . . . . . . .20
MTS Architecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .20
MTS Programming Model . . . . . . . . . . . . . . . . . . . . . . . . . . .21
MTS Security Model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .24
MTS Limitations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .24
What Is COM+? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .25
Core COM+ Services . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .25
New COM+ Services . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .26
COM+ Programming Model . . . . . . . . . . . . . . . . . . . . . . . . . .27
2 Windows DNA 2000 and COM+ . . . . . . . . . . . . . . . . . . . . . . . . . . .31
Windows DNA 2000 Architecture . . . . . . . . . . . . . . . . . . . . . . . . . .32
Internet Applications and Their Challenges . . . . . . . . . . . . .32
Three-Tiered Application Architecture . . . . . . . . . . . . . . . . . .33
Infrastructure Services . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .42
Where COM+ Fits In . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .43
Microsoft Data Access Components (MDAC) . . . . . . . . . . . . . . . . .44
MDAC and Universal Data Access (UDA) . . . . . . . . . . . . . . .44
Internet Information Services (IIS) and Active Server
Pages (ASP) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .64
00 0789724588 fm 10/25/00 5:03 PM Page v
vi
vii
viii
ix
xi
Dedication
To my Dad and Mom, who gave me life, wisdom, and love.
To my wonderful wife, Xiaofang, and my lovely son, Jeff.
00 0789724588 fm 10/25/00 5:03 PM Page xii
xii
Acknowledgments
This book is a product of teamwork. I would like to thank the entire editorial
team at Que for their help and efforts on this project. Special thanks to
Acquisitions Editor Gretchen Ganser, Technical Editors Steve White and
Vincent MayField, Development Editor Sean Dixon, Project Editor Heather
McNeill, Copy Editor Chuck Hutchinson, and Proofreader Harvey Stanbrough
for their patience and guidance.
Special thanks to Holly Allender, the original acquisitions editor of this book,
who hired me for this exciting and challenging project.
00 0789724588 fm 10/25/00 5:03 PM Page xiii
xiii
Introduction
The Component Object Model, or COM, dramatically changed the programming
paradigm and made the life of developers who use Microsoft programming tools,
such as Visual Basic and Visual C++, much easier. Microsoft Transaction Server,
or MTS, further simplified the programming model by sheltering developers
from the complex programming tasks, such as system level services and trans-
action management, letting them focus on solving business problems rather
than struggling with writing plumbing code.
COM+ combines the power of both COM and MTS and adds a whole set of new
services, which makes programming using Microsoft Visual Studio tools more
configuration or attributes based. COM+ adds more infrastructure services at
the operating system level and becomes an integral part of Windows 2000.
Understanding COM+ services and mastering its programming paradigm is
imperative since COM+ is the essential enabling technology for building
Windows Distributed interNet Applications, or Windows DNA 2000.
2 Introduction
Introduction 3
Part I
Welcome to COM+
Chapter 1: What COM+ Is All About
Chapter 2: Windows DNA 2000 and COM+
Chapter 3: Introduction to Microsoft Message Queuing Services (MSMQ)
Chapter 4: Introduction to Visual Basic COM Programming
Chapter 5: Using the Component Services MMC Snap-In
03 0789724588 CH01 10/25/00 5:00 PM Page 6
03 0789724588 CH01 10/25/00 5:00 PM Page 7
1
What COM+ Is All About
COM+ is not a radical departure from Microsoft’s existing component-based
technologies, such as COM, DCOM, and MTS. Rather, COM+ builds on the
success of its predecessors by improving, extending, and unifying them, and
providing several significant new services. In addition, COM+ services are
deeply integrated into the Windows 2000 operating system and have
become part of system services. To unleash the power of COM+ services,
you need a solid understanding of COM, DCOM, and MTS.
This chapter teaches you the following:
• The fundamentals of the Component Object Model (COM), Distributed
COM (DCOM), and Microsoft Transaction Server (MTS)
• The evolution of COM+ from its predecessors
• The core COM+ services
• The COM+ programming model
03 0789724588 CH01 10/25/00 5:00 PM Page 8
What Is COM?
Before you delve deeper into COM internals, you’ll look at the problems of
software reuse and the issues involved in component (or application) inter-
operability. Then you’ll see how COM addresses these problems and issues.
So, what is software reuse? As a software developer, you might find yourself
repeatedly writing some common routines in project after project. For
example, most enterprise applications require some mechanism to check
users’ security credentials, such as identification, permissions, and so on. It
would be most efficient if you could simply reuse the routines you wrote
earlier to save yourself the time and effort of developing and testing new
code. You could probably use the Windows Clipboard to copy and paste the
code you wrote. Some object-oriented programming languages, such as C++,
allow you to reuse the source code in several ways, including inheritance, or
sub-classing. But having copied and pasted multiple copies of the same
code, what are you going to do when you want to enhance the security-
checking functionality? Unfortunately, you will have to modify the same
routines everywhere you use them.
Also, code reuse works only when two projects are written in the same pro-
gramming language. Unfortunately, this is often not the case and that pre-
sents a second problem. For instance, if your original security-checking
routine was written in Visual Basic and the next project uses Java, you
have to rewrite the same routines in Java, meaning you have to “translate”
the code from one programming language to another. What a pain!
As you can see, code reuse is not an elegant solution in software reuse. It
would be nice if there were some mechanism that would allow you to write
the security routines just once and wrap them into some form of software.
Later, you could simply plug this software into other applications and use it
regardless of the programming languages’ differences. The mechanism I am
talking about here is called software reuse. The difference between code
reuse and software reuse is that the former is language specific and the lat-
ter is language independent—reuse happens at the binary level.
03 0789724588 CH01 10/25/00 5:00 PM Page 9
NOTE
Any programming languages that support pointers (or pointers to functions) can be
used to write COM components. You can write COM components in C++, Java, Visual
Basic, PowerBuilder, Delphi, or even Microfocus COBOL. Pointer is an unfamiliar term to
most Visual Basic programmers. A pointer is a variable that stores a memory address
of another variable. In Visual Basic, you don’t need to explicitly use pointers; this job is
handled by the Visual Basic runtime behind the scene.
After you finish writing your security-checking routine, you just need to
compile (wrap) the code into a binary file. This binary file is called a COM
component (a piece of software). Later, you can simply register (plug) this
COM component into the System Registry and start using this functionality
without worrying about which language was used to build the COM compo-
nent or which language is reusing it.
Put in COM terminology, the security-checking functionalities are the ser-
vices exposed by a COM component, called a COM server. The application
that uses the service of a COM server is called a COM client.
NOTE
Some COM literature uses the terms COM component and COM object interchangeably.
This use is confusing and, strictly speaking, incorrect. In this book, I use either COM
components or COM servers to refer to the physical binary file, whereas I use the term
COM objects to refer to instances of activated COM classes. COM objects and COM
classes will be covered in Chapter 4, “Introduction to Visual Basic COM Programming.”
Client
COM Object
Application
Interface
COM Interfaces
A COM interface is a group of functions that define a set of behaviors of a
COM component. The functions defined by an interface are called methods.
The attributes of an interface are called properties. A COM component can
have more than one interface. By convention, interface names start with an
I. For example, the interface that defines the security COM component can
be named ISecurity. This interface defines two methods. One method is
called CheckIdentity(), and the other method is called CheckPermission().
Figure 1.2 is a Unified Modeling Language (UML) diagram of the
ISecurity interface.
<<Interface>>
ISecurity
CheckIdentity()
EXAMPLE CheckPermission()
Figure 1.2: The ISecurity interface has two methods: CheckIdentity() and
CheckPermission().
NOTE
Microsoft uses the “I” naming convention in most of their published COM interfaces
(not all of them, though). The advantage of using a consistent naming convention for
the interfaces is that you can differentiate them (abstract classes) from concrete imple-
mentations.
NOTE
UML, which stands for Unified Modeling Language, is a standard object-modeling lan-
guage that is supported by various visual modeling tools such as Microsoft Visual
Modeler and Rational Rose. In UML, an interface diagram is similar to a class diagram.
It contains three sections. From the top to the bottom, the first section is the interface
name (or the class name in case of a class diagram), the second section contains
properties (the ISecurity interface doesn’t have any properties, so in Figure 1.2 this
section is empty), and the third section contains methods.
03 0789724588 CH01 10/25/00 5:00 PM Page 11
The COM specification requires that all COM components must support a
special interface: IUnknown. COM further specifies that all other interfaces
must be derived from this IUnknown interface. Therefore, IUnknown is the
root interface of all other COM interfaces. It has three methods:
QueryInterface(), AddRef(), and Release(). Figure 1.3 is a UML diagram
of the IUnknown interface.
<<Interface>>
IUnknown
QueryInterface()
EXAMPLE AddRef()
Release()
UUIDs). You can manually generate a GUID with a utility called GUID-
GEN. To use the GUIDGEN utility, click Start, Run and type GUIDGEN to
open the screen shown in Figure 1.4.
TIP
The GUIDGEN.EXE utility comes with Windows 2000 installation. It is not available, how-
ever, in the default installation of Windows NT or 9.x platforms. To get GUIDGEN.EXE for
these platforms, you can run a custom setup of Visual Studio 6.0, Enterprise Edition.
For purposes of this book, the most useful GUID format is the Registry for-
mat, which looks something like this:
{CAAABA82-855C-4204-8171-1D3453F7A304}
This is the format used in the IDL (Interface Identification Language) files,
and internally by Visual Basic, when you compile your COM components.
When you click the New GUID button, the GUID changes in the Result
frame. You can copy the GUID being generated by GUIDGEN to the
Clipboard by clicking the Copy button and pasting it in your code.
In Visual Basic, you usually don’t have to generate GUIDs manually by
yourself. When you compile your COM components, Visual Basic automati-
cally generates these GUIDs for your class (called a Class ID or CLSID)
and interfaces (called an Interface ID or IID). Chapter 4, “Introduction to
Visual Basic COM Programming,” discusses Class IDs and the Registry in
greater detail.
Figure 1.5 shows how the CLSID for the Microsoft Word application (a
COM object) looks in the Registry.
03 0789724588 CH01 10/25/00 5:00 PM Page 13
EXAMPLE
Figure 1.5: The CLSID for the Microsoft Word application component in the
Registry.
IUnknown
Security Component
Client Application
Security Object Instance
QueryInterface()
Interface Pointers {
m_pIUnknown ISecurity }
m_pISecurity vtable pointer AddRef()
{
}
Release()
{
Vtable }
CheckIdentity()
pQueryInterface {
pAddRef }
pRelease
CheckPermission()
pCheckIdentity
{
pCheckPermission
}
Figure 1.6: A client accesses the actual methods in an interface through the
virtual function table (vtable).
TIP
In Figure 1.6, the prefix p in a variable’s name indicates that the variable is a pointer;
the prefix m_ indicates that the variable is a member of a class. Class member vari-
ables are said to be at class scope. Variables at global scope should be prefixed with
g_, and variables inside methods, at local scope, should be of the form
pVariableName. This is a convention known as Hungarian notation, developed at
Microsoft by Charles Simonyi.
section, you’ll look at a few more terms, threads and apartments, and exam-
ine the threading models that COM supports.
A thread is a scheduled, time-sliced context of execution within a process
that is executed serially.
Processors execute code at thread level, not at process level. All processes
must have at least one thread. You can view a thread as an execution unit
inside a process. Threads actually interact with objects to perform a job in
response to requests from clients. A process has one or more threads. In
DOS or earlier Windows operating systems, all applications ran on a single
thread of execution. Windows 9.x and NT rely on multiple threads of execu-
tion to support multitasking. The system kernel provides a thread sched-
uler to dynamically schedule and execute threads according to certain
priority settings at both the process and thread levels.
Threads and objects reside in logical containers called apartments. An
apartment is a conceptual association that describes the relationship
between objects and threads, defines the rules that govern the concurrency
pattern among objects, and provides a means by which the access to objects
is handled.
COM supports two apartment models: the Single-Threaded Apartment
model, or STA, and Multi-Threaded Apartment model, or MTA. An STA can
have only a single thread. All objects inside the STA are associated with
EXAMPLE this particular thread, as shown in Figure 1.7.
Process
STA
Object
Thread
Object
Object
All calls to the objects inside an STA must be made through the thread in
that STA. This applies to any call from any thread outside the STA, includ-
ing calls from clients or other COM objects. Direct calls to objects in an STA
are not allowed from threads outside the STA. Calls from one apartment to
03 0789724588 CH01 10/25/00 5:00 PM Page 16
Client
Marshaled
Process
STA STA
Object Object
Thread Thread
Object Object
Object Object
Apartment 1 Apartment 2
Marshaled
Figure 1.8: Calls from a client are made through the thread. Calls from one
apartment to another need to be marshaled.
Process
MTA
Object
Object
Object
Threads
Process
STA MTA
Object Object
Thread
Object Object
Object Object
Table 1.1 lists possible values that can be stored in ThreadingModel and the
corresponding threading models they support.
NOTE
In-process servers (ActiveX DLLs) offer better performance over out-of-process servers
(ActiveX EXEs) because they don’t have the overhead of crossing the process bound-
aries and data marshaling.
DCOM Architecture
DCOM achieves this interprocess or intermachine communication through
a proxy-stub mechanism with the help of a COM library. Figure 1.12
depicts the DCOM architecture.
03 0789724588 CH01 10/25/00 5:00 PM Page 19
Machine boundary
Client
Proxy Object Stub COM Component
Application
EXAMPLE
DCOM network
protocol
TIP
The proxy and stub objects are in fact identical to one another, but they behave differ-
ently in their different roles. The proxy is designed to imitate the server, so the client
believes it is communicating directly with the server. The point of proxies is that all
COM calls must be made to an object in the client’s process. In-process COM servers
(DLLs) are already in the client’s process, but out-of-process and remote servers must
have a “representative” in their client’s process, which looks exactly like them—the
stub. This stub then uses interprocess communication (of which cross-machine calls
are a special case) to communicate with the server’s process.
MTS Architecture
MTS provides its services through a runtime environment. It also provides
a server process for hosting COM components. For example, Figure 1.14
illustrates how a client application interacts with a series of COM compo-
nents and a back-end database to perform user security credential
checking.
03 0789724588 CH01 10/25/00 5:00 PM Page 21
Client
Application
EXAMPLE
Resource
Dispenser
Manager
ADO
MS DTC
OLE DB
User Table
SQL Server
Figure 1.14: A client accesses a database back end through MTS compo-
nents.
The two types of MTS packages are the library package and the server
package. A library package runs in the process address of the client that
creates it. A server package runs in its own process address on the local
computer where the package resides.
03 0789724588 CH01 10/25/00 5:00 PM Page 23
TransactionID
Object-Creator ID
IObjectContext
Security
ISecurity
Component
Figure 1.16: MTS creates an associated context object for each COM object
it hosts.
MTS Limitations
Although MTS provides numerous services to developers so that they can
focus on resolving business problems; it does have several limitations, how-
ever.
MTS is not part of COM. Rather, it is built on top of COM, which places
some restrictions on the way the client can create an instance of COM com-
ponents hosted in MTS. For example, if you use the Visual Basic keyword
New or the CreateObject() method to create an instance of COM compo-
nents, you do not get the reference of the context object. Therefore, the
object cannot participate in the existing transaction, even if its transac-
tional attributes say it should do so. For the COM object to be enlisted in
03 0789724588 CH01 10/25/00 5:00 PM Page 25
What Is COM+? 25
What Is COM+?
Now that you have read through the preceding discussion on COM, DCOM,
MTS, and their limitations, you probably want to know exactly what COM+
is. COM+ is a set of component services that ship with Microsoft Windows
2000. COM+ combines and enhances all the services available in COM,
DCOM, and MTS and unifies them into a new integrated entity with many
great new services. The following sections introduce some core COM+ ser-
vices and the new COM+ programming model.
SECURITY MODEL
COM+ extended role-based security to library applications in addition to
server applications.
TIP
Former MTS packages are now called applications in COM+. COM+ applications will be
discussed in Chapter 5, “Using the Component Services MMC Snap-In.”
03 0789724588 CH01 10/25/00 5:00 PM Page 26
THREADING MODEL
As you learned earlier, MTS restricts the threading model of its components
to the Single-Threaded Apartment (STA) model. COM+ now supports a new
threading model called Neutral-threading Apartment (NTA), which allows
execution of objects on any thread types.
TRANSACTION MANAGEMENT
In addition to the MTS transaction semantics, COM+ now introduces an
“auto-done” transaction management mechanism that allows the system to
automatically call SetComplete() or SetAbort() if the developer didn’t code
these calls explicitly. Transaction will be covered in Chapter 6, “Writing
Transactional Components.”
SIMPLIFIED ADMINISTRATION
Whereas MTS uses the System Registry for storing the metadata that
describes components, COM+ now provides a new registration database
called RegDB for this purpose. RegDB offers better management capability
than the System Registry. Information in RegDB can be accessed program-
matically through a transactional, scriptable COM interface called the
COM+ catalog. Additionally, the Component Services MMC snap-in pro-
vides a better centralized administrative tool than the MTS Explorer for
administering and deploying components.
RegDB and the COM+ catalog will be discussed in Chapter 11,
“Administering COM+ Applications Programmatically”. The Component
Services MMC snap-in will be covered in Chapter 5, “Using the Component
Services MMC Snap-in.”
What Is COM+? 27
OBJECT POOLING
COM+ provides the object pooling service to reduce the overhead incurred
in object creation by maintaining a pool of objects instead of destroying and
re-creating objects each time after using them. For the object to be pooled,
it must meet several requirements. For example, the object must have no
thread affinity. As you learned earlier in this chapter, the object must sup-
port the MTA or Free-threading model. Visual Basic 6.0 supports only sin-
gle apartment threading (STA), so you can’t use VB6 to create pooled
objects yet. Microsoft promised that the next version of Visual Basic will
support free threading. Until then, to create pooled objects, you have to use
a tool that meets the requirements, such as Visual C++. Object pooling will
be discussed in Chapter 12, “More on COM+ Programming.”
For example, the Synchronization attribute allows you to specify what type
of synchronization you want when your components are activated. The
Object pooling attribute lets you decide whether the component should be
pooled. For pooled objects, you can further specify the pool sizes and time-
out value. You can control whether the JIT should be enabled. The Queuing
and Events attributes allow you to control the behavior of Queued
Components and Loosely Coupled Events, respectively.
One of the most noticeable new COM+ attributes is Object construction.
This feature enables parameterized object construction with an administra-
tively specified construction string. When this feature is enabled, construc-
tor strings are passed in during object creation. Components can access
these strings during runtime through the IobjectConstruct interface. In
Chapter 5, you will see more detailed discussion and examples about object
construction.
What’s Next
By now, you should have a firm understanding of what COM+ and its ser-
vices are, as well as the importance of COM+ services in building scalable,
distributed enterprise applications. The next chapter describes Microsoft’s
framework for building three-tier applications: the Windows Distributed
interNet Application (DNA) architecture. You’ll also see how COM+ plays a
significant role in the DNA architecture as an enabling technology and a
glue for sticking all the different pieces together.
03 0789724588 CH01 10/25/00 5:00 PM Page 29
04 0789724588 CH02 10/25/00 5:11 PM Page 30
04 0789724588 CH02 10/25/00 5:11 PM Page 31
2
Windows DNA 2000 and COM+
Microsoft Windows Distributed interNet Application (DNA) is a framework
and blueprint for developers to quickly and easily build scalable, high-per-
formance, distributed, and robust enterprise and Internet applications.
Windows 2000 is the next generation of Windows DNA. For purposes of this
discussion, I’ll simply use the term Windows DNA or just DNA although I
am implying Windows DNA 2000.
This chapter teaches you the following:
• The basics of Windows DNA 2000
• How COM+ is related to Windows DNA
• Microsoft data-access strategies and how to use Microsoft ActiveX
Data Objects (ADO) in Windows DNA applications
• The basics of Internet Information Services (IIS 5.0) and Active Server
Pages (ASP)
04 0789724588 CH02 10/25/00 5:11 PM Page 32
SCALABILITY
The more successful an Internet application becomes, the more potential
users it will reach. The potential number of users can grow dramatically
overnight in orders of magnitude. The application must be able to handle
the increases in number of users without significantly degrading its perfor-
mance.
AVAILABILITY
Today’s Internet applications must be kept up and running all the time, 24
hours a day, 7 days a week. A possible system shutdown in an Internet
environment could cause a significant financial loss. No business can afford
such a loss.
RELIABILITY
Internet applications must be rock-solid. Data processed or generated by an
application must be reliable and consistent. Inconsistent data in a system
could cause disastrous consequences to the business.
SECURITY
Internet applications have a much higher security requirement than tradi-
tional applications. An Internet application must be able to protect its sen-
sitive data against unauthorized access. An Internet application also must
04 0789724588 CH02 10/25/00 5:11 PM Page 33
be able to determine what group of users are allowed to perform what oper-
ations or to access what part of data based on their given privileges to the
application.
MAINTAINABILITY
Applications must be easy to support and maintain. They also must be easy
to modify and enhance to accommodate business evolution and keep an
organization competitive.
TWO-TIERED APPLICATIONS
Traditional client/server applications are typically two-tiered applications.
In a two-tiered application, all clients directly access the data store on the
server through a network such as a local area network (LAN), as illustrated
EXAMPLE in Figure 2.1.
04 0789724588 CH02 10/25/00 5:11 PM Page 34
Client 1
Server
Client 2
Data Store
Client 3
User Name
Password
Logon Cancel
Server
Logins Table
Users Database
User Name
Password
Logon Cancel
THREE-TIERED APPLICATIONS
Due to the limitations with the two-tiered application model, Microsoft
DNA presents a three-tiered application architecture. In a three-tiered
model, the business logic is separated from both the client and the database
as a separate tier, called the business tier or sometimes the middle tier.
04 0789724588 CH02 10/25/00 5:11 PM Page 38
TIP
In some Microsoft documentation, the three different tiers are called services. For
example, the presentation tier is called User Services, the middle tier (or business tier)
is called Business Services, and the data tier is called Data Services.
Business Tier
(Business Services)
Data Tier
(Data Services)
The presentation tier accesses the business components in the business tier
directly through either the DCOM protocol, as in the case of a Win32 appli-
cation, or indirectly through the Active Server Pages (ASP) on a Web
server, such as the Internet Information Services (IIS) using the HTTP pro-
tocol, as in the case of a Web browser application. ASP and IIS will be dis-
cussed later in the section “Internet Information Services (IIS) and Active
Server Pages (ASP).”
The business tier can further be partitioned as a Business Logic Layer
(BLL) and Data Access Layer (DAL). The DAL components access data
stored in the data tier typically through Microsoft ActiveX Data Objects
(ADO). ADO is an important component in Microsoft Data Access
Components (MDAC). You’ll see many ADO examples later, in the section
“Microsoft Data Access Components (MDAC).”
CAUTION
Don’t confuse the Data Access Layer (or DAL) with the data tier. The Data Access Layer
is a subtier within the business tier; it is part of the Business Services. Because the
data tier is the place where data stores (such as databases) reside, it is the data ser-
vice.
• The DAL object encapsulates all the database access details from the
BLL and the client. It calls the stored procedure proc_ValidateUser
(Step 4) and
• Retrieves a result set from the database table (Step 5).
04 0789724588 CH02 10/25/00 5:11 PM Page 40
User Services
HTTP DCOM
BLL DAL
ASP pages
Business Services
ADO
OLE DB OLE DB
Providers Providers
Data Services
You don’t have to worry about the detail design of the COM objects and the
implementation of code. You’ll learn about them in depth in Chapter 4,
“Introduction to Visual Basic COM Programming.” The purpose here is sim-
ply to demonstrate the concept of Windows DNA three-tiered application
architecture.
In the Windows DNA three-tiered architecture, the business tier is sepa-
rated from the presentation and data tiers. It can be used by any number of
clients. Because business components (BLL and DAL) are centralized in the
business tier, they are more easily deployed and maintained. The clients
now can’t directly access the database, so you can enhance the security by
setting appropriate security configurations at the component level, at the
interface level, or even at the methods level. So, the security of the applica-
tion is improved. The scalability of the three-tiered application is also
improved because the component can serve a lot more clients and the
04 0789724588 CH02 10/25/00 5:11 PM Page 41
User Name
Password
Logon Cancel
1
Infrastructure Services
The preceding section discussed the benefits of the three-tiered application
architecture of Windows DNA. This section briefly introduces you to the
rich set of infrastructure services provided by the Windows 2000 operating
system and shows how these services enhance the three-tiered architecture,
addresses the issues of enterprise and Internet applications, and helps
developers focus on providing business solutions rather than developing
“plumbing” code.
COMPONENT SERVICES
The heart of Windows DNA is the component services, or COM+, which is
what this book is all about. The component services of Windows DNA pro-
vide the following:
• Distributed transactions
• Extended transaction support for nontransactional systems (the
Compensation Resource Manager, or CRM)
• Security
• Queued Components (QC)
• Loosely Coupled Events (LCE)
• Object pooling
• Object construction
• Transient state management
Part II of this book will teach you all the component services through
plenty of Visual Basic code examples.
DATA SERVICES
Microsoft Data Access Components (MDAC) provides a universal strategy
for accessing all kinds of data across the enterprise. MDAC consists of a set
of programming interfaces, including OLE DB, ActiveX Data Objects
(ADO), and Open Database Connectivity (ODBC). OLE DB and ADO are
COM-based interfaces that offer easier programming models and better
performance over ODBC. Microsoft integrates OLE DB and ADO support
into all the programming languages in Visual Studio. OLE DB and ADO
are described later, in the section “Microsoft Data Access Components
(MDAC).”
04 0789724588 CH02 10/25/00 5:11 PM Page 43
MESSAGE SERVICES
Message queuing is an industry standard that allows different applications
or their components to interact in a loosely coupled, asynchronous manner.
The Microsoft Message Queue (MSMQ) 1.0 was originally introduced as a
component in the Windows NT 4.0 Option Pack. In Windows 2000, MSMQ
2.0 is integrated as part of system services. MSMQ will be discussed in
Chapter 3, “Introduction to Microsoft Message Queuing Services (MSMQ).”
WEB SERVICES
Microsoft also integrated its powerful Web server, Internet Information
Service (IIS 5.0), and the Active Server Pages (ASP) technology into the
Windows 2000 operating system. IIS and ASP allow developers to build
powerful script applications that run on the server side to produce dynamic,
interactive Web pages. IIS and ASP will be discussed in the section
“Internet Information Services (IIS) and Active Server Pages (ASP).”
SECURITY SERVICES
Windows DNA provides integrated security services, including authentica-
tion, authorization, data integrity, and data privacy services. Security will
be discussed in Chapter 8, “COM+ Securities.”
Data Consumers
ADO
Server Components
Cursor Engine
Data Providers
ODBC Driver
Non- Mainframe
Relational Data
Data
Connection Errors
Command Parameters
Properties
Recordset
Fields
Record
Stream
As shown in Figure 2.8, ADO 2.5 introduces a couple of new objects: the
Record and Stream objects. The Record object represents and manages direc-
tories and files in a file system, for example, or folders and messages in an
04 0789724588 CH02 10/25/00 5:11 PM Page 46
email system. It can also represent a row in a recordset. The Stream object
is used to read, write, and manage the binary stream of bytes or text that
comprise a file or message stream.
Compared to previous Microsoft data access object models, such as DAO
and RDO, ADO has a relatively “flat” object model, as shown in Figure 2.8.
That is, no restrictive hierarchical relationships exist between the top-level
objects. Among the three objects—Connection, Command, or Recordset—you
can create any one of them without explicitly creating the other two.
A thorough coverage of ADO 2.5 is beyond the scope of this book. This dis-
cussion focuses on the practical aspects of ADO that are most important in
programming the Data Access Layer of business components. This chapter
describes the Connection, Command, and Recordset objects, which are the
most important and most used ADO objects. I will teach you how and when
to use them. I will also show you some advanced ADO techniques. To follow
the examples here, you need to use the pubs SQL Server sample database.
TIP
The pubs database should be installed by default when you install the SQL Server. If it
is not installed for some reason, you can run the SQL script file, instpubs.sql, using the
Query Analyzer of SQL Server 7.0. The instpubs.sql script file is located under the
install subdirectory of your SQL Server installation, such as C:\MSSQL7\Install\.
To work with ADO in Visual Basic, you need to set a reference to the ADO
Object Library, as shown in Figure 2.9. This process is called early binding.
You can also use late binding to create and use ADO objects, without refer-
EXAMPLE encing the ADO Object Library in your Visual Basic project. You’ll learn
about object binding in Chapter 4, “Introduction to Visual Basic COM
Programming.”
Figure 2.9: Setting a reference to the ADO Object Library in Visual Basic.
04 0789724588 CH02 10/25/00 5:11 PM Page 47
With oConnection
‘Specify the Provider property,
‘MSDASQL is the default provider.
.Provider = “MSDASQL”
‘Set the ConnectionString property
.ConnectionString = “Driver={SQL Server};” & _
“Server=(local);” & _
“Database=pubs;” & _
“UID=sa;” & _
“PWD=;”
‘Open the connection.
.Open
End With
DSN is the Data Source Name. To run some of the sample code in this sec-
tion, you need to set up a System DSN on your machine, name it pubs, and
point to the pubs database on the SQL Server. Toss set up a DSN in
Windows 2000, open the Control Panel and click the Administrative Tools
applet to open the Administrative Tools window. There, you can access the
Data Sources (ODBC) utility to set up the DSN (see Figure 2.10).
Figure 2.10: Accessing the Data Sources (ODBC) utility in Windows 2000.
04 0789724588 CH02 10/25/00 5:11 PM Page 49
Listing 2.2 uses the OLE DB provider for SQL Server, the native SQL
Server provider.
Listing 2.2 Opening a Database Connection Using the OLE DB Provider for SQL Server
EXAMPLE
Private Sub cmdConnection2_Click()
‘Enable the error handler
On Error GoTo Connection2_Err
‘Declare and create an instance of
‘a Connection object.
Dim oConnection As ADODB.Connection
Set oConnection = New ADODB.Connection
With oConnection
‘Specify the Provider property.
.Provider = “SQLOLEDB”
‘Set the ConnectionString property
.ConnectionString = “Data Source=(local);” & _
“Initial Catalog=pubs;” & _
“User ID=sa;” & _
“Password=;”
‘Open the connection.
.Open
End With
In this case, the Provider property is set to “SQLOLEDB”. Also, notice that the
syntax for ConnectionString is different from Listing 2.1:
.ConnectionString = “Data Source=(local);” & _
“Initial Catalog=pubs;” & _
“User ID=sa;” & _
“Password=;”
04 0789724588 CH02 10/25/00 5:11 PM Page 50
In this syntax, the Data Source is the server, and Initial Catalog is the
database. SQL Server has another important optional parameter,
Trusted_Connection, which indicates the User Authentication mode. The
default value of Trusted_Connection is No. In this case, the OLEDBSQL
provider uses the Mixed mode to authorize user access to the SQL Server
database. The SQL Server login and password are specified in the User Id
and Password properties. If you set this property to Yes, SQLOLEDB uses
the NT Authentication mode to authorize user access to the SQL Server
database.
TIP
These examples are for demonstration purposes. When possible, always use the native
OLE DB providers because they are optimized for the target data stores. Use the OLE
DB provider for ODBC only when no native OLE DB provider for a specific data store is
available.
.CommandText = sSQL
.ActiveConnection = oConnection
.Execute
End With
In Figure 2.11, a SQL Server Query Analyzer window shows that the new
row is inserted into the authors table in the pubs database.
Notice that Listing 2.3 explicitly uses an ADO connection object and sets it
as the ActiveConnection property. ADO allows you to use the Command object
without explicitly creating a Connection object, by setting the
ActiveConnection property to a connection string. There are a couple of
downsides to using this approach. First, ADO automatically creates a new
Connection object for you instead of using an existing connection, thus con-
suming additional server resources. Second, the Connection object has an
04 0789724588 CH02 10/25/00 5:11 PM Page 52
associated Errors collection, which stores any OLE DB provider errors dur-
ing the session. The Errors collection is very valuable for debugging and
troubleshooting. However, this Errors collection is available to you only
through the explicitly created Connection object.
The ADO Command object has a Parameters collection, which allows you to
execute parameterized queries or stored procedures. You’ll see how to use
the Parameters collection later in this chapter when you examine how to
execute stored procedures using ADO.
MsgBox Err.Description
End Sub
Figure 2.12 shows the results of Listing 2.4 in Visual Basic’s debug window.
Figure 2.12: The results of Listing 2.4 in Visual Basic’s debug window.
The other two methods of creating ADO Recordsets include calling the
Execute method against either the Connection or Command object.
EXAMPLE
04 0789724588 CH02 10/25/00 5:11 PM Page 54
Listing 2.5 Creating an ADO Recordset Using the Execute Method of the Connection
Object
Private Sub cmdRecordset2_Click()
‘Enable the error handler.
On Error GoTo Recordset2_Err
‘Declare and initialize ADO object variables.
Dim oRecordset As ADODB.Recordset
Dim oConnection As ADODB.Connection
Set oConnection = New ADODB.Connection
The way the Execute method works against the ADO Command object is simi-
lar to the way it works with the ADO Connection object. Listing 2.6 shows
how to do this.
EXAMPLE
04 0789724588 CH02 10/25/00 5:11 PM Page 55
Listing 2.6 Creating an ADO Recordset Using the Execute Method of the Command Object
Private Sub cmdRecordset3_Click()
‘Enable the error handler.
On Error GoTo Recordset3_Err
‘Declare and initialize ADO object variables.
Dim oRecordset As ADODB.Recordset
Dim oConnection As ADODB.Connection
Dim oCommand As ADODB.Command
Set oConnection = New ADODB.Connection
Set oCommand = New ADODB.Command
With oCommand
‘Set command properties.
.ActiveConnection = oConnection
.CommandType = adCmdText
.CommandText = “Select au_fname, au_lname from authors”
‘Create the recordset object
Set oRecordset = .Execute
End With
The output of Listings 2.5 and 2.6 is the same as that of Listing 2.4, as
shown in Figure 2.12.
Given these three different methods for creating an ADO recordset, which
one should you choose? If the recordset is the result of a SQL Select state-
ment, as shown in the examples, use the first method (see Listing 2.4). If
the recordset is the result returned by a stored procedure without output
parameters or a return value, use the second method (see Listing 2.5). If
the recordset is the result of executing a stored procedure that takes output
parameters or a return value, or if the SQL statement is a parameterized
query, use the third method (see Listing 2.6). Calling stored procedures
using ADO will be discussed next.
@title varchar(80),
@message varchar (255) out
As
Return @count
Go
To create a stored procedure, open SQL Server Query Analyzer, type the
script code as shown in Listing 2.7, and then press Ctrl+E.
Listing 2.8 contains sample Visual Basic code that shows how to process
EXAMPLE the stored procedure you just created.
Listing 2.8 Visual Basic Code for Processing the Stored Procedure
Private Sub cmdStoredProc_Click()
‘Enable the error handler
On Error GoTo StoredProc_Err
‘Declare variables.
Dim oConnection As ADODB.Connection ‘the Connection object
Dim oCommand As ADODB.Command ‘the Command object
Dim oParameter As ADODB.Parameter ‘the Parameter object
Dim sSQL As String ‘the stored procedure
Dim sMessage As String ‘string variable for holing the output
parameter.
‘Initialize the Connection and Command objects.
Set oConnection = New ADODB.Connection
Set oCommand = New ADODB.Command
End With
For the Name argument, you use the actual parameter names of the stored
procedure, such as @title_id and @title for both input and output parame-
ters, but you use Return for the return values. You specify the Direction
argument as adParamInput, adParamOutput, and adParamReturnValue for
input parameters, output parameters, and return values, respectively. The
return value has to be the first parameter object to be appended to the
Parameters collection. When you run the code in Listing 2.8, you see a mes-
sage box like the one shown in Figure 2.13.
If the stored procedure takes neither output parameters nor return values,
you can use the execute methods of either the Connection or Command object,
followed by the stored procedure statement, as shown in the following code
segments:
OConnection.Execute “Exec AddTitle @title_id = ‘BU8000’, “ & _
“ @title = ‘All You Need to Know About COM+’”
or:
OCommand.Execute “Exec AddTitle @title_id = ‘BU8000’, “ & _
“ @title = ‘All You Need to Know About COM+’”
In the “Working with Recordsets” section, you saw some basic VB code
examples for dealing with ADO recordsets. Now see what else you can do
about ADO recordsets.
04 0789724588 CH02 10/25/00 5:11 PM Page 60
.UpdateBatch
End With
With rsPublishers
‘Define fields and append them to the recordset.
.Fields.Append “pub_id”, adChar, 4
.Fields.Append “pub_name”, adChar, 10
.Fields.Append “city”, adVarChar, 20
.Fields.Append “state”, adChar, 2
.Fields.Append “country”, adVarChar, 30
‘Open the recordset
.Open
‘populate the recordset with AddNew and Update methods.
‘Add Que to the publisher recordset.
.AddNew
!pub_id = “1111”
!pub_name = “Que”
!city = “Indianapolis”
!State = “IN”
!country = “USA”
.Update
‘Add Sams to the publisher recordset.
.AddNew
!pub_id = “2222”
!pub_name = “Sams”
!city = “Indianapolis”
!State = “IN”
!country = “USA”
.Update
‘Add BradyGames to the publisher recordset.
.AddNew
!pub_id = “3333”
!pub_name = “BradyGames”
!city = “Indianapolis”
!State = “IN”
!country = “USA”
.Update
After the execution of code in Listing 2.10, the Visual Basic debug window
looks like the one in Figure 2.14.
Figure 2.14: Three publishers have been added to the shorthand recordset.
Before finishing this journey through ADO, let’s spend a little time on ADO
error handling. Whereas Chapter 12, “More on COM+ Programming,” will
discuss COM+ error handling techniques in general, this section focuses on
ADO- and OLE DB-specific error handling issues.
As you saw earlier, the ADO Connection object has an associated Errors col-
lection. Errors generated by the OLE DB provider are added to this Errors
collection. If ADO itself encounters an error, however, it stores the error
information in the Visual Basic Err object, so the error handling code has to
04 0789724588 CH02 10/25/00 5:11 PM Page 64
take care of both errors. Now you can expand the error handling section of
Listing 2.8 to see how to appropriately handle ADO errors, as shown in the
following code segment:
Private Sub cmdStoredProc_Click()
‘See code in Listing 2.8
‘for details
‘. . . . . .
StoredProc_Err:
Dim sErrorInfo As String
Dim oErr As Error
If Not oConnection Is Nothing Then ‘Make sure the Coonection object
‘is not destroyed yet.
For Each oErr In oConnection.Errors
sErrorInfo = sErrorInfo & oErr.Description vbCrLf
Next
‘Now we are ready to destroy the Connection object.
Set oConnection = Nothing
End If
‘Destroy the Command Object
If Not oCommand Is Nothing Then
Set oCommand = Nothing
End If
If len(sErrorInfo) <> 0 Then
MsgBox sErrorInfo ‘Report OLE DB provider error.
Else
MsgBox Err.Description ‘Report regular error.
End IF
End Sub
Inside an ASP page, you use <SCRIPT> tags or the inline equivalent <%> to
indicate the scripts so that the Web server knows which part of the code
should be executed on the server.
Let’s look at an ASP example to see how it works. Listing 2.11 is an ASP
page that retrieves the author names from the authors table of the pubs
database on the SQL Server using ADO and displays the results in an
EXAMPLE
HTML table.
Listing 2.11 Retrieving Author Names and Displaying Them in an HTML Table
<%@ Language=VBScript %>
<html>
<head>
<title>ASP ADO Demo</title>
<h3>Authors</h3>
<% dim rsAuthors ‘ADO recordset for holding authors.%>
<% set rsAuthors = server.CreateObject(“ADODB.Recordset”)%>
<table width =300 border=2>
<tr><td><b>First Name</b></td><td><b>Last Name</b></td></tr>
<%
‘Set the ActiveConnection property
rsAuthors.ActiveConnection = “Provider=SQLOLEDB;” & _
“Data Source=(local);” & _
“Initial Catalog=pubs;” & _
“User ID=sa;” & _
“Password=;”
The first line specifies that you are going to use VBScript as your scripting
language. VBScript is the default language used in ASP. You create an ADO
Recordset object by calling the CreateObject() method of the Server object.
04 0789724588 CH02 10/25/00 5:11 PM Page 67
Then you call the Open method of the ADO recordset, just as you do in the
Visual Basic examples. Now you loop through the ADO recordset using the
MoveNext() method and write the results as an HTML table through the
Response object.
To see this ASP page in action, open a text editor such as Windows
Notepad, type the code in Listing 2.11, and save the file as ADODemo.asp.
In the Web server’s root directory (C:\Inetpub\wwwroot\), create a subdi-
rectory called ASPDemo, and copy ADODemo.asp to it. Now launch a Web
browser such as Internet Explorer 5.0. In the Address line, type
http://<YourMachineName>/ASPDemo/ADODemo.asp. You should see something
like the result shown in Figure 2.16. Here, <YourMachineName> is the name
of the IIS Server; in my case, the server name is Peishu.
TIP
You can also use Microsoft Visual InterDev 6.0 to create ASP pages. Visual InterDev is
part of the Visual Studio family. It supports a rich set of IDE functionalities that make
creating a Web page a lot easier.
In Windows DNA architecture, ASP pages are considered part of the User
Services, or the presentation tier. In Chapter 13, “Northwind Traders
Online: A COM+ Enabled Windows DNA 2000 Application,” you will use
ASP pages as the client application.
04 0789724588 CH02 10/25/00 5:11 PM Page 68
What’s Next
In this chapter, you explored Windows DNA 2000 architecture. You also
spent a great deal of time on Active Data Objects (ADO) because this topic
is fundamental to the Data Access Layer of the entire business logic middle
tier. In the next chapter, “Introduction to Microsoft Message Queuing
Services (MSMQ),” you will shift gears a little bit and look at the asynchro-
nous processing world of Windows 2000 message queuing services.
Knowledge of how MSMQ works is essential for understanding some
advanced COM+ services—for example, Queued Components (QC).
04 0789724588 CH02 10/25/00 5:11 PM Page 69
05 0789724588 CH03 10/25/00 5:06 PM Page 70
05 0789724588 CH03 10/25/00 5:06 PM Page 71
3
Introduction to Microsoft Message
Queuing Services (MSMQ)
Distributed applications run on two or more computers. They communicate
with one another by passing data over machine boundaries through appro-
priate network protocols. Most of these protocols use synchronous technolo-
gies, such as Remote Procedure Calls (RPC) and DCOM. The synchronous
process model has a number of limitations, however. Message queuing pro-
vides an asynchronous programming model and a loosely coupled environ-
ment for different components of distributed applications.
This chapter teaches you the following:
• The limitations of synchronous processing
• Message queuing technology and Microsoft Message Queuing Services
(MSMQ)
• MSMQ architecture
• How to write MSMQ applications in Visual Basic
05 0789724588 CH03 10/25/00 5:06 PM Page 72
Client Application
DCOM
Machine 2
COM+
Data Store
In Figure 3.1, the client application in machine 1 interacts with the COM
objects that are running on machine 2 using DCOM. This synchronous pro-
cessing model has several limitations:
• A synchronous processing system requires a reliable network connec-
tion. As in Figure 3.1, the client has no way to communicate with the
server when the network is disconnected.
• In a synchronous processing system, both communicating parties must
be available at the same time. In Figure 3.1, if the COM objects on
machine 2 are not up and running, calls from the client application
fail.
05 0789724588 CH03 10/25/00 5:06 PM Page 73
• In Figure 3.1, when the client application makes a call to the COM
object on the server, it must wait for the server to finish its processing.
If the server takes a long time to process a client call, the client appli-
cation is blocked (or frozen) until the server finishes processing.
• If machine 2 in Figure 3.1 is shut down for some reason, the calls from
the client to the server fail. Therefore, the synchronous processing
model is not fault tolerant and thus is not a robust architecture.
Machine 1 Machine 2
Message
Message
Client
Receiver COM Object
Application
Queue Queue
COM Object
COM+
Message
Data Store
NOTE
The terms client and client application here simply mean the message sender. They are
relative terms. The roles of the sender and the receiver can be reversed.
TIP
The configuration described here is called an independent client in MSMQ. In the IBM
MQSeries system, you can use clustering queue managers to achieve the same results.
IBM MQSeries, which is IBM’s message queuing product, offers capabilities comparable
to MSMQ. I will discuss only MSMQ in this book, because this chapter is intended to
give you all the background information for Chapter 9, “Queued Components.” Queued
Components is an important COM+ services that uses MSMQ to achieve messaging
queuing functionality.
MSMQ
Message queuing products are sometimes referred to as Message-Oriented
Middleware (MOM). Microsoft Message Queue Services 2.0 is now an inte-
grated part of Windows 2000 component services; it is the Microsoft imple-
mentation of MOM technology.
Applications developed for MSMQ can communicate across heterogeneous
networks and with computers that may be offline. MSMQ provides guaran-
teed message delivery, efficient routing, security, transactional support, and
priority-based messaging.
05 0789724588 CH03 10/25/00 5:06 PM Page 75
MSMQ Architecture 75
TIP
In Microsoft Windows 2000 documentation, MSMQ 2.0 is referred to as message queu-
ing. In Microsoft Platform SDK documentation, both terms (MSMQ, and message
queuing) are used.
MSMQ Architecture
Depending on your Windows 2000 configuration, MSMQ can be used in a
domain environment or a workgroup environment. The difference is that
for MSMQ, a domain environment includes domain controllers that provide
a directory service, such as Active Directory, whereas a workgroup environ-
ment does not provide such a directory service.
Domain Environment
In a domain environment, an MSMQ network is a group of Windows 2000
sites, connected by routing links. Sites map the physical structure of a net-
work, whereas domains map the logical structure of an organization. Sites
and domain structures are independent of each other. A single site can have
multiple domains, whereas a single domain can also have multiple sites. In
Windows 2000, a site is defined as a set of computers in one or more IP sub-
nets. Routing links are logic communication links created by MSMQ to
route messages between different sites. In MSMQ, a computer that can pro-
vide message queuing, routing, and directory services to client computers is
called an MSMQ server. A routing link is made up of MSMQ servers, one on
each site.
CAUTION
Don’t confuse routing links with site links. Routing links are used by MSMQ to route
messages between sites, whereas site links are used by domain controllers to replicate
Active Directory between sites.
Workgroup Environment
An MSMQ computer can also run in a workgroup environment that is not
part of a domain. There are several restrictions, however. All the benefits
provided by Active Directory Services are not available.
First, messages cannot be routed by an MSMQ server; a direct connection
with the destination server is required.
Second, you can create and manage only private queues on a local computer.
You cannot view or manage public queues. You can, however, send messages
to or read messages from private queues, provided that a direct connection
to the destination MSMQ server is specified.
05 0789724588 CH03 10/25/00 5:06 PM Page 76
NOTE
In MSMQ 2.0, public queues are those published in Active Directory and can be
accessed anywhere in the Active Directory forest. Private queues are not published in
Active Directory and can be accessed only by MSMQ applications that know the full
pathname or the format name of the queue. Public queues are persistent. Private
queues are lightweight and more suitable for offline operations in which the directory
services may not be available.
Queues
In MSMQ, queues are temporary storage locations for different types of
messages. Queues can be logically divided into two groups: application
queues and system queues. Application queues are created by applications.
System queues are created by MSMQ.
TIP
Application queues can also be created using the Computer Management MMC snap-in.
Figure 3.3 shows the different types of queues in the Message Queuing ser-
vices in the Computer Management snap-in.
EXAMPLE
TIP
If the Message Queuing service is not started yet for some reason, you cannot see it
under the Services and Applications node in the Computer Management snap-in. You
can manually start the Message Queuing service by using the Component Services
snap-in. From Services, locate and right-click the Message Queuing service; then select
Start (as shown in Figure 3.4).
05 0789724588 CH03 10/25/00 5:06 PM Page 77
MSMQ Architecture 77
In Figure 3.3, you may have noticed another type of queue: the outgoing
queue. Those queues are used for offline operations in which directory ser-
vice is not available. When MSMQ on a client machine is configured for
offline use, it is called an independent client. When MSMQ on a client
machine is configured for real-time access support, it is called a dependent
client.
APPLICATION QUEUES
Application queues include message queues, administration queues, response
queues, and report queues. These queues are created by applications.
Message queues allow applications to exchange data through messages.
Applications can send messages to and receive them from message queues.
Message queues can be either public or private. Figure 3.5 shows an exam-
EXAMPLE ple of a message queue called TestQueue that is created as a private queue.
CAUTION
When you create a queue from an application, it is always displayed in lowercase under
Message Queuing in the Computer Management snap-in. However, the names in MSMQ
are case sensitive, so be extremely careful in your code when you refer to a queue. For
example, if you create a queue called MyQueue, it shows up in MSMQ as myqueue. In
your code, however, you still need to access this queue by using MyQueue. You get an
error if you refer it as myqueue.
05 0789724588 CH03 10/25/00 5:06 PM Page 78
Application
Message
Destination
Sending
Queue
Application
Specify
Administration
Queue
Administration
Queue
MSMQ
Acknowledgement
Message
MSMQ Architecture 79
Response queues are specified by the sending application and used by the
receiving application to send response messages back to the sending appli-
cation (see Figure 3.7).
EXAMPLE
Application
Message
Destination
Queue Application
Message
Sending Sending
Application Application
Response
Message
Specify
Response Response
Queue Queue
Report queues track the progress of messages as they move through the
enterprise. When the sending application enables tracking and specifies a
report queue, MSMQ sends report messages to the report queue. A report
message is a system message that is generated each time an application
message passes through an MSMQ routing server.
SYSTEM QUEUES
System queues are created either by MSMQ or the MSMQ administrator.
System queues contain journal queues and dead-letter queues. Whenever an
application queue is created, MSMQ automatically create a journal to track
EXAMPLE the messages that are removed from the queue. Dead-letter queues store
messages that could not be delivered. MSMQ provides two dead-letter
queues for each computer: one for nontransactional messages and the other
for transactional messages. Figure 3.8 shows system queues.
05 0789724588 CH03 10/25/00 5:07 PM Page 80
Messages
MSMQ messages are data exchanged between applications. Messages can
be generated by MSMQ applications or by MSMQ itself. This chapter
addresses only application-generated messages and some of their important
properties.
For each message, MSMQ generates and assigns a message identifier. The
identifier, or ID, of a message is unique on the computer where the message
resides and can be used along with other message properties to identify a
EXAMPLE
message. Figure 3.9 shows the property page of a message with its message
identifier highlighted.
The Label property of a message is used to describe the message, much like
the subject of an email. The Label of the message in Figure 3.9 is Testing.
Unlike an email message, however, the Body property of a message is not
limited to string data types. The body of a message is a variant data type.
It can be literally any data type, including string, date, numeric, currency,
or array of bytes. The body of a message can be a persistent object such as
an Excel spreadsheet or even an ADO recordset.
Journaling
MSMQ journaling allows you to keep track of messages. The two types of
MSMQ journaling are source journaling and target journaling. Source jour-
naling tracks messages sent by a computer, whereas target journaling
tracks messages removed from a queue.
MSMQQueueInfo
MSMQQueue
MSMQMessage
CAUTION
Depending in which directory you put the sample code of this chapter, when you load
the source code you may experience an error, “Could Not Create Reference….” If this
error occurs, you should reset the references to “Microsoft Message Queue 2.0 Object
Library” by select Project, References menu option. This object library is usually located
in “\WINNT\system32\MQOA.dll”.
05 0789724588 CH03 10/25/00 5:07 PM Page 83
In Listing 3.1, you use a Visual Basic conditional compilation constant that
you set on the Make tab of the project’s property page (see Figure 2.12).
This way, you can have a single code base to handle creating both public
and private queues.
The Open method of the MSMQQueueInfo object takes two parameters: Access
Mode and Shared Mode. Access Mode can be MQ_SEND_ACCESS,
MQ_RECEIVE_ACCESS, or MQ_PEEK_ACCESS. Shared Mode can be MQ_DENY_NOEN
(the default) or MQ_DENY_RECEIVE_SHARE. Note that you set the priority to 5
to overwrite the default priority (3). MSMQ puts a message with higher pri-
ority in front of a message with lower priority. MSMQ message priorities
range from 0 to 7. Also note that in the error handler, you test whether the
error was caused by trying to create an already existing queue; then you
ignore the error and continue execution of the next line of code. Figure 3.13
05 0789724588 CH03 10/25/00 5:07 PM Page 85
shows that the queue is created, and a testing message with a priority of 5
appears in the queue.
The next example, Listing 3.2), opens an existing queue, retrieves a mes-
sage from the queue, and prints the contents of the message (label and
body) in the debug window.
EXAMPLE Listing 3.2 Opening an Existing Queue and Receiving a Message
Public Sub ReceiveQueueMessage()
‘==================================================
‘In this sub routine, we open an existing queue
‘retrieve the message and print to debug window.
‘==================================================
‘Enable the error handler
On Error GoTo ReceiveQueueMessage_Err
‘Declare variables for MSMQ objects.
Dim oQInfo As MSMQ.MSMQQueueInfo
Dim oQueue As MSMQ.MSMQQueue
Dim oMessage As MSMQ.MSMQMessage
CAUTION
The code in Listing 3.2 will only work if there is a message in the queue. Otherwise you
will get an “Object variable or With block variable not set” error message. This is
because if there is no message in the queue, the ReceiveCurrent() will time out and
the next line tries to access the oMessage object which is set to Nothing.
In Listing 3.2, you use the Receive method of the MSMQQueue object.
Messages are removed from the queue after the Receive method is called.
This procedure is called dequeuing. Note that you use a Visual Basic named
argument syntax to specify the timeout value to one minute. Figure 3.14
shows the result.
The following example, Listing 3.3, shows you how to locate a public queue
that is registered in Active Directory and delete it if you find one.
Listing 3.3 Locating a Public Queue and Deleting It
EXAMPLE
Public Sub DeleteTestQueue()
‘==================================================
‘In this sub routine, we locate an pubic queue
‘in the Active Directory and delete it.
‘==================================================
‘Enable the error handler
On Error GoTo DeleteTestQueue_Err
‘Declare variables for MSMQ objects.
Dim oQuery As MSMQ.MSMQQuery
Dim oQInfos As MSMQ.MSMQQueueInfos
Dim oQInfo As MSMQ.MSMQQueueInfo
Dim oQueue As MSMQ.MSMQQueue
In Listing 3.2, you used the Receive method to read the message and
remove it from the queue. In Listing 3.4, you will use another technique to
read the message selectively and remove only certain messages that meet
certain criteria. Before you test the code in Listing 3.3, though, send two
messages to the queue. Send the first message by running the code in
Listing 3.1 without any modification. Then add .AppSpecific = 25 to
Listing 3.1 between the line .Priority = 5 ‘Default priority is 3 and
05 0789724588 CH03 10/25/00 5:07 PM Page 88
the line .Body = “Testing Message”. The code should now read as shown in
the following segment:
Public Sub SendQueueMessage()
‘==================================================
‘In this sub routine, we will create a queue, open
‘the queue and send a testing message to the queue.
‘==================================================
‘Code is omitted here, see listing 3.1 for details.
‘. . . . . .
‘Prepare the message and send to the queue.
With oMessage
.Label = “Testing Message”
.Priority = 5 ‘Default priority is 3.
.AppSpecific = 25
.Body = “Testing Message”
.Send oQueue
End With
‘The rest of the code is omitted, see Figure 3.1.
End Sub
Then run the modified code, and a message with the AppSpecific property
set to 25 is sent to the queue. Figure 3.15 shows the two messages sent to
the queue.
Listing 3.4 uses Peek methods (PeekCurrent and PeekNext) to search the
queue for specific messages that meek certain criteria without removing
them. If a specific message is found, the code will remove the message from
EXAMPLE the queue using the ReceiveCurrent method and also print the label and
body of the message in the Debug window.
Listing 3.4 Searching for Specific Messages to Remove from the Queue
Public Sub FilterMessages()
‘==================================================
‘In this sub routine, we open an existing queue
‘and selectively retrieve a message.
‘==================================================
‘Enable the error handler
05 0789724588 CH03 10/25/00 5:07 PM Page 89
After executing the code in Listing 3.4, you get results similar to those
shown in Figure 3.14. If you open the Computer Management snap-in, you
will notice that the second message you saw in Figure 3.15 is gone, as you
can see in Figure 3.16.
Listing 3.4 filters messages based on the AppSpecific property. You can also
use other message properties to look for specific messages. For example,
you can use the MsgClass property to filter out report messages. To do so,
simply change the line .AppSpecific = 25 in Listing 3.4 to
.MsgClass = MQMSG_CLASS_REPORT
Message
Message
Arrived
Event
Message Sender Message Receiver
(VB EXE Application) TestQueue (VB EXE Application)
The MultiLine property of the text box is better set to True so that it will
function more like a text editor.
Listing 3.5 contains the code for the Message Sender application.
Listing 3.5 The MSMQMsgSender Project
‘==================================================
EXAMPLE
‘This is a sample MSMQ message sender application.
‘It is paired with another MSMQ Receiver
‘application to demonstrate how MSMQ event works.
‘==================================================
Option Explicit
‘=================================================
‘The Change event of the text box tracks your key
‘stroke and sends a message to the TestQueue every
‘time when you press a key on the keyboard
‘=================================================
Private Sub txtMessage_Change()
‘Enable the error handler
On Error GoTo MessageSend_Error
‘Declare variables for MSMQ objects.
Dim oQInfo As New MSMQ.MSMQQueueInfo
Dim oQMsg As New MSMQ.MSMQMessage
Dim oQueue As MSMQ.MSMQQueue
#Else
oQInfo.PathName = “.\PRIVATE$\TestQueue”
#End If
‘===================================
‘The Click event of the Exit button.
‘===================================
Private Sub cmdExit_Click()
‘Exit the program.
Unload Me
End Sub
Listing 3.6 shows the code for the Message Receiver application.
Listing 3.6 The MSMQMessageReceiver Project
‘====================================================
EXAMPLE
‘This is a sample MSMQ message receiver application.
‘It is paired with the MSMQ Sender
‘application to demonstrate how MSMQ event works.
‘====================================================
Option Explicit
‘Declare some model level variables for MSMQ objects.
Dim oQInfo As New MSMQ.MSMQQueueInfo
Dim oQReceive As MSMQ.MSMQQueue
Dim WithEvents oQEvent As MSMQ.MSMQEvent
‘=========================================
‘The form load event then opens the
‘TestQueue and enables event notification.
‘=========================================
Private Sub Form_Load()
‘Enable error handler.
On Error GoTo Load_Err
‘Set the PathName of the queue.
#If bUseDS Then
oQInfo.PathName = “.\TestQueue”
#Else
oQInfo.PathName = “.\PRIVATE$\TestQueue”
#End If
‘Open the queue for receive access.
Set oQReceive = oQInfo.Open(MQ_RECEIVE_ACCESS, MQ_DENY_NONE)
‘Set the MSMQEvent object.
05 0789724588 CH03 10/25/00 5:07 PM Page 94
‘====================================
‘The Click event of the Exit button.
‘====================================
Private Sub cmdExit_Click()
‘Exit the program.
Unload Me
End Sub
‘=================================================
‘The Arrived event of the MSMQEvent object.
‘Whenever this event fires, we update the content
‘of the text box. Remember to enable the event
‘notification for ensuring the firing of the
‘subsequent events.
‘=================================================
Private Sub oQEvent_Arrived(ByVal Queue As Object, _
ByVal Cursor As Long)
‘Enable error handler.
On Error GoTo Event_Arrived_Err
‘Declare the MSMQMessage object.
Dim oQMsg As MSMQ.MSMQMessage
‘Retrieve the message and display its contents in the text box.
Set oQMsg = oQReceive.ReceiveCurrent(ReceiveTimeout:=1000)
txtMessage = oQMsg.Body
‘Important!!!---Enable event notification before exiting the event.
oQReceive.EnableNotification Event:=oQEvent, Cursor:=MQMSG_FIRST
Exit Sub
Event_Arrived_Err:
MsgBox Err.Description
End Sub
‘======================================================
‘The ArrivedError event of MSMQEvent object.
‘This event will be fired when the EnableNotification
‘of the message object is called and an error has
‘been generated. The ErrorCode is the return code
‘of the ReceiveCurrent call of the MSMQQueue object.
‘======================================================
05 0789724588 CH03 10/25/00 5:07 PM Page 95
In Listing 3.6, the Load event of the form opens the queue, initializes the
event object, and enables event notification. The Arrived event receives the
message, updates the content of the text box with the message, and enables
event notification before you exit the event procedure. To see how this list-
ing works, run two separate instances of the Message Sender and the
Message Receiver applications. Arrange the screens so that you can see
both of them at the same time. Notice that whenever you type something in
the text box of the Send application, its content also appears in the text box
of the Receiver application, as shown in Figure 3.20.
‘Declare variables.
Dim rsTitles As New ADODB.Recordset
Dim oQinfo As New MSMQ.MSMQQueueInfo
Dim oQueue As MSMQ.MSMQQueue
Dim oMessage As New MSMQ.MSMQMessage
Dim sConnection As String
Dim sSQL As String
oQinfo.PathName = “.\PRIVATE$\TestQueue”
#End If
Run this example, and click the Send Recordset button. A disconnected
ADO recordset is then placed on the TestQueue (see Figure 3.22).
NOTE
The size of the message on your machine may be a little different from the size you saw in
Figure 3.22.
If you then click the Read Recordset button, the recordset is dequeued, and
its contents are listed in the debug window (see Figure 3.23).
1. Order Data
(Message)
3. Event fires
OrderQueue when an order
message arrives
Ordering Order
Application Processor 4. Insert an
6. Event order into
fires when the orders
confirmation table in the
message database
5. Order Confirmation
arrives
Message
Order
Response
Queue
Orders Database
2. Specify Response Table
Queue
You can use the Computer Management snap-in to create the two queues
for this example: the OrderQueue and the OrderResponseQueue (see
Figure 3.25).
Figure 3.26 shows the asynchronous ordering system with the ordering
application on the left and the order processing application on the right.
When you fill up the order information on the form and click the Submit
Order button, the status of the ordering processing application briefly
changes to Processing order and then back to Ready. Depending on the
CPU speed and how much RAM you have on your machine, you may hardly
notice the status change. Soon a message box pops up and confirms that
your order (with an OrderID) is processed (see Figure 3.27).
Listings 3.9 and 3.10 provide the complete code for this application and
EXAMPLE reveal what’s happening behind the scenes.
05 0789724588 CH03 10/25/00 5:07 PM Page 102
‘=============================
‘General Declarations section
‘=============================
Option Explicit
‘Declare module level variables.
Dim oQinfoOrder As New MSMQ.MSMQQueueInfo
Dim oQInfoOrderResponse As New MSMQ.MSMQQueueInfo
Dim oQueueResponse As MSMQ.MSMQQueue
Dim WithEvents oQEvent As MSMQ.MSMQEvent
‘===========================
‘The Load event of the form
‘===========================
Private Sub Form_Load()
‘In the load event of the form, specify PathNames for
‘both OrderQueue and OrderResponseQueue.
On Error GoTo Load_Err
#If bUseDS Then
oQinfoOrder.PathName = “.\OrderQueue”
oQInfoOrderResponse.PathName = “.\OrderResponseQueue”
#Else
oQinfoOrder.PathName = “.\PRIVATE$\OrderQueue”
oQInfoOrderResponse.PathName = “.\PRIVATE$\OrderResponseQueue”
#End If
‘========================================
‘The Click event of the New Order button
‘========================================
05 0789724588 CH03 10/25/00 5:07 PM Page 103
‘===========================================
‘The Click event of the Submit Order button
‘===========================================
Private Sub cmdSubmit_Click()
On Error GoTo SubmitOrder_Err
Dim oQueue As MSMQ.MSMQQueue
Dim oMessage As New MSMQ.MSMQMessage
Dim sMessage As String
Screen.MousePointer = vbHourglass
‘Open the OrderQueue for send access and send the order
‘message to the queue.
Set oQueue = oQinfoOrder.Open(MQ_SEND_ACCESS, MQ_DENY_NONE)
sMessage = sMessage
With oMessage
.Label = “Order”
.Body = sMessage
‘Specify the response queue.
Set .ResponseQueueInfo = oQInfoOrderResponse
.Send oQueue
End With
oQueue.Close
Screen.MousePointer = vbDefault
Exit Sub
SubmitOrder_Err:
Screen.MousePointer = vbDefault
MsgBox Err.Description
End Sub
‘============================================
‘The Arrived event of the OrderResponseQueue
‘============================================
Private Sub oQEvent_Arrived(ByVal Queue As Object, ByVal Cursor As Long)
‘Display the response message when it arrives.
On Error GoTo Event_Arrived_Err
Exit Sub
Event_Arrived_Err:
05 0789724588 CH03 10/25/00 5:07 PM Page 105
MsgBox Err.Description
End Sub
‘=================================================
‘The ArrivedError event of the OrderResponseQueue
‘=================================================
Private Sub oQEvent_ArrivedError(ByVal Queue As Object, _
ByVal ErrorCode As Long, _
ByVal Cursor As Long)
MsgBox “Error event fired!” & vbCrLf & _
“Error: “ & Hex(ErrorCode)
End Sub
‘===================================
‘The Click event of the Exit button
‘===================================
Private Sub cmdExit_Click()
Unload Me
End Sub
‘=============================
‘General Declarations section
‘=============================
Option Explicit
‘Declare module level variables.
Dim oQinfoOrder As New MSMQ.MSMQQueueInfo
Dim oQueue As MSMQ.MSMQQueue
Dim WithEvents oQEvent As MSMQ.MSMQEvent
‘===========================
‘The Load event of the form
‘===========================
Private Sub Form_Load()
‘Listen to the event of the OrderQueue.
#If bUseDS Then
oQinfoOrder.PathName = “.\OrderQueue”
#Else
05 0789724588 CH03 10/25/00 5:07 PM Page 106
oQinfoOrder.PathName = “.\PRIVATE$\OrderQueue”
#End If
lblStatus = “Ready”
End Sub
‘=====================================
‘The Arrived event of the OrderQueue
‘=====================================
Private Sub oQEvent_Arrived(ByVal Queue As Object, ByVal Cursor As Long)
‘Process the order message when it arrives and
‘send a response message when the process is finished.
On Error GoTo Event_Arrived_Err
.CommandType = adCmdStoredProc
.CommandText = “PlaceOrder”
.Parameters.Append .CreateParameter(“@Order”, _
adVarChar, _
adParamInput, _
300)
.Parameters.Append .CreateParameter(“@OrderID”, _
adInteger, _
adParamOutput)
.Parameters(“@Order”) = sMessage
.Execute
iOrderID = .Parameters(“@OrderID”)
End With
Exit Sub
Event_Arrived_Err:
Screen.MousePointer = vbDefault
lblStatus = “Ready”
MsgBox Err.Description
End Sub
‘=========================================
‘The ArrivedError event of the OrderQueue
‘=========================================
Private Sub oQEvent_ArrivedError(ByVal Queue As Object, _
05 0789724588 CH03 10/25/00 5:07 PM Page 108
End Sub
‘===================================
‘The Click event of the Exit button
‘===================================
Private Sub cmdExit_Click()
Unload Me
End Sub
Now start the ordering application again. This time, you will see a confir-
mation message box. If you check the queues again, you will notice that no
messages appear in OrderQueue or OrderResponseQueue.
What’s Next
This chapter introduced MSMQ and showed you how to program MSMQ in
Visual Basic. The knowledge you learned will be essential for you to under-
stand important COM+ services, such as Queued Components (QC). In
Chapter 4, “Introduction to Visual Basic COM Programming,” you will
learn how to develop COM components in Visual Basic.
06 0789724588 CH04 10/25/00 4:58 PM Page 110
06 0789724588 CH04 10/25/00 4:58 PM Page 111
4
Introduction to Visual Basic COM
Programming
In previous chapters, you learned how to work with ADO and MSMQ
through their COM interfaces. In this chapter, you’ll learn how to build
your own COM components in Visual Basic to provide services to other
applications.
This chapter teaches you the following:
• Basic COM programming techniques
• How to create in-process servers
• How to design COM components
• Important COM design decisions
06 0789724588 CH04 10/25/00 4:58 PM Page 112
Now you are ready to set properties for the project and the classes.
PROJECT PROPERTIES
You can access the Project Properties sheet by choosing Project, Project1
Properties. Alternatively, you can just right-click Project1 in the Project
Browser and choose Project1 Properties. Either way, you go to the General
tab of the Project Properties sheet. I’ll describe only those project properties
that are important for ActiveX DLL components here.
Figure 4.3 shows the General tab with the project name COMDLLDemo.
The project name becomes the DLL filename after you compile your project.
In this case, you end up with a DLL file called COMDLLDemo.dll.
The information you put in the Project Description field will be displayed in
the References property sheet as one of the Available References (see Figure
4.4). It will also appear in the Object Browser (see Figure 4.5).
For the Threading Model option, you have only two choices: Single
Threaded and Apartment Threaded. As you learned in Chapter 1, Single
Threaded is the single-threaded model, whereas Apartment Threaded
means the STA model. For COM+ applications built in Visual Basic,
Apartment Threaded is the default threading model for ActiveX DLL
projects.
CAUTION
Visual Basic ActiveX DLL project uses a slightly different terminology than COM specifi-
cations to describe the threading models. The Single-Threaded threading option in a
Visual Basic ActiveX project corresponds to the COM single-threaded model. Apartment
Threading, however, corresponds to the COM STA model, not the MTA model, which can
be confusing.
Now look at the Component tab of the Project Properties sheet (see Figure
4.6). The Remote Server Files option in the Remote Server group generates
two extra files when you compile your ActiveX DLL: VBR and TLB files
with the same filenames as the DLL file. The VBR file contains information
for the Windows Registry to run the component on a remote machine, and
the TLB file is a separate type library file. If you don’t select this option,
the type library is embedded in the DLL file.
NOTE
A type library provides information about the interfaces, methods, properties, and
events of a component. Type library information is stored in the Windows Registry in
HKEY_CLASSES_ROOT under the TypeLib folder.
component. CLSIDs and IIDs are used to uniquely identify the classes and
interfaces of COM components. Type library IDs are also used by Visual
Basic to populate the Available References list of the References window
(refer to Figure 2.9 of Chapter 2, “Windows DNA 2000 and COM+”). Each
option in the Version Compatibility group has a different level of preserva-
tion of these GUIDs for each time you compile your components, as outlined
in Table 4.1.
As you can see from Table 4.1, setting the option to No Compatibility
breaks all the compatibilities of any existing client applications of your
component. Project Compatibility (the default option) preserves the type
library ID for your components when you are testing and debugging your
components, so you don’t have to refresh the reference to your component in
the client application each time you make minor changes to your compo-
nent and recompile it. However, because the CLSIDs and IIDs are re-cre-
ated each time you recompile the component, practically speaking, Project
Compatibility is no different from No Compatibility. The last option, Binary
Compatibility, preserves all the GUIDs for you (this feature is new in
Visual Basic 6.0). Therefore, you should make sure not to change the inter-
faces of your component (that is, classes, properties, methods, and all the
signatures) when you choose Binary Compatibility.
I recommend that, after you compile your component for the first time, you
set the option to Binary Compatibility (see Figure 4.6). Remember from
Chapter 1 that interfaces are immutable as soon as they are published.
Therefore, if you have to change the interfaces to meet some specific needs,
you should create new interfaces rather than change existing interfaces.
TIP
Figure 4.6 shows the physical pathname for the component file, which is the default
when you set the path through the Browse button. Change it to a relative path; that is,
use only the filename without the path. For example, use COMDLLDemo.dll instead of
D:\QUE\Chap04\Src\COMDLLDemo.dll. Using the relative path helps Visual Basic to
automatically locate the DLL file if you move the project source code to a different loca-
tion, as long as you put the DLL file in the same folder as the project file (the VBP file).
06 0789724588 CH04 10/25/00 4:58 PM Page 117
CLASS PROPERTIES
Chapter 1 introduced interfaces as the contracts between COM components
and the outside world. Classes are a concrete implementation of specific
interfaces. Objects are individual instances of classes. The relationship
between classes and objects is similar to the relationship between blue-
prints and houses. A blueprint defines the shape, dimensions, and different
elements of houses, whereas each individual house can have a specific
implementation of elements. For example, each house from the same blue-
print can have different colored walls and use different materials for the
windows. Similarly, each Employee object can have different attributes, such
as names, ages, hiring dates, and so on, that are defined by the Employee
class.
Some programming languages, such as C++ and Java, separate the inter-
face definition from the classes. They define the interface through the
Interface Definition Language (IDL), save it as a separate file, and then
compile the IDL file to generate source files. In Visual Basic, however, this
procedure is all handled behind the scenes.
Visual Basic uses class modules to define and implement classes. You can
define your classes in two ways. One way is to define abstract classes with-
out any implementations. Another way is to directly implement the inter-
faces using class modules. You’ll learn different ways of implementing
classes later in this chapter. For now, let’s focus on some important proper-
ties of class modules.
The Name property defines the identification of the class in a human-
readable manner. For this example, change the Name property of your class
in the COMDLLDemo project from Class1 to SayHi. Another important
class property is Instancing. The sample project is an ActiveX DLL, so you
can choose from four possible options for the Instancing property, as shown
in Figure 4.7.
The Hello() function takes one string input parameter, which is the name
of the person you want to greet. If you use an empty string, the function
returns the generic greeting message “Hello, there!”. Otherwise, it
returns a customized greeting message for a given name.
Save the project and compile the ActiveX DLL by choosing the File, Make
COMDLLDemo.dll menu option. Then set the Version Compatibility to
Binary Compatibility for the created DLL. Now you have a fully functional
COM component ready for use. Start a new Visual Basic Standard EXE
project and set a reference to the COM component you just created (see
Figure 4.8).
Put a command button on the form and double-click it to open the code
window. Now type the code in Listing 4.2.
Listing 4.2 Sample Client Code for Using the SayHi COM Component
Private Sub Command1_Click()
On Error GoTo SayHi_Err
Dim oSayHi As New COMDLLDemo.SayHi
MsgBox oSayHi.Hello(“Peishu”)
Set oSayHi = Nothing
Exit Sub
SayHi_Err:
MsgBox Err.Description
End Sub
Now press F5 to run the project. Click the command button to see a mes-
sage box with a greeting message returned from the Hello() function of the
SayHi component (see Figure 4.9).
06 0789724588 CH04 10/25/00 4:58 PM Page 120
Figure 4.9: Using the SayHi COM component in a Visual Basic client appli-
cation.
Interfaces Revisited
Chapter 1 introduced COM interfaces and discussed several important
interfaces, such as IUnknown. Earlier in this chapter, you learned that, in
Visual Basic, you can define and implement interfaces in two ways. This
section introduces more COM interfaces and shows you how to implement
interfaces in Visual Basic.
For this section, extend the SayHi example and create an abstract class that
defines an interface. To do so, start a new ActiveX DLL project. Set the pro-
ject properties as in Figure 4.12.
EXAMPLE
Change the name of Class1 to ISayHi and add a public function definition
for Hello(), as in Listing 4.3. Notice that no code appears inside the
Hello() function.
06 0789724588 CH04 10/25/00 4:58 PM Page 122
Listing 4.3 Defining the Hello() Function for the ISayHi Interface
Option Explicit
Compile the project to make a SayHi.dll and set its Version Compatibility
as Binary Compatibility. Now you have an abstract interface definition for
ISayHi without any concrete implementation of the function Hello(). Here,
you follow the naming convention using I as the prefix of the interface
name. Figure 4.13 shows the ISayHi interface in the OLE/COM Object
Viewer.
Now create two ActiveX DLL components that implement the ISayHi inter-
face. To do so, start a new ActiveX DLL project and name the project
SayHi1, set the project description to SayHi Type Library Implementation
1, and change Class1 to Hello1. Then set a reference to the ISayHi interface
(see Figure 4.14).
Open a code window; then, in the General Declarations section, type
Implements ISayHi. The Visual Basic Implements keyword makes the ISayHi
interface available in the Object drop-down list so that you can implement
specific functionalities. Click the Object drop-down list and select ISayHi.
Visual Basic then generates the skeleton code for the ISayHi_Hello() func-
tion. Type the code as highlighted in Figure 4.15.
Compile the project to create the SayHi1.dll. Similarly, create a SayHi2.dll
project as described in Table 4.3.
06 0789724588 CH04 10/25/00 4:58 PM Page 123
Property/Function Description
Project Name SayHi2
Project Description SayHi Type Library Implementation 2
Class Name Hello2
Implements ISayHi
Now you can start testing your interface and its implementations. Start a
new Standard EXE project and set references to the ISayHi interface and
the two COM DLLs you just created (see Figure 4.16).
Add two command buttons to the form and name them cmdSayHi1 and
cmdSayHi2, respectively.
Press F5 to run the application; then click the SayHi 1 button. The greeting
shown in Figure 4.17 then appears. Clicking the SayHi 2 button generates
the greeting shown in Figure 4.18. These messages match the different
implementations of the ISayHi interface in SayHi1.dll and SayHi2.dll. The
capability of implementing different behavior from the same interface is
called polymorphism, as introduced in Chapter 1.
06 0789724588 CH04 10/25/00 4:58 PM Page 125
If you carefully look at the code in Listing 4.5, you’ll notice that the syntax
for declaring and creating objects is a little different from what you used
before. You used the following syntax:
Dim oHello As SayHi.ISayHi
Set oHello = New SayHi2.Hello1
In either case, you create an object from the class that implements the
interface, not from the interface itself.
Another benefit of using abstract classes is that you can implement multi-
ple interfaces in your components so that they can evolve to meet new busi-
ness requirements over time.
<<Interface>>
IDispatch
AddRef()
Release()
QueryInterface()
GetTypeInfoCount()
GetTypeInfo()
GetIDsOfNames()
Invoke()
DUAL INTERFACE
The IDispatch interface provides a way for client applications to access the
functionality of a COM component. This interface is slow, however, because
it involves two functions, GetIDsOfNames and Invoke, to execute an actual
call. A dual interface provides a more efficient way of communicating
between a client application and the component. Figure 4.20 illustrates a
typical dual interface.
A dual interface is actually a custom interface that implements all the func-
tions of the IDispatch and IUnknown interfaces, as well as any functions
that are specific to the object you are building (see Figure 4.20). An object
that supports a dual interface allows different types of client applications to
access its functionalities by the most efficient means possible. When you’re
writing your objects in Visual Basic, you don’t explicitly implement the
IDispatch interface nor dual interfaces. When you compile your component
that contain the objects you built, Visual Basic automatically implements
the dual interfaces for you.
06 0789724588 CH04 10/25/00 4:58 PM Page 127
<<<<Dual Interfaces>>>>
AddRef()
Release()
QueryInterface()
GetTypeInfoCount()
GetTypeInfo()
GetIDsOfNames()
Invoke()
CheckIdentity()
CheckPermission()
PROPERTIES
Properties define the attributes of a class. In Visual Basic, properties are
implemented either as module-level public variables or as public property
procedures. As a COM developer, you should always implement properties
using property procedures instead of public variables so that a client appli-
cation can never directly access the data. In this way, you can have com-
plete control over how your data is accessed and manipulated.
To continue with the example, add a Name property and a new Greeting()
function to the COMDLLDemo component you started earlier in this chap-
ter. Add some code in the SayHi.cls class module, as shown in Listing 4.6.
EXAMPLE
Listing 4.6 Adding a Name Property and a New Greeting() Function to the SayHi.cls of
the COMDLLDemo Project
Option Explicit
Else
Greeting = “Hello, there!”
End If
Exit Function
Greeting_Err:
MsgBox Err.Description
End Function
METHODS
Methods define the behavior of a class. In Visual Basic, methods are imple-
mented by Public Function or Public Sub. Therefore, the Hello() and
Greeting() functions are two methods of the SayHi.cls class module.
So far, you have learned about public properties and methods. They can
both be accessed from client applications.
EVENTS
Using Visual Basic, you can add event notification functionality to your
class. When something interesting happens, your component can raise an
event so that the client application can implement an event handler to act
accordingly. To implement an event in your class, you need to take two
steps. First, you need to declare the event by using the Event keyword. The
syntax for declaring an event is
Event EventName(ParameterList)
In this HasName event, you define one string input parameter, sMessage.
Now insert one line of code inside the Greeting() function to raise the
event when the client sets the Name property (see Listing 4.8).
Listing 4.8 The Greeting() Function to Raise an Event
Public Function Greeting() As String
On Error GoTo Greeting_Err
If Len(m_sName) <> 0 Then
Greeting = “Hello, “ & m_sName & “!”
‘If sName parameter is passed, we rasie HasName event.
RaiseEvent HasName(“A name is passed as “ & m_sName & “.”)
Else
Greeting = “Hello, there!”
End If
Exit Function
Greeting_Err:
MsgBox Err.Description
End Function
06 0789724588 CH04 10/25/00 4:59 PM Page 130
Here, you simply pass a message to the client application, indicating that a
name has been passed in.
Now let’s see how to get the event in a client application. To do so, start a
new Standard EXE project and name it UseCOMEvent. Name the form
frmUseCOMEvent. Then add a command button to the form and name it
cmdGetEvent. In the General Declarations section, declare the SayHi object
by using the WithEvents keyword:
Option Explicit
Dim WithEvents oHello As COMDLLDemo.SayHi
Next, add some code inside the event procedure so that it looks like this:
Private Sub oHello_HasName(ByVal sMessage As String)
MsgBox sMessage
End Sub
Now double-click the command button and add the code in Listing 4.9.
Listing 4.9 Code for the Command Button of the Event Client Application
Private Sub cmdGetEvent_Click()
On Error GoTo GetEvent_Err
Set oHello = New COMDLLDemo.SayHi
oHello.Name = “Peishu”
MsgBox oHello.Greeting
Set oHello = Nothing
Exit Sub
GetEvent_Err:
MsgBox Err.Description
End Sub
06 0789724588 CH04 10/25/00 4:59 PM Page 131
Press F5 to run the application; then click the Get Event button. You get
two message boxes. The first one says A name is passed as Peishu, and
the second says Hello, Peishu!. If you comment out the fourth line of code
in Listing 4.9, oHello.Name, the event doesn’t fire, and you get only one
message box containing Hello, there!. This result is exactly what you
would expect because you put the RaiseEvent call only in the first part of
the If...Else...End If structure (refer to Listing 4.8).
In this section, you learned how to implement events in your COM compo-
nents and how to handle events in client applications. Here, the events are
processed synchronously. Chapter 10, “COM+ Events,” discusses how to
take advantage of the loosely coupled, asynchronous event services offered
by COM+.
Error Handling
So far, I haven’t really described error handling for developing Visual Basic
COM components. The error handler code in previous examples does noth-
ing more than display a message that describes what’s gone wrong when an
error happens. Unfortunately, these kinds of error messages are usually
nearly useless for the users of your applications. Now I’m going to introduce
a couple of useful error handling techniques that can make your COM com-
ponents more robust and more supportive for troubleshooting when things
do go wrong.
RAISING AN ERROR
The first method of handling errors in COM components is raising an error
in the error handler of the methods inside your component. You do so by
calling the Raise method of the Err object, as illustrated in the following
code segment:
ErrorHandler:
Err.Raise Number: = (10000 + vbObjectError), _
Source:= m_sObjectName, _
Description: = “Your customized error descriptions”
This technique is useful when you want to generate your own error for your
component and return a customized error message to the client application
that uses the component. Note that the Visual Basic constant
vbObjectError is added to the error number to guarantee that your error
doesn’t conflict with any preserved errors for Visual Basic.
This method of error handling is not suitable, however, for situations in
which you want to pass the exact error or errors back to the client applica-
tion. In this case, you need to use the exact error number in the Raise
method, as shown in the following example:
Err.Raise Number: = Err.Number, ......
06 0789724588 CH04 10/25/00 4:59 PM Page 132
If you add the vbObjectError constant to the error number, you end up gen-
erating completely irrelevant errors to the client application. For example,
the error code for OLE DB error General access denied error is
–2147493639. If you add the vbObjectError constant to the error number,
you get Overflow error (runtime error 6), which has nothing to do with the
actual error. Another downside of the raising error strategy is that it is not
adequate for returning comprehensive error information, such as the
Errors collection, back to the client application.
TIP
To run the code in Listing 4.10, you need to set a reference to “Microsoft ActiveX Data
Access Objects 2.5 Library” in the Visual Basic project.
Listing 4.10 Passing Errors Back to the Client Application as Output Parameters
Public Function GetResult(ByVal sConnection As String, _
ByVal sSQL As String, _
ByRef lRows As Long, _
ByRef sErrorMessage As String) As ADODB.Recordset
.ActiveConnection = oConn
.Open sSQL
‘Clean up.
On Error Resume Next
Set oConn = Nothing
Set rsGetResult = Nothing
‘Pass error to the client.
sErrorMessage = sErrDesc
Inside the error handler in Listing 4.10, notice that the error information is
also written into the Windows Event Log by calling the LogEvent method of
the App object.
06 0789724588 CH04 10/25/00 4:59 PM Page 134
In the client application, you can check the returned error string to decide
what to do if any errors occur:
If Len(sErrors) > 0 Then ‘Some error happens
‘Code for handling errors in client application.
End If
Figure 4.22: Setting the standard EXE project as the startup project in the
project group.
Notice that Project1 is in bold now, indicating that it’s the startup project.
Now you need to set a reference to the COM component. To do so, select
Project1 from the Project menu, choose References. Check COMDLLDemo
from the Available References list. Notice that the Location points to the
COMDLLDemo.vbp file instead of the compiled binary file
COMDLLDemo.dll. This way, you can debug the code of your component.
Also, the name of the project—COMDLLDemo—instead of the description—
Visual Basic COM DLL Demo Project—appears in the list. Name the form
frmCOMDLLDemoTest and set its caption to COMDLLDemo Test. Add a text box
on the form of the new project you just added and set its Name property to
txtName; then set the Text property to “”. Add a command button, set its
06 0789724588 CH04 10/25/00 4:59 PM Page 135
Figure 4.23: Setting a stop point in the Hello method of the SayHi class.
NOTE
You can also debug and test your COM component by starting a separate instance of
Visual Basic. Then follow the steps described here to set up a stop point in the code of
your component. You need to start the COM component project first by pressing Ctrl ¶
F5. Then you can start the testing project and test your component as you did in the
procedures discussed earlier.
In this and previous sections, you learned some basic debugging and error
handling techniques for developing COM components. Chapter 12, “More on
COM+ Programming,” covers more COM+ debugging and error handling
issues.
As you can see, the authors table has many columns that map the attrib-
utes of an author entity. Let’s design an Author class from this authors
table. Figure 4.26 is a UML class diagram of the Author class.
06 0789724588 CH04 10/25/00 4:59 PM Page 137
Author
ID
FirstName
LastName
Phone
Address
City
State
Zip
Contract
Add()
Delete()
Update()
FindBy()
The Author class in Figure 4.26 has many properties. It also has several
methods, such as Add, Delete, Update, and FindByID. Properties are usually
implemented by property procedures in combination with private module-
level variables. As you learned earlier in this chapter, classes that have
many properties are stateful (a property is considered a state of the class).
Stateful classes are not scalable. Therefore, you can remodel the Author
class as a stateless class, as shown in Figure 4.27.
Author
Add()
Delete()
Update()
FindBy()
When you’re designing a stateless class, you can pass all the information as
parameters to the methods. For example, you might design the Add method
of the Author class as follows:
EXAMPLE Public Function Add(ByVal sAuthorID As String, _
ByVal sFirstName As String, _
ByVal sLastName As String, _
ByVal sPhone As String, _
ByVal sAddress As String, _
ByVal sCity As String, _
ByVal sState As String, _
ByVal sZip As String, _
ByVal bContract As Boolean) As Boolean
06 0789724588 CH04 10/25/00 4:59 PM Page 138
Now let’s look at another example that models a database entity. Figure
4.28 is the table schema for the Orders table in the Northwind database.
A stateless Order class might look like the one in Figure 4.29.
Order
PlaceOrder()
CancelOrder()
Notice that the OrderID is not part of the input parameters because it is an
identity column in the database. An identity column in SQL Server is simi-
lar to the AutoKey field in an Access database. The value of the column is
system-generated.
File
Create()
Delete()
Save()
Update()
oMessage.Update
oMessage.Send showDialog:=False
oSession.Logoff
Exit Function
Send_Err:
App.LogEvent Err.Description
End Function
The Service class supports one method, Send. It takes three input parame-
ters and sends an email message through the CDO (MAPI) interface.
06 0789724588 CH04 10/25/00 4:59 PM Page 141
NOTE
Notice that instead of setting a reference to the CDO (MAPI) component library and
using the New keyword to create an object (an instance of the component) as in previ-
ous examples, you declare the variable as an Object data type and use a
CreateObject method. The former mechanism of instantiating objects is called early
binding, whereas the CreateObject method uses late binding to create the object.
Early binding uses either Vtable binding or dispID binding, depending on whether the
component supports dual interfaces. Late binding uses the IDispatch interface, so the
performance is relatively slower than early binding. The late binding, however, provides
more flexibility.
Now you can create a Standard EXE project to test Email.dll. Figure 4.32
illustrates the design time of the testing project.
CreateSessionAndLogon_Err:
MsgBox Err.Description
End Sub
Here again, you use the CreateObject method (late binding) to create an
instance of the Email.Access class. Figure 4.33 shows the result of running
the testing client application.
Granularity of Interfaces
When designing an interface, you decide which properties and methods the
interface should support. The process of grouping the properties and meth-
ods into a particular interface is called interface factoring. You can design
from a coarse interface that includes many methods and/or properties to a
fine interface that contains only a small set of methods and/or properties.
In most cases, fine interfaces have several advantages over coarse inter-
faces and are easier to evolve and maintain.
going to use your component. If your components are designed to run in the
middle tier, they need to be scalable. Stateless is the choice for this situa-
tion. If, on the other hand, you want to use your components on the client
side for temporarily cached data so that you don’t have to make round trips
to the database back end every time you want to look up something, you
should design a stateful class.
Passing Parameters
You may have noticed that in previous examples, parameters of methods
are declared either using ByVal or ByRef. When a parameter is defined
using ByVal, only a copy of the data is passed in the function, but the origi-
nal data is not affected. If you change the value of the parameter being
passed in, the original data inside the calling application is not changed. If
you define the parameters using ByRef, the actual memory address pointer
of the variable is passed in. Therefore, any changes by the function to the
variable are directly made to the original data. For this reason, the para-
meters defined using ByRef are called output parameters. Because of the
way ByRef parameters work, you should choose ByVal when possible to
avoid unnecessary overhead. Use ByRef only when you need the value to be
passed back to clients.
Scripting Clients
When you’re designing COM components for scripting clients, such as
Active Server Pages, you should take special care to ensure your compo-
nents behave as expected.
First, scripting languages such as Visual Basic Scripting Edition (or
VBScript) support only late binding, so you should design a default inter-
face for the scripting clients. That is, you should use class modules that
carry implementation instead of using abstract classes.
Second, scripting languages are not strongly typed; all the variables in
VBScripts are of the variant data type. So, when the parameters are
declared using ByRef, they should use the variant data type. If you use a
specific data type for a parameter that is declared as ByRef, you get a Type
mismatch error when calling from scripting languages such as VBScript in
ASP. For the parameters declared as ByVal, however, you can use specific
data types.
nents usually encapsulate data access code that is fine-tuned for specific
database back ends, whereas BLL components usually encapsulate busi-
ness rules that are application-specific. BLL components use the services
provided by the DAL components to access data stored in the Data Services
tier. This separation of DAL from BLL provides great flexibility for the sys-
tem to evolve. For example, if you want to migrate your database from one
RDBMS to another RDBMS, such as from Oracle to SQL Server, all you
need to do is fine-tune your DAL components. No BLL components are
affected. On the other hand, should your business rules change, you can
simply modify the corresponding BLL components but keep the DAL com-
ponents unaffected.
COM+ Components
There are several constraints for designing COM+ components such as
Queued Components (QC) and components for Loosely Coupled Events
(LCE). You’ll learn about these limitations later in this book when I discuss
these specific COM+ services. Queued Components will be discussed in
Chapter 9, “Queued Components.” COM+ events will be covered in Chapter
10, “COM+ Events.”
What’s Next
By now, you have covered enough background to get started building COM+
applications. As the last chapter of Part I of this book, Chapter 5, “Using
the Component Services MMC Snap-In,” will introduce you to the COM+
world by helping you get familiar with the graphic administration tool for
managing and administering COM+ applications.
06 0789724588 CH04 10/25/00 4:59 PM Page 145
07 0789724588 CH05 10/25/00 5:10 PM Page 146
07 0789724588 CH05 10/25/00 5:10 PM Page 147
5
Using the Component Services MMC
Snap-In
Windows 2000 provides an administrative tool, the Component Services,
that is implemented as a Microsoft Management Console (MMC) snap-in
for developers and system administrators to create, configure, and adminis-
ter COM+ applications.
This chapter teaches you the following:
• What COM+ applications are
• How to create a COM+ application
• How to delete a COM+ application
• How to configure a COM+ application
• How to deploy a COM+ application
• How to convert an existing MTS package into a COM+ application
07 0789724588 CH05 10/25/00 5:10 PM Page 148
NOTE
A COM+ application is roughly equivalent to an MTS package in Windows NT 4.0. A
COM+ application allows the COM components it contains (called configured compo-
nents) to receive a richer set of services provided by Windows 2000 Component
Services.
• Application proxies
• COM+ preinstalled applications
A server application runs in its own process and supports all COM+ ser-
vices. A library application, on the other hand, runs in the process of its
creator (the client). Library applications support only a subset of COM+
services, such as role-based security, but do not support more advanced
features, such as remote access or Queued Components (QC).
COM+ allows you to export application proxies. An application proxy is a
set of files containing registration information that allows a client to
remotely access a server application. When these application proxy files are
run on a client machine, they write all the information from the COM+
server application to the client machine. When this server-related informa-
tion, such as CLSIDs, ProgIDs, RemoteServerName, and marshaling infor-
mation, are written to the client machine, the server application can then
be accessed remotely from the client machine.
NOTE
A COM+ application proxy is roughly equivalent to an MTS client executable in Windows
NT 4.0. Both are exported from the server machine and run in the client machine to
provide proxy information for the server so that the client can access the server
remotely.
COM+ also includes a set of preinstalled applications that are used inter-
nally by Windows 2000 Component Services. These preinstalled COM+
applications, which include System Application, COM+ Utilities, and IIS
Utilities, are located under the COM+ Applications folder in the Component
Services snap-in. They are read-only and cannot be modified or deleted. You
can see these preinstalled COM+ applications in Figure 5.1.
The type library can be embedded in the compiled DLL file or a separate
type library file (a TBL file). COM components that are installed in a
COM+ application are called configured components. Those COM compo-
nents that are not installed in a COM+ application are called unconfigured
components. Most unconfigured components can be transformed into config-
ured components by integrating them into a COM+ application, provided
that they meet the requirements described previously.
07 0789724588 CH05 10/25/00 5:10 PM Page 151
After designing the COM components, you can compile them into ActiveX
DLLs and integrate them into a COM+ application and configure the appli-
cation. You can either create a new COM+ application and install the com-
ponents into the COM+ application you created, or you can install the
components into an existing COM+ application. In the following sections of
this chapter you’ll learn how to create, configure, and deploy COM+ appli-
cations.
Alternatively, you can open the Action menu and then select New,
Application.
Click Next on the Welcome screen to go to the Install or Create a New
Application screen (see Figure 5.4).
Figure 5.4: The Install or Create a New Application screen of the COM
Application Install Wizard.
The first button, Install Pre-Built Application(s), allows you to install pre-
built applications from an exported COM+ application file (an MSI file) or
an MTS package. You’ll learn about exporting and importing COM+ appli-
cations in the section “Deploying COM+ Applications,” and importing MTS
packages in the section “Converting MTS Packages into COM+
Applications” later in this chapter. For this example, choose Create an
Empty Application, which takes you to the next screen (see Figure 5.5).
Figure 5.5: The Create Empty Application screen of the COM Application
Install Wizard.
07 0789724588 CH05 10/25/00 5:10 PM Page 153
You can choose to create either a server application, which is the default
option, or a library application. I discussed the difference between a server
application and a library application earlier in this chapter, which is that a
server application runs as a separate process whereas a library application
runs at the process of the client application. Here, create a server applica-
tion. Type Que’s Sample COM+ Application in the Enter a Name for the
New Application text box and click Next. On the next screen (see Figure
5.6), you can set the application identity, in which you specify under which
account you want the COM+ application to run.
Figure 5.6: The Set Application Identity screen of the COM Application
Install Wizard.
You can specify Interactive User (the default option) or This User. The
Interactive User is whoever is logged on to the server computer. You can set
the identity to a specific user account by selecting This User and typing the
username and password for the account, which must be a domain user
account. Choosing Interactive User makes the Component Services avail-
able to applications running on remote computers under any user account,
but it requires someone to log on to the server computer. In contrast, This
User makes the Component Services available only to the user account you
specify, but this option does not require anyone to log on to the server
machine. I recommend that you select Interactive User in development
environments and choose This User in production environments. In this
way, the services will keep running even when no one actually logs on to
the server.
TIP
You or the system administrator can reconfigure the Application Identity attribute after
the COM+ application is deployed.
07 0789724588 CH05 10/25/00 5:10 PM Page 154
NOTE
The Set Application Identity screen is available only for server applications, not for
library applications.
For this example, choose the default, Interactive User, and click Next. Click
Finish on the last screen of the COM Application Install Wizard. Now you
have an empty COM+ application, Que’s Sample COM+ Application, as
shown in Figure 5.7.
After you create the empty COM+ application, you are ready to add compo-
nents into the application either by installing new components or importing
existing components.
You can also drag the ActiveX DLLs that contain the components you want
to install from Windows Explorer and drop them into the right pane of the
Component Services under the Components folder. Figure 5.10 shows what
the window looks like after COMDLLDemo.dll is dragged from Windows
Explorer and dropped into the Component Services.
In this example, the COMDLLDemo.dll has only one class: the SayHi class.
Therefore, you have only one component. If you install an ActiveX DLL that
has multiple classes, each class creates a corresponding component after
you install the ActiveX DLL into a COM+ application.
07 0789724588 CH05 10/25/00 5:10 PM Page 156
TIP
For an ActiveX DLL that contains multiple components (classes), you can delete only
the specific component and keep the rest of the components in the COM+ application.
You can also install two different components from the sample ActiveX DLL into two dif-
ferent COM+ applications by installing both components in both COM+ applications and
then deleting unwanted ones from corresponding COM+ applications. In this way, you
can install different components of a single ActiveX DLL file into different COM+ appli-
cations.
07 0789724588 CH05 10/25/00 5:10 PM Page 157
CAUTION
Be careful when you want to delete or reinstall a component from a COM+ application.
Make sure you fully understand all its interfaces, methods, classes, attributes, securi-
ties, and other settings, as well as the dependencies before you perform this task.
Click OK on the message box that confirms the ActiveX DLL has been suc-
cessfully registered.
Now that the component has been registered on the local machine, you can
import it into the sample COM+ application. From the Component
Services, right-click the Components folder under the COM+ application
and select New, Component. In the Import or Install a Component screen
(refer to Figure 5.9), click the second button, Import Component(s) That
Are Already Registered. On the next screen, Choose Components to Import,
highlight the component(s) that you want to import. For this example,
highlight the COMDLLDemo.SayHi component (see Figure 5.13). Click Next,
and you see a Thank You screen. Click Finish to finish importing COM+
components.
CAUTION
Importing a component into a COM+ application does not populate interfaces, meth-
ods, and marshalling information for the component. Therefore, if you plan to distribute
the COM+ application later, install rather than import components.
07 0789724588 CH05 10/25/00 5:10 PM Page 158
• Interface-level
• Method-level
Configuring Transactions
Chapter 6, “Writing Transactional Components,” provides a detailed
discussion of COM+ transactions. This section shows you to how to set
some important transaction attributes. You can set transaction attributes at
the component level. COM+ reads the attributes of your components at run-
time to determine the type of transaction service to provide.
To set the transaction attributes for the COMDLLDemo.SayHi component,
right-click the component in the Component Services and select Properties.
Then click the Transactions tab of the Properties sheet (see Figure 5.14).
EXAMPLE The five possible transaction attribute values shown in Figure 5.14 are
Disabled, Not Supported, Supported, Required, and Requires New. These
values correspond to the five possible settings of the MTSTransactionMode
property of a class module in a Visual Basic 6.0 ActiveX DLL project, as
shown in Table 5.1.
NOTE
When a component is created in Visual Basic and installed in a COM+ application, the
MTSTransactionMode property is read and converted into the transaction attribute
according to Table 5.1. You can always overwrite these predefined transaction attributes
of components later by using the Component Services.
The differences between Disabled and Not Supported are subtle. If you set
the transaction attribute of a component to Disabled, COM+ preserves the
transactional behavior of the component as the unconfigured COM compo-
nent; the object may or may not participate in the transaction of its caller.
If you set the transaction attribute to Not Supported (the default), however,
the object never participates in any transaction, regardless of the transac-
tional status of its caller.
When you set the transaction attribute to Supported, the object participates
in the transaction of the caller if one exists, but never starts its own trans-
action.
Setting transaction attributes to Required allows the object to participate
in an existing transaction or start a new transaction if there is no transac-
tion of the caller.
If the transaction attribute of a component is marked as Requires New, it
always starts a new transaction as the root, regardless of the transactional
status of the caller.
07 0789724588 CH05 10/25/00 5:10 PM Page 161
NOTE
COM+ does not support nested transactions. Therefore, when the object’s transaction
attribute is set to Requires New and a transaction exists in the caller, the object always
starts an independent, separate transaction.
Configuring Security
COM+ provides a number of security features to protect COM+ applica-
tions, including declarative or programmatic role-based security, authenti-
cation services, and impersonation/delegation. Chapter 8, “COM+
Securities,” covers COM+ security in detail. This section introduces you
to setting security attributes for your COM+ applications using the
Component Services.
Type the name SayHi Users in the Role dialog box and click OK. The SayHi
Users role is then added to the application (see Figure 5.16).
For the next step, you need to map user accounts to the role you defined. To
do so, expand the SayHi Users folder, right-click the Users folder, and then
select New, User. In the Select Users or Groups dialog box, select the users
or groups that you want to map to the roles defined. For this example,
select the Administrator user (see Figure 5.17). Click OK, and
Administrator is added to the SayHi Users role (see Figure 5.18).
07 0789724588 CH05 10/25/00 5:10 PM Page 162
After the roles have been defined (and optionally, user or group accounts
have been mapped to the roles), the next step is to assign the roles to com-
ponents, interfaces, and methods. To assign a role to your component, right-
click the COMDLLDemo.SayHi folder and select Properties. Next, click the
Security tab and select SayHi Users from the Roles Explicitly Set for
Selected Item(s) box (see Figure 5.19). Finally, click OK to assign the role to
the component.
07 0789724588 CH05 10/25/00 5:10 PM Page 163
In a similar manner, you can assign roles to the interfaces and their
methods.
As the last step for the role-based security to work, you need to enable the
authorization at the COM+ application level to let the role-based security
take place. To do so, right-click the COM+ application and select Properties.
Next, click the Security tab and check Enforce Access Checks for This
Application (see Figure 5.20). Then click OK.
As you can see in Figure 5.20, you can further specify the security levels at
which to perform access checking, either at the process level or component
level.
07 0789724588 CH05 10/25/00 5:10 PM Page 164
AUTHENTICATION LEVELS
Authentication is the process of verifying that someone is actually who he
or she claims to be. The six authentication levels are described in Table 5.2.
IMPERSONATION LEVELS
Impersonation is a process that verifies what degree of authority the appli-
cation grants other applications to use its identity when it calls them. Table
5.3 lists four impersonation levels.
it’s done with the object. Both MTS and COM+ provide a Just-in-Time (JIT)
activation mechanism to simplify the programming model and conserve the
server resources. The disadvantage of JIT is that there is an overhead each
time the object is activated. In most scenarios, however, the benefit of JIT
far overweighs the disadvantages.
COM+ also allows you to administratively control the JIT behavior. You can
turn on the JIT activation for a component. You can also enable the Auto-
done feature at the method level. When the Auto-done feature is enabled
for a given method, the object is automatically deactivated on method
return.
To enable JIT activation for the COMDLLDemo.SayHi component, right-click
the component in the Component Services and select Properties. Click the
Activation tab and select the Enable Just In Time Activation check box (see
EXAMPLE Figure 5.21).
NOTE
If a component is configured to support transaction—that is, its transaction attribute is
set to something other than Disabled or Not Supported—JIT activation is automatically
enabled, and you cannot disable it.
Now enable the Auto-done feature for the Hello method of the
COMDLLDemo.SayHi component. Expand the Interfaces, _SayHi interface
and the Methods folder. Then right-click the Hello method and select
Properties. On the General tab, select the Automatically Deactivate This
Object When This Method Returns check box and click OK (see
Figure 5.22).
07 0789724588 CH05 10/25/00 5:10 PM Page 166
Figure 5.22: Enabling the Auto-done feature for the Hello method.
CAUTION
When you enable Auto-done for a method, you are actually changing the default behav-
ior of both JIT activation and automation. Exercise this option with care.
Configuring Queuing
COM+ supports Queued Components (QC), which allows activation and
interaction with an object in an asynchronous manner. This is achieved by
using the Message Queuing services behind the scenes. You’ll learn more
about QC in Chapter 9, “Queued Components.” In this section, you’ll learn
how to enable queuing for a component.
To enable QC for the COMDLLDemo.SayHi component, first you need to set the
Queued attribute at the application level. To do so, right-click the sample
COM+ application and select Properties. Click the Queuing tab and select
EXAMPLE the Queued-This Application Can Be Reached by MSMQ Queues check box
(see Figure 5.23).
As soon as you click OK after specifying the Queued attribute at the appli-
cation level, COM+ internally creates several queues to support QC (see
Figure 5.24). You’ll learn about these queues in Chapter 6.
In addition to setting up the Queued attribute at the application level, you
also need to enable the Queuing property at the interface level. For this
example, select the _SayHi interface, right-click, and select Properties. Click
the Queuing tab and select the Queued check box (see Figure 5.25).
07 0789724588 CH05 10/25/00 5:10 PM Page 167
Figure 5.24: COM+ creates several queues for the application marked
Queued.
As you can see in Figure 5.26, you can choose between exporting as a
server application or an application proxy; both generate an MSI file and a
CAB file (see Figure 5.27). You can export an application proxy in a similar
manner.
The exported MSI file is a Windows Installer file. If you select to export as
a server application, the Windows Installer file contains all the information
about the Registry settings, DLLs, and type libraries describing the inter-
faces implemented by the COM+ application’s classes. If you choose to
export as an application proxy, the Windows Installer file contains all the
information for a client to access the server remotely.
You can install COM+ server applications or application proxies by using
the Component Services snap-in on computers that are running Windows
2000. To install COM+ server applications or application proxies on com-
puters that are not running Windows 2000, you need to make sure that
Windows Installer is installed on the target machine. You can download
Windows Installer from the Microsoft Web site at
http://www.microsoft.com/msdownload/platformsdk/instmsi.htm
What’s Next
In this chapter, you learned how to use the Component Services snap-in to
administer COM+ applications. In Chapter 11, “Administering COM+
Applications Programmatically,” you’ll learn how to programmatically
administer COM+ applications through a scriptable COM interface, the
COM+ Catalog. Chapter 6, “Writing Transactional Components,” will teach
you how to write components that support transactions.
08 0789724588 Part II 10/25/00 4:55 PM Page 171
Part II
6
Writing Transactional Components
Transaction support is a basic and important requirement for any serious
enterprise or Internet application. COM+ provides a rich set of transaction
services that greatly simplify transaction programming. Thus, developers
can spend more time solving business problems rather than writing com-
plex code to deal with difficult transaction management scenarios.
This chapter teaches you the following:
• Transaction fundamentals
• Distributed transactions
• COM+ automatic transaction services
• How to write a complete transaction application
09 0789724588 CH06 10/25/00 5:05 PM Page 174
Introduction to Transactions
The following sections discuss some basic transaction concepts. You begin
with the fundamentals of transactions, and then learn the ACID transac-
tion characteristics. You also learn about transaction managers and trans-
action monitors, and finally you learn about distributed transactions, 2-pc
transaction protocol, and MS DTC.
Transaction Basics
A transaction is a series of operations that are considered a unit of work
completed as a single, atomic operation. A transaction has only two possible
outcomes: either all the work is completed successfully, or none of it is.
Transferring money between two bank accounts is a classic example of a
transaction. For instance, you transfer $100 from your checking account to
your savings account. Two individual operations are involved in this trans-
action. The first operation is to take $100 from your checking account. The
second operation is to add $100 to your savings account. Each operation is a
possible point of failure. If one operation fails, the other cannot succeed
either. Otherwise, either you or the bank will be unhappy. You don’t want
the transaction to succeed in the first operation and fail in the second—that
is, $100 be taken from your checking account without being added to your
savings account. The bank doesn’t want to see the opposite result—that is,
$100 added to your savings account without it being removed from your
checking account.
ACID Properties
A successful transaction must be atomic, consistent, isolated, and durable.
These attributes are known as the ACID properties of a transaction.
For a transaction to be atomic, it must execute exactly once. Either all the
operations involved in the unit of work are done, or none of them are done.
The states of data before and after a transaction must be consistent. In the
money transfer example, the account balances must be consistent, regard-
less of the output of the transaction. That is, if the transaction succeeds,
both accounts should have new balances. Should the transaction fail, none
of the account balances should be changed. Maintaining the consistency of
data is usually the responsibility of the application developer.
Individual transactions should be isolated from one another. None of the
transactions should see incomplete results of other transactions. Most
transaction managers achieve transaction isolation through serialization;
that is, individual transactions are are handled one by one in series.
09 0789724588 CH06 10/25/00 5:05 PM Page 175
DATABASE SERVERS
Most Relational Database Management Systems (RDBMS), such as
Microsoft SQL Server, offer transaction processing capabilities. For exam-
ple, the following code segment is a Transact SQL (TSQL) statement in
Microsoft SQL Server that groups two UPDATE statements into a single
transaction, starting with the statement BEGIN TRANSACTION. If everything
goes fine—that is, the error number (@@error) is zero—we issue a COMMIT
TRANSACTION statement to complete the transaction. Should anything go
wrong, we call the ROLLBACK TRANSACTION statement to abort the tran-
saction:
/*******************
Begin transaction
*******************/
BEGIN TRANSACTION
/*******************
Update statements
********************/
UPDATE Orders
SET Status = “Shipped”
WHERE OrderID = @OrderID
UPDATE Shipment
SET ShipDate = getdate()
WHERE OrderID = @OrderID
/***************************************************
In case error occurred, roll back the transaction,
Otherwise, commit the transaction.
***************************************************/
09 0789724588 CH06 10/25/00 5:05 PM Page 176
IF @@error = 0
COMMIT TRANSACTION
ELSE
ROLLBACK TRANSACTION
As you can see in the preceding code segment, if no error occurs (the
@@error global variable is zero), the transaction is committed; otherwise,
the transaction is aborted (or rolled back).
Distributed Transactions
Transactions in which more than one database or machine is involved are
called distributed transactions. Distributed transactions share the same
basic requirements as normal transactions that involve a single database.
Managing a distributed application is more complicated than managing a
normal transaction because it involves coordinating all the participating
databases and resources. The overall output of a distributed transaction
must be consistent for all involved parties. The following sections discuss
the two-phase commit protocol (2-pc) and how the 2-pc protocol is used to
manage distributed transactions. We will also introduce the Microsoft
Distributed Transaction Coordinator (MS DTC).
09 0789724588 CH06 10/25/00 5:05 PM Page 177
Transactional Components
A component whose transactional attribute is configured to support trans-
actions is called a transactional component. As you learned in Chapter 5,
this means that the transaction attribute for a transactional component
must be set to one of the following values: Supported, Required, or Requires
New. COM+ checks the transaction attribute of a component before activat-
ing an object from the component.
A component that doesn’t support transactions usually never participates in
a transaction. This type of component does not benefit from the transaction
protection provided by COM+ but still incurs the overhead of context cre-
ation (a process in which COM+ creates an associated context object when
creating initializing your COM object. I will explain the context object in
the next section. When you don’t want your component to participate in
transactions but want to use the COM+ framework, such as location trans-
parency of, you should set the transaction attribute of your component to
Disabled to avoid the overhead of context creation. You’ll learn why and
when to use nontransactional components later in this chapter in the
sample application.
done bits at the same time by calling the methods of the IObjectContext
interface, as described in Table 6.1.
Table 6.1 Setting Consistent and Done Bits Through the Methods of the
IObjectContext Interface
Method Description
SetDeactivateOnReturn Sets the done bit
SetMyTransactionVote Sets the consistent bit
GetDeactivateOnReturn Returns the Boolean value stored in the done bit
GetMyTransactionVote Returns the value associated with the consistent bit
Application Scope
For this example, you’ll build an ordering application that utilizes the
Northwind sample database that shipped with Microsoft SQL Server 7.0.
If the Northwind database has not been installed yet on your system,
use the Query Analyzer to run the SQL script file located at
<Your SQL Server root dir>\MSSQL7\install\instnwnd.sql.
09 0789724588 CH06 10/25/00 5:05 PM Page 180
ASSUMPTIONS
For this example, you can make the following assumptions:
• Northwind Trade, Inc., has an order processing center at which its
employees take calls or faxes from customers. The employees will use
the application you are going to build here to place an order into the
system.
• Other applications take care of other aspects of the system, such as
adding or modifying customer information and managing products,
suppliers, and shippers. So, this application will be solely responsible
for placing orders of existing products for existing customers.
• Employees who take calls from customers can order only one product
at a time. They can order more than one of that particular product at
a time, though.
BUSINESS RULES
This sample application employs a couple of simple business rules:
• The quantity on the order must be less than or equal to the quantity
in stock.
• After an order is placed, the quantity in stock (the UnitsInStock col-
umn) should be deducted from the Products table (refer to Figure 6.1).
Application Architecture
For the purposes of this chapter, you’ll design the Northwind ordering
application as a three-tiered architecture in which the application is
divided into three distinct logic tiers: a user services tier, a business services
tier, and a data services tier.
Select a product
Select a customer
Select a shipper
Northwind Employee
Select an employee
Place an order
Figure 6.3: The user services tier of the Northwind ordering application.
As shown in Figure 6.3, the graphical user interface (GUI) consists of two
sections (frames): Order Info and Shipping Info. Most data input is gath-
ered through text boxes. This interface also contains four drop-down combo
boxes for the user to select the product, customer, shipper, and employee
who processes the order.
object can be used to update the quantity in stock, and the Access (Data)
object is a generic data access wrapper that internally uses ActiveX Data
Objects (ADOs) to interact with the database.
Depending on the manner in which data interacts with the COM objects,
they may have different transactional requirements or might not partici-
pate in the transaction at all.
The Lookup object’s primary function is to retrieve data from the database
and return a disconnected ADO recordset for the client application for local
lookups. These functions do not involve any transactions. Therefore, the
Lookup object is better designed as a nontransactional component.
The Order object uses the Access object to place an order in the database
and uses the Product object to update the stock quantity. The Product
object, in turn, uses the Access object to persist the updates to the data-
base. As you can see, the Order object is the root of the transaction. Its
transaction attribute should be set to Required; that way, if the caller of the
Order object has a transaction, the Order object is enlisted into the existing
transaction. In the sample application, the caller does not have a transac-
tion, so the Order object always starts a new transaction. The Product and
Data objects are not the root objects, so their transaction attributes can be
set to Supported. If the caller has a transaction, it is enlisted into the exist-
ing transaction. In this case, the caller of the Product object is the Order
object, and the caller of the Access object is either the Product object or the
Order object. All the transactional COM components are hosted by the
COM+ runtime environment.
Figure 6.4 defines the transaction boundary and attributes of the sample
application.
Transaction Boundary
Product Object
(Supported)
Northwind
Database
Figure 6.4: The transaction boundary of the sample application and the
transaction attributes of the COM objects.
Implementation
The following sections will walk you through the code for all the stored pro-
cedures, COM objects, and the GUIs of the sample application.
Listing 6.2 shows the Customers_Get stored procedure, which returns a list
of customers and their addresses.
09 0789724588 CH06 10/25/00 5:05 PM Page 186
Listing 6.3 shows the Employees_Get stored procedure, which returns a list
of employees.
Listing 6.3 The Employees_Get Stored Procedure
EXAMPLE
/***************************************
* Name: Employees_Get *
* Purpose: Returns a list of Employees *
***************************************/
if exists(select * from sysobjects where id =
object_id(‘Employees_Get’))
drop proc Employees_Get
go
Listing 6.4 shows the Shippers_Get stored procedure, which returns a list of
shippers.
09 0789724588 CH06 10/25/00 5:05 PM Page 187
Listing 6.5 shows the Orders_Add stored procedure, which inserts a row in
the Orders table. This stored procedure is called by another stored proce-
dure, Order_Place.
EXAMPLE Listing 6.5 The Orders_Add Stored Procedure
/*************************************************************
* Name: Orders_Add *
* Purpose: Add an order to the Orders table. *
* Remarks: It is called by stored procedure Order_Place. *
* The output parameter @OrderID is returned to the *
* caller for subsequent insert to the Order Details *
*************************************************************/
if exists(select * from sysobjects where id = object_id(‘Orders_Add’))
drop proc Orders_Add
go
@ShipPostalCode nvarchar(10),
@ShipCountry nvarchar(15)
as
set nocount on
insert Orders (
CustomerID,
EmployeeID,
OrderDate,
RequiredDate,
ShippedDate,
ShipVia,
Freight,
ShipName,
ShipAddress,
ShipCity,
ShipRegion,
ShipPostalCode,
ShipCountry
)
values (
@CustomerID,
@EmployeeID,
@OrderDate,
@RequiredDate,
@ShippedDate,
@ShipVia,
@Freight,
@ShipName,
@ShipAddress,
@ShipCity,
@ShipRegion,
@ShipPostalCode,
@ShipCountry
)
Listing 6.6 shows the OrderDetails_Add stored procedure. This stored proce-
dure is also called by the Order_Place stored procedure after inserting a
row into the Orders table. The output parameter returned by the
EXAMPLE
Orders_Add stored procedure is used as the input parameter when calling
this stored procedure.
09 0789724588 CH06 10/25/00 5:05 PM Page 189
Listing 6.7 shows the Order_Place stored procedure, which in turn calls two
other stored procedures, Orders_Add and OrderDetails_Add. This stored pro-
cedure uses the transaction to protect data integrity.
EXAMPLE Listing 6.7 The Order_Place Stored Procedure
/******************************************************************
* Name: Order_Place *
* Purpose: Places an order. *
* Remarks: This stored procedure places an order by calling *
* two stored procedures, Orders_Add and OrderDetails_Add *
* The execution of the two stored procedures is wrapped *
09 0789724588 CH06 10/25/00 5:05 PM Page 190
@UnitPrice = @UnitPrice,
@Quantity = @Quantity,
@Discount = @Discount
if @@error = 0
commit tran
else
rollback
go
Our business rule #1 states that the quantity you try to order must
not be greater than the units in stock
********************************************************************/
if @UnitsInStock < @Quantity
raiserror (‘You tried to order more items than we have in stock.’,16,1)
update Products
set UnitsInStock = UnitsInStock - @Quantity
where ProductID = @ProductID
go
COM COMPONENTS
As you saw in Figure 6.5 earlier, the Data.Access object (class) is used by
all other objects, transactional (such as Northwind_Order.Order and
Northwind_Product.Product) and nontransactional (the Northwind_biz.
EXAMPLE Lookup object). The Data.Access object has two methods: GetResults() and
ExecSQL(). The GetResults() method returns a disconnected ADO recordset
to the caller, and the ExecSQL() method executes a SQL statement or stored
procedure. The transaction attribute of the Data.Access component is set to
Supported. From Visual Basic, you set the MTSTransactionMode property to
3-UsesTransaction (see Figure 6.6).
Figure 6.6: Setting the MTSTransactionMode property for the Access class.
In the Visual Basic project, you also need to set a reference to the COM+
Services Type Library, as shown in Figure 6.7.
09 0789724588 CH06 10/25/00 5:05 PM Page 193
‘******************************************************************************
‘ File: Data.vbp
‘ Object: Access
‘ Purpose: Generic data access object.
‘ Properties: ExecSQL() -- Execute SQL statements and/or stored procedures.
‘ GetResult() -- Get a recordset from the database.
‘******************************************************************************
‘******************************************************************************
‘ ExecSQL()
‘ Purpose: Execute SQL statements and/or stored procedures.
‘ Inputs: sConnection -- ADO connection string.
‘ sSQL -- SQL statement.
‘ Returns: A boolean variable indicates if the action is successful.
‘ Modification History
‘ Date Initials Description
‘ 5/8/00 PLI Created.
‘******************************************************************************
‘Clean up.
If Not oConnection Is Nothing Then Set oConnection = Nothing
End Function
‘******************************************************************************
‘ GetResult()
‘ Purpose: Get an ADO recordset from a SQL action.
‘ Inputs: sConnection -- ADO connection string.
‘ sSQL -- SQL statement.
‘ Outputs: lRows -- Rows returned.
‘ Returns: An ADO Recordset as a result of a SQL action.
‘ Modification History
‘ Date Initials Description
‘ 5/8/00 PLI Created.
‘*****************************************************************************
.LockType = adLockBatchOptimistic
.ActiveConnection = oConn
.Open sSQL
‘Clean up.
On Error Resume Next
Set oConn = Nothing
Set rsGetResult = Nothing
End Function
09 0789724588 CH06 10/25/00 5:05 PM Page 197
‘******************************************************************************
EXAMPLE
‘ File: Northwind_biz.vbp
‘ Object: Lookup
‘ Purpose: A lookup object that returns a disconnected ADO recordest for
‘ local offline lookup.
‘ Properties: GetProduct() -- Returns a Product recordset.
‘ GetCustomer() -- Returns a Customer recordset.
‘ GetShipper() -- Returns a Shipper recordset.
‘ GetEmployee() -- Returns an Employee recordset.
‘******************************************************************************
‘******************************************************************************
‘ GetProduct()
‘ Purpose: Calls the GetResult() method of the Data.Access object and returns
‘ a disconnected ADO recordset containing products.
‘ Inputs: sConnection -- ADO connection string.
‘ Returns: A disconnected ADO Recordset.
‘ Modification History
‘ Date Initials Description
‘ 6/8/00 PLI Created.
‘******************************************************************************
Exit Function
GetProduct_Err:
‘clean up
If Not rsProduct Is Nothing Then
Set rsProduct = Nothing
End If
Err.Raise Err.Number
End Function
‘******************************************************************************
‘ GetCustomer()
‘ Purpose: Calls the GetResult() method of the Data.Access object and returns
‘ a disconnected ADO recordset containing products.
‘ Inputs: sConnection -- ADO connection string.
‘ Returns: A disconnected ADO Recordset.
‘ Modification History
‘ Date Initials Description
‘ 6/8/00 PLI Created.
‘******************************************************************************
Exit Function
GetCustomer_Err:
‘clean up
If Not rsCustomer Is Nothing Then
09 0789724588 CH06 10/25/00 5:05 PM Page 199
‘******************************************************************************
‘ GetEmployee()
‘ Purpose: Calls the GetResult() method of the Data.Access object and returns
‘ a disconnected ADO recordset containing products.
‘ Inputs: sConnection -- ADO connection string.
‘ Returns: A disconnected ADO Recordset.
‘ Modification History
‘ Date Initials Description
‘ 6/8/00 PLI Created.
‘******************************************************************************
Exit Function
GetEmployee_Err:
‘clean up
If Not rsEmployee Is Nothing Then
Set rsEmployee = Nothing
End If
Err.Raise Err.Number
End Function
‘******************************************************************************
‘ GetShipper()
‘ Purpose: Calls the GetResult() method of the Data.Access object and returns
‘ a disconnected ADO recordset containing products.
‘ Inputs: sConnection -- ADO connection string.
09 0789724588 CH06 10/25/00 5:05 PM Page 200
Exit Function
GetShipper_Err:
‘clean up
If Not rsShipper Is Nothing Then
Set rsShipper = Nothing
End If
Err.Raise Err.Number
End Function
‘******************************************************************************
EXAMPLE
‘ File: Northwind_Product.vbp
‘ Object: Product
09 0789724588 CH06 10/25/00 5:05 PM Page 201
‘******************************************************************************
‘ UpdateStock()
‘ Purpose: Deducts the units in stock from the quantity ordered.
‘ Inputs: iProductID -- The id of the product ordered.
‘ iQuantity -- the quantity of the product ordered.
‘ Returns: A Boolean value indicating the success or failure of the action.
‘ Modification History
‘ Date Initials Description
‘ 6/8/00 PLI Created.
‘******************************************************************************
The last COM object discussed here is the root object in the entire order-
placing transaction: the Northwind_Order.Order object. Because it’s the root
of the transaction, its transaction attribute is set to Required. The
Northwind_Order.Order object has a single method, PlaceOrder(). The
PlaceOrder() method calls the ExecSQL() method of the Data.Access
method, executing the stored procedure Order_Place to place an order. It
then calls the UpdateStock() method of the Northwind_Product.Product
object to deduct the quantity in stock. If either method call fails, the entire
transaction is aborted. No order is placed, and no quantity in stock is
deducted. Listing 6.12 shows the code of the Northwind_Order.Order class.
Listing 6.12 The Northwind_Order.Order Class
Option Explicit
‘******************************************************************************
EXAMPLE
‘ File: Northwind_Order.vbp
‘ Object: Order
‘ Purpose: Used in conjunction with the Northwind_Product.Product object
‘ to Place an order and Update the quantity of units in stock of
‘ the Products table.
‘ Properties: PlaceOrder() -- Places an order and updates the units in stock
‘ for the product ordered.
‘******************************************************************************
‘******************************************************************************
‘ PlaceOrder()
‘ Purpose: Places an order and deducts the units in stock from the quantity
‘ ordered.
‘ Inputs: iProductID -- The id of the product ordered.
‘ cUnitPrice -- the unit price of the product ordered.
‘ iQuantity -- the quantity of the product ordered.
‘ sCustomerID -- the id of the customer who placed the order.
‘ iEmployeeID -- the id of the employee who processed the order.
‘ dOrderDate -- the date when the product is ordered.
‘ dRequiredDate -- the date when the product is required.
‘ dShippedDate -- the data when the product is shipped.
‘ iShipVia -- the id of the shipper.
‘ cFreight -- the freight.
‘ sShipName -- the shipping name.
‘ sAddress -- the shipping address.
‘ sCity -- the shipping city.
09 0789724588 CH06 10/25/00 5:05 PM Page 203
Now you can create an empty COM+ application called Northwind and
install the three components that support transactions: Data.Access,
Northwind_Order.Order, and Northwind_Product.Product. The results are
shown in Figure 6.8.
Notice that the transaction attributes you set in Visual Basic are automati-
cally picked up by the COM+ application after you install the components.
Listing 6.13 shows the general declaration section of the form. Two groups
of module-level variables are declared. The four ADO recordset objects hold
the disconnected recordset by the methods of the Northwind_biz.Lookup
EXAMPLE object. The second group of module-level variables tracks the IDs behind
the scenes.
Listing 6.13 The General Declaration Section of the Order Form
Option Explicit
‘Module level variables for holding disconnected
‘ADO recordsets as local data cache for lookups.
Private m_rsProduct As New ADOR.Recordset
Private m_rsCustomer As New ADOR.Recordset
Private m_rsEmployee As New ADOR.Recordset
Private m_rsShipper As New ADOR.Recordset
The Load event procedure of the form initializes the module-level recordset
objects, populates the combo boxes on the form, and provides other basic
information (see Listing 6.14).
Listing 6.14 The Load Event of the Order Form
Private Sub Form_Load()
On Error GoTo Load_Err
m_rsShipper.MoveFirst
m_rsShipper.Find “CompanyName = ‘“ & cbShipper.Text & “‘“
iShipperID = m_rsShipper!ShipperID
The GUI behavior is managed by the Change events of the text boxes, the
Click events of the combo boxes, and so on. It is worthwhile to mention the
code behind the Click event of the four combo boxes. These combo boxes
hold lists of products, customers, shippers, and employees for fast lookups.
Disconnected ADO recordsets are used for this purpose. The Clickevents of
the combo boxes use the Find method of the ADO recordsets to navigate the
disconnected recordsets and populate other text boxes and so on. Listing
6.15 shows the code of the events that maintain the GUI behavior.
Listing 6.15 Control Events That Maintain the GUI Behavior
Private Sub txtFreight_Change()
txtTotal = Val(txtSubTotal) + Val(txtFreight)
End Sub
EXAMPLE
Notice that the Click events of the combo boxes call a ConvertQuote() func-
tion. This helper function handles the embedded single quote in the search
criteria. Listing 6.16 shows the ConvertQuote() function.
09 0789724588 CH06 10/25/00 5:05 PM Page 209
The Click event of the Place an Order command button, shown in Listing
6.17, triggers the ordering process. It calls the PlaceOrder() method of the
Northwind_Order.Order object to place the order.
Listing 6.17 The Click Event of the Place an Order Command Button
Private Sub cmdOrder_Click()
On Error GoTo cmdOrder_Err
Screen.MousePointer = vbHourglass
Dim oOrder As Object ‘Northwind_Order.Order object
Set oOrder = CreateObject(“Northwind_Order.Order”)
oOrder.PlaceOrder iProductID, CCur(txtUnitPrice.Text),
CInt(txtQuantity.Text),
sCustomerID, iEmployeeID,
CDate(txtOrderDate.Text), _
CDate(txtRequiredDate.Text),
CDate(txtShippedDate.Text), _
iShipperID, CCur(txtFreight.Text),
cbCustomer.Text, _
txtAddress.Text, txtCity.Text, txtRegion.Text, _
txtPostalCode.Text, txtCountry.Text
Exit Sub
cmdOrder_Err:
Screen.MousePointer = vbDefault
MsgBox Err.Description
End Sub
09 0789724588 CH06 10/25/00 5:05 PM Page 210
In my case, the Query Analyzer returns 11077 as the maximum order ID.
Your case may vary, depending on your database, but that’s not important.
Now check one more thing before getting started. Type the SQL statements
shown in Figure 6.9 and press Ctrl+D and then Ctrl+E. The first key com-
bination configures the Query Analyzer so that the results will be displayed
in a grid view, as shown in Figure 6.9.
The query returns the product name as “Uncle Bob’s Organic Dried Pears”
and shows that 15 units are in stock.
Now that you’ve set your baseline, you’re ready to take an order. For this
example, select Uncle Bob’s Pears as the product and order 10 units. Then
select Consolidated Holdings as your customer. Notice that as soon as you
select a new customer from the drop-down list, the address information is
automatically changed to reflect the correct address for the customer
selected. This process is done entirely offline by searching the disconnected
recordset, as you learned earlier. Next, pick Speedy Express as the shipper
and assume Andrew Fuller is the Northwind employee who is going to
place the order. Charge $15 for the freight, so this brings the total price of
the order to $315. Now click the Place an Order button, and a confirmation
message box will appear shortly, indicating that the order has been
processed (see Figure 6.10).
09 0789724588 CH06 10/25/00 5:05 PM Page 211
Now check the database to make sure the order is processed as you
expected. Run the SQL statement with a subquery like the one in Figure
6.11. When you do, the lower part of the Query Analyzer window displays
the results that confirm the order was processed correctly.
You also need to make sure that the second business rule is followed; that
is, the 10 units of Uncle Bob’s pears should be deducted from the Products
table. To do so, run the query in Figure 6.9 again, and this time it shows
that 5 units are left in stock (see Figure 6.12). Everything is going fine
so far.
Now keep everything the same on the ordering form and click the Place an
Order button again. This time, you get an error message saying that you
tried to order more items than are available in stock (see Figure 6.13).
Figure 6.13: A message box indicates that you can’t order more items than
the number of units in stock.
So what happened? You tried to order 10 units again, but only 5 units were
left, so this is a violation of the first business rule. In this case, the stored
procedure raises an error and causes the COM object to call SetAbort to
terminate the transaction. If you check the Orders table at this point, you
will notice that the maximum order ID is still 11078. No new orders are
placed.
Conclusions
The preceding sections demonstrated a complete sample application that
uses COM+ automatic transaction services. You first learned about the
three-tiered architectural design, which is the foundation of Windows DNA,
as introduced in Chapter 2, “Windows DNA 2000 and COM+.” Keep in
mind that this is a logical architecture. You can physically deploy the appli-
cation in a single machine (as you did in the sample) or distribute different
tiers and components to multiple machines across the network, which is
usually the case in a real-world scenario. You should always test, bench-
mark, and stress-test different deployment options to achieve the maximum
possible performance as well as scalability.
You also learned how to implement business rules by using both SQL
stored procedures and COM objects. Stored procedures can also implement
transactions, as you learned by using the Order_Place stored procedure.
This approach can sometimes simplify transaction management. The bot-
tom line is that there are always different solutions to a problem domain.
Which solution you use depends on a variety of factors. You have to balance
the pros and cons of available options to make a final decision.
09 0789724588 CH06 10/25/00 5:05 PM Page 213
You also need to partition the COM objects into two distinct categories:
transactional and nontransactional. Objects that don’t modify any data
should be designated as nontransactional.
Also, take a few minutes to review the syntax used to create the COM
objects from the transaction root object. You used the CreateObject() func-
tion instead of the CreateInstance() function of the ObjectContext object.
Actually, COM+ supports both syntaxes. If you want to run the application
under MTS, however, you need to change CreateObject() to CreateInstance
to enlist the object being created into the existing transaction of the creator.
Finally, you should keep in mind the way errors are handled in transac-
tional COM objects. For this example, you used the raising error strategy,
which has something to do with the way COM+ ends a transaction. When a
COM object encounters an error and calls SetAbort, the transaction may
still continue until the root object of the transaction calls SetAbort. By rais-
ing an error to the root object, you can speed up the ending of the transac-
tion should an error occur.
What’s Next
This chapter covered the transaction services provided by COM+ and
guided you through the entire process of building a complete COM+ trans-
action application based on a three-tiered architecture. Chapter 7,
“Compensating Resource Manager (CRM),” will introduce you to CRM, a
new COM+ service. There, you’ll learn how to use CRM to extend the MS
DTC’s transactional support to some traditionally nontransactional
resources, such as file systems.
10 0789724588 CH07 10/25/00 5:09 PM Page 214
10 0789724588 CH07 10/25/00 5:09 PM Page 215
7
Compensating Resource Manager
(CRM)
In the preceding chapter, you learned about COM+ transaction services and
how to write transactional components and use them in your applications.
In this chapter, you’ll learn how to extend COM+ transaction services to
nontransactional resources by using a new feature provided by COM+: the
Compensating Resource Manager (CRM) framework.
This chapter teaches you the following:
• What resource dispensers and resource managers are
• The fundamentals of the Compensating Resource Manager (CRM)
• How to write your own CRM
• Issues involved in developing a CRM
10 0789724588 CH07 10/25/00 5:09 PM Page 216
protocols such as the OLE Transactions protocol and the X/Open XA proto-
col, so they cannot participate in MS DTC transactions. With the help of
the SDK, developers can implement the transaction protocols mentioned
earlier in the form of resource managers. This allows traditionally non-
transactional resources to utilize the transaction protection provided by
MTS and MS DTC. The SDK is designed for programmers who want to
implement resource managers in C++. It is not practical to implement
resource managers in Visual Basic. Therefore, you need to have skills in
C++ if you want to create resource managers.
TIP
In the context of this chapter, non-transactional resources refers to those resources that
don’t participate in MS DTC distributed transactions (and thus MTS or COM+ transac-
tions). Even though these resources might implement some form of transactional func-
tionality themselves, as far as MS DTC is concerned, they are non-transactional
resources. For example, even though Microsoft Access has its own transaction support,
it is considered a non-transactional resource because Access does not participate in
MS DTC distributed transactions.
Application
Component Ap
p-S
Inte pecif CRM
rfac ic l Clerk
e gC ontro
ICrmLo
COM+
CRM on IC ICrmCo Transaction
Worker rmC m
omp pensato Services/DTC
ens
ator r TXRLog
Varia
nt
CRM
Compensator
CRM
Recovery Clerk
Developing a CRM
Developing a CRM involves writing two specific components: namely, the
CRM Worker and the CRM Compensator. You also need to write an applica-
tion component that interacts with the CRM Worker component through an
application-specific interface (see Figure 7.1).
After an instance of the CRM Clerk is initiated, the CRM Worker first
needs to notify the CRM infrastructure which CRM Compensator to use to
receive transaction outcome notifications and perform compensating
actions. It does so by calling the RegisterCompensator() method, passing
the ProgID of the CRM Compensator, a description, and a flag constant
that controls which phases of transaction completion should be received by
the CRM Compensator, and whether recovery should fail in-doubt transac-
tions remain after recovery has been attempted. This is an asynchronous
method call. So, if the CRM Worker receives a “recovery in progress” error
code, it will continuously keep trying until it succeeds.
TIP
An in-doubt transaction is a transaction that has been prepared but hasn’t received a
decision to commit or abort because the server coordinating the transaction is unavail-
able.
Before performing the actual work that involves transactions, the CRM
Worker needs to write information to the durable log file in the form of
variant arrays by calling the WriteLogRecordVariants() method. This
process is known as write-ahead. The information that must be written into
the log is application-specific. The CRM Compensator relies on this infor-
mation to perform compensating actions later, at which time it will either
abort or commit the transactions. For the log to be durable, the CRM
Worker also needs to call the ForceLog() method of the ICrmLogControl
interface through the CRM Clerk object.
10 0789724588 CH07 10/25/00 5:09 PM Page 220
In case of an error during the normal operation, the CRM Worker must
abort the transaction immediately by calling the
ForceTransactionToAbort() method. After the CRM Worker is done with its
actions, it must persist the records to the log, and it must release the
ICrmLogControl.
NOTE
The CRM Worker must run under an MS DTC transaction. Therefore, the transaction
attribute of the CRM Worker component (the class) should be configured as Supported;
that is, you should set the MTSTransactionMode property to 4-RequiresTransaction.
The CRM Compensator may first be notified of the prepare phase of the
transaction completion. It can vote either Yes or No to the prepare request.
If its vote is No, the CRM Compensator will no longer receive any further
abort notifications. In case of a client abort, the CRM Compensator will
only receive abort notifications, not prepare notifications. The majority of
the compensating action is done in two methods: BeginCommitVariants()
and AbortRecordVariants(). You write code in the BeginCommitVariants()
method to commit the transaction and implement the logic in the
AbortRecordVariants() method to perform any compensating actions. The
CRM Compensator can log new records to the durable log if necessary.
NOTE
You should not assume that the same CRM Compensator instance will receive notifica-
tion during both phases (that is, phase 1, the prepare phase, and phase 2, the commit
or abort phase) because they could be interrupted by recovery.
NOTE
The CRM Compensator does not run under an MS DTC transaction. Therefore, the
transaction attribute of the component (the class) that implements the
ICrmCompensatorVariants interface must be configured as Disabled; that is, you
should set the MTSTransactionMode property to 0-NotAnMTSObject.
Table 7.3 summarizes the recommended COM+ configurations for the CRM
Worker and CRM Compensator that are developed in Visual Basic.
Table 7.3 Recommended COM+ Configurations for the CRM Worker and CRM
Compensator
Components Transaction JIT Threading Model
CRM Worker Required Yes Apartment
CRM Compensator Disabled No Apartment
Next, create a SQL Server database called COMPlus with a single table,
again called CRM (see Figure 7.4).
Figure 7.4: The CRM table in the SQL Server COMPlus database.
Notice that the two CRM tables are virtually identical in structure. Now
insert one row into the CRM table on the SQL Server database by running
the following script from the SQL Server Query Analyzer:
Insert into CRM values(1,’Testing Access Transaction’)
Now create two Data Source Names (DSNs), CRM and COMPlus, pointing
to the Access database and SQL Server database created earlier, respec-
tively.
10 0789724588 CH07 10/25/00 5:09 PM Page 224
Add a class module into the AccessTx project and set its properties as
shown in Figure 7.6.
Now start a new Visual Basic Standard EXE project and create a reference
to AccessTx.dll. Place a command button (Command1) on the form. Double-
click the command button and type the code shown in Listing 7.2.
Listing 7.2 Code for Testing the AccessTx Component
Private Sub Command1_Click()
On Error GoTo Test_Err
Dim oAccessTx As New AccessTx.CAccessTx
oAccessTx.TestAccessTrans
Exit Sub
Test_Err:
MsgBox Err.Description
End Sub
Press F5 to run the application. Clicking the command button on the form
opens a message box that indicates you have a primary key constraint vio-
lation on the SQL Server (see Figure 7.8). This is the result you expected.
Now open the CRM table in the Access database. In this case, a record does
make it to the CRM table (see Figure 7.9).
So what happened? The problem is that even though you call the SetAbort
method, the insert action to the Access database is not protected under the
transaction. Therefore, you have proved that Microsoft Access is a non-
transactional resource. In the following sections, you’ll build a sample CRM
application that makes Access data sources to participate in the
COM+/DTC transaction.
10 0789724588 CH07 10/25/00 5:09 PM Page 227
Figure 7.9: The row made its way to the Access database.
DATABASE PREPARATION
For purposes of this chapter, create a Northwind_Invoice Access database
that contains a single table, Invoice, as shown in Figure 7.10.
You also need to create a DSN for the Northwind_Invoice database and
name it Invoice.
Another process is to perform a credit check before ending the transaction.
If the customer has a bad credit history, then the entire transaction will be
aborted. To implement the credit function, add a Credits table in the
Northwind database by running the script in Listing 7.3.
Listing 7.3 SQL Script for Creating the Credits Table
if exists(select * from sysobjects
where type=’u’
and id = object_id(‘Credits’))
drop table Credits
go
The last thing you do in Listing 7.3 is to change the Standing of all the cus-
tomers whose names start with the letter B to 0. For this example, assume
those customers have bad credit histories (in reality, most of them might be
innocent, though).
APPLICATION DESIGN
To make the data manipulation of the Microsoft Access database transac-
tional, you need to create a pair of CRM components: a CRM Worker and
CRM Compensator. You also need to create an application component whose
transaction attribute should be set to support transactions. This component
needs to create a CRM Worker that, in turn, will register a CRM
Compensator to the CRM infrastructure to ensure that the action is
reversible.
Figure 7.11 is a Unified Modeling Language (UML) diagram that illustrates
the system architecture of the sample Northwind CRM application.
10 0789724588 CH07 10/25/00 5:09 PM Page 229
Northwind_Lookup
Northwind_Product.Product
GetProduct()
GetCustomer()
GetEmployee() UpdateStock()
GetShipper()
Northwind(SQL Server)
Northwind Order Form
Northwind_Credit.Credit
CheckCredit()
Data.Access
Northwind_Order.Order2
GetResults()
PlaceOrder() ExecSQL()
Northwind_Invoice.Invoice
CRMInvoice.CRMInvoiceWorker
GenerateInvoice()
CRMInvoice_GenerateInvoice() Northwind_Invoice(Access)
CRMInvoice.CRMInvoiceCompensator
TFXLog
IcrmCompensatorVariants_AbortRecordVariants()
Figure 7.11: The system architecture of the sample Northwind CRM appli-
cation.
Figure 7.12 defines the transaction scope of the sample application. As you
can see, the Access database, Invoice, is also included in the transaction
boundary.
Lookup Object
(Disabled)
Transaction Boundary
Product Object
(Supported)
TXRLog
CRM Compensator
(Not Support)
THE WORKFLOW
Figure 7.13 illustrates the modified workflow of the ordering process in the
sample application, presented as a UML sequence diagram. The UML
sequence diagram is a very powerful means by which you can document
and verify your design logic. A sequence diagram displays the interactions
between objects and resources. It also describes the sequences in which dif-
ferent events occur during the application’s lifetime. The vertical rectangle
boxes in a sequence diagram represent the lifetime of individual objects.
Create() Create()
Delete()
Create()
UpdateStock() Create()
ExeSQL()
Delete()
Delete()
Create()
GenerateInvoice()
GenerateInvoice()
RegisterCompensator()
Inserted Invoice()
Delete()
Delete()
Create()
CheckCredit() Create()
GetResult() Credit_Check()
Recordset()
Delete()
Delete()
Delete() Create()
As you can see in Figure 7.13, the ordering process starts (as the result of
the user clicking the Place Order button) with the creation of an Order2
object (you’ll learn about the Order2 object versus the Order object later)
and a call to its PlaceOrder() method. The Order2 object, in turn, creates
three objects, invoking appropriate methods in one after the other. As hap-
pened in the sample application you created in the preceding chapter, the
Order2 object inserts an order to the Northwind database and updates the
10 0789724588 CH07 10/25/00 5:09 PM Page 231
NOTE
To ensure that older client applications don’t crash, you need to keep the binary com-
patibility set to the existing Northwind_Order.dll before you compile the project with the
new class.
dRequiredDate, _
dShippedDate, _
sShipper, _
iProductID, _
sProductName, _
iQuantity, _
cUnitPrice, _
cSubtotal, _
cFreight, _
cTotal, _
sCustomer, _
sAddress, _
sCity, _
sRegion, _
sPostalCode, _
sCountry
In Listing 7.4, you change the call to the stored procedure by calling the
GetResult method of the Data.Access object instead of ExecSQL as you did in
the PlaceOrder method in Order.cls in the preceding chapter. You do so
because you modify the Order_Place stored procedure to return the Order
ID by using a SELECT statement near the end of the stored procedure.
Listing 7.5 shows the modified stored procedure.
10 0789724588 CH07 10/25/00 5:09 PM Page 235
@ShipPostalCode =@ShipPostalCode,
@ShipCountry = @ShipCountry
if @@error = 0
commit tran
else
rollback
go
Listings 7.6 and 7.7 show the CheckCredit method and the Credit_Check
stored procedure, respectively.
10 0789724588 CH07 10/25/00 5:09 PM Page 237
select Standing
from Credits
where CustomerID = @CustomerID
if @@rowcount = 0
raiserror (‘This customer does not exist.’,16,1)
go
End Function
With this ICRMInvoice interface in place, you are now ready to create the
CRM Worker and CRM Compensator components. To do so, start a new
ActiveX DLL project, name the project CRMInvoice, and set the rest of its
properties as shown in Figure 7.17.
10 0789724588 CH07 10/25/00 5:09 PM Page 239
In the project, set references to the COM+ Services Type Library, Que’s
Data Access Object Library, and Que Northwind Invoice CRM Interface, as
shown in Figure 7.18.
Implements ICRMInvoice
Click the Object drop-down box on the upper-left side of the code window
to see ICRMInvoice in the drop-down list (see Figure 7.19).
Selecting ICRMInvoice from the drop-down list generates the skeleton code
for the ICRMInvoice_GenerateInvoice method. By default, the function is
Private, so change it to Public. This way, the client can call the
GenerateInvoice method as normal. Finally, type the code in Listing 7.10 to
implement the logic of generating an invoice.
10 0789724588 CH07 10/25/00 5:09 PM Page 241
ICRMInvoice_GenerateInvoice = True
Exit Function
GenerateInvoice_Err:
ICRMInvoice_GenerateInvoice = False
‘Abort the transaction.
m_oCrmLogControl.ForceTransactionToAbort
‘Raise the error.
Err.Raise Number:=Err.Number, Description:=Err.Description
End Function
Exit Sub
GetCrmLogControl_Err:
Err.Raise Number:=Err.Number, Description:=Err.Description
End Sub
In Listing 7.11, notice that you use the syntax to create a custom interface.
See Chapter 4, “Introduction to Visual Basic COM Programming,” for
details about default and custom interfaces.
A one-to-one relationship exists between a CRM Worker instance and a
CRM Clerk instance. So, this GetCrmLogControl() method should be called
only once for the entire lifetime of the CRM Worker instance. This is done
by checking whether the module-level variable m_oCrmLogControl is set yet.
Only if the m_oCrmLogControl object is not set yet should you create an
instance of the CRM Clerk (see Listing 7.10).
After making sure the CRM Clerk is created, the CRM Worker then calls
the RegisterCRMInvoiceCompensator method to register the CRM
Compensator (see Listing 7.12).
Listing 7.12 The RegisterCRMInvoiceCompensator Method
Private Sub RegisterCRMInvoiceCompensator()
sProgId = “CRMInvoice.CRMInvoiceCompensator”
sDescription = “Que Northwind Invoice CRM Sample”
sDescription, _
CRMREGFLAG_ALLPHASES
Loop
Exit Sub
RegisterCrmInvoiceCompensator_Err:
Err.Raise Number:=Err.Number, Description:=Err.Description
End Sub
As you can see in Listing 7.12, the CRM Worker registers the CRM
Compensator by calling the RegisterCompensator() method of the CRM
Clerk through the ICRMLogControl object, passing in the ProgID, a descrip-
tion, and a flag.
The ProgID in the example is CRMInvoice.CRMInvoiceCompensator. The last
parameter of the RegisterCompensator() method is a flag that gives you
fine granular control over which phases of transaction completion you are
interested in so that the CRM Compensator will receive the notification. In
this example, you should be especially interested in the abort phase of the
transaction completion, so set the flag to CRMREGFLAG_ABORTPHASE.
Due to the timing issue, you use a Do...While loop to check the return
value of the RegisterCompensator() method call to make sure the CRM
Compensator is eventually registered.
Now go back to Listing 7.10, which shows the ICRMInvoice_GenerateInvoice
function. After the CRM has been instantiated and the CRM Compensator
is registered, the CRM needs to write a record to the durable log before it
performs the actual action so that later the CRM infrastructure can per-
form a compensating action if needed. This is known as write-ahead.
The write-ahead is performed by calling the WriteLogRecordVariants()
method of the CRM Clerk object, passing a variant array. This record will
be used by the CRM Compensator to perform a compensating action later if
something goes wrong. In the sample application, the normal action is to
10 0789724588 CH07 10/25/00 5:09 PM Page 244
insert a row into the Invoice table in a Microsoft Access database. So, the
reverse action would be to delete the row you inserted if the whole ordering
process fails to keep the system in a consistent state. The trick you are
going to use here is to pass a Where clause that includes the primary key. So
later, the CRM Compensator will know exactly which row in the Invoice
table to delete if something goes wrong.
The WriteLogRecordVariants() method call is followed by a ForceLog() call
to ensure that the log record is durable, so the system can be recovered
should it experience a possible failure during the process.
Now that you’ve written a durable log record, you are ready to do the action
insert. To do so, create the Data.Access object and call its ExecSQL method
to perform the insert. If something goes wrong, call the
ForceTransactionToAbort() method before raising an error. Calling this
method notifies MS DTC that you vote to abort the transaction.
Now you can turn your attention to another important part of the show: the
CRMInvoiceCompensator class.
In the General Declaration section of the class module, insert these two
lines of code:
Implements ICrmCompensatorVariants
Dim m_oCrmLogControl As ICrmLogControl
sWhereClause = pLogRecord(0)
sSQL = “Delete * from Invoice “ & sWhereClause
ICrmCompensatorVariants_AbortRecordVariants = True
Exit Function
AbortRecordVariants_Err:
ICrmCompensatorVariants_AbortRecordVariants = False
Err.Raise Number:=Err.Number, Description:=Err.Description
End Function
CommitRecordVariants_Err:
Err.Raise Number:=Err.Number, Description:=Err.Description
End Function
10 0789724588 CH07 10/25/00 5:09 PM Page 246
Listing 7.16 shows the rest of the methods for the CRM Compensator
object.
Listing 7.16 The Rest of the Methods for the CRM Compensator Object
Private Sub ICrmCompensatorVariants_EndAbortVariants()
‘Do nothing.
Exit Sub
End Sub
ICrmCompensatorVariants_EndPrepareVariants = True
End Function
ICrmCompensatorVariants_PrepareRecordVariants = False
End Function
After developing the CRM Worker and CRM Compensator, you also need an
application component that uses the CRM objects you just created. This
application component in the example is the Northwind_Invoice.Invoice
object. To create this object, create an ActiveX DLL project named
Northwind_Invoice.vbp and change the name of the class module from
Class1 to Invoice. Set the MTSTransactionMode to 2-RequiresTransaction.
This Invoice class has only one method, GenerateInvoice(), as shown in
Listing 7.17.
Listing 7.17 The GenerateInvoice() Method of the Invoice Class
Public Function GenerateInvoice(ByRef iOrderID As Integer, _
ByVal sCustomerID As String, _
ByVal sSalesPerson As String, _
ByVal dOrderDate As Date, _
ByVal dRequiredDate As Date, _
ByVal dShippedDate As Date, _
ByVal sShipVia As String, _
ByVal iProductID As Integer, _
ByVal sProductName As String, _
ByVal iQuantity As Integer, _
10 0789724588 CH07 10/25/00 5:09 PM Page 247
GenerateInvoice_Err:
If Not oObjectContext Is Nothing Then
oObjectContext.SetAbort
End If
Err.Raise Number:=Err.Number, Description:=Err.Description
End Function
10 0789724588 CH07 10/25/00 5:09 PM Page 248
Screen.MousePointer = vbDefault
10 0789724588 CH07 10/25/00 5:09 PM Page 249
Exit Sub
cmdOrder_Err:
Screen.MousePointer = vbDefault
MsgBox Err.Description, vbCritical
End Sub
Next, you need to enable the CRM for the Northwind CRM COM+ applica-
tion. To do so, right-click the Northwind CRM application in Component
Services, choose Properties, click the Advanced tab, and then select the
Enable Compensating Resource Managers check box (see Figure 7.2).
10 0789724588 CH07 10/25/00 5:09 PM Page 250
As soon as you check this option and close the Properties page, COM+ auto-
matically creates a CRM log file and places it in the same directory as the
DTC log file (see Figure 7.21). The CRM log filename is the Application ID
of the CRM COM+ application with the extension .crmlog.
NOTE
If your application uses CRM, make certain that the Enable Compensating Resource
Manager option is checked. Otherwise, the calls to CRM will fail.
Earlier in this chapter, you learned that you need to disable the JIT (Just-
in-time activation) for the CRM Compensator. To do so, right-click the
CRMInvoice.CRMInvoiceCompensator component in Component Services,
choose Properties, click the Activation tab, and deselect the Enable Just In
Time Activation check box (see Figure 7.22).
Now check the databases to verify that the system is in a consistent state.
The maximum Order ID is still 11099, so no order has been placed. The
UnitsInStock for product Teatime Chocolate Biscuits is still 25. Finally, you
can see that no entry appears in the Invoice table of the Northwind_Invoice
database. So, everything is in order.
10 0789724588 CH07 10/25/00 5:09 PM Page 252
Now keep everything on the order form the same but switch to another cus-
tomer, Ernst Handel. Try to place the order again. This time, you get a con-
firmation message box that says the order has been processed. Again, check
the databases. This time, you’ll find a new order with Order ID 11101 in
the orders table and that ProductID 19 now has 15 items in stock. Most
importantly, the Invoice table in the Access database, Northwind_Invoice,
also has an invoice with Order ID 11101 (see Figure 7.24).
Figure 7.24: An invoice with Order ID 11101 in the Invoice table of the
Northwind_Invoice database.
As you can see, the order made it all the way through the process.
Something interesting happened, though. Notice that the Order ID is
11101. The maximum Order ID before processing the order was 11099. So,
the Order ID skipped one. What happened to Order ID 11100? Well, the
Order ID column in the orders table is an Identity column. It is an autoin-
cremental number that is maintained by the system. The workflow of pro-
cessing an order is as follows: creating an order, updating the stock,
generating an invoice, and checking the credit. If anything goes wrong in
the process, the entire thing is rolled back. The Invoice is rolled back by the
CRM Compensator—it is deleted from the table by the
AbortRecordVariants() method (see Listing 7.14). However, the orders and
the products tables are reversed by the SQL Server and MS DTC. It is obvi-
ous that the order was originally entered into the orders table. When MS
DTC is notified to abort the entire transaction, it deletes the orders record
as the CRM did to the invoice. The DTC does not, however, reset the iden-
tity value to 11100, thus leaving a gap in the Order ID.
Isolation
Unlike a normal COM+ transaction that supports all the ACID properties,
CRM supports only atomicity, consistency, and durability of the transactions
10 0789724588 CH07 10/25/00 5:09 PM Page 253
Recovery
Recovery is the process of completing any interrupted transactions due to
abnormalities such as a system failure. When a server process with CRM
enabled restarts, the CRM infrastructure locates the CRM log file using the
Application ID of the COM+ application. If it detects any uncompleted
transactions, it consults the MS DTC for the transaction outcome. It then
creates a CRM Compensator to commit or abort the transaction. The CRM
Worker must handle the “Recovery in progress” situation by checking the
XACT_E_RECOVERYINPROGRESS return code.
The CRM Compensator should also be designed to keep the recovery time
as short as possible so that the CRM server application is not blocked out
for too long to start a new CRMWorker.
Idempotence
A CRM Compensator might receive the same log record more than once.
Therefore, the action the CRM Compensator performs, either undo or redo,
must be idempotent. This means no matter how many times a specific
action is repeated, the result should stay the same.
Other Considerations
You also should take care of some other issues. For example, the CRM
Compensator must deal with a situation in which the CRM Worker writes a
record to the log without actually performing the action for any reason. The
CRM Compensator must also handle cases in which the client aborts the
transaction; in this situation, the MS DTC is not notified, nor is the CRM
Compensator.
When developing a CRM application, you should carefully consider all the
potential issues before you start building it. Beware that not all situations
are suitable for a CRM solution.
Applications that can take advantage of CRM include file systems, the
Windows Registry, desktop databases, and some legacy applications.
10 0789724588 CH07 10/25/00 5:09 PM Page 254
What’s Next
In this chapter, you learned that CRM provides an easier way to build a
resource manager and to make traditionally nontransactional resources
participate in the 2-pc transaction of MS DTC. Chapter 8, “COM+ Security,”
will teach you how to build highly secured applications using the security
services provided by COM+.
10 0789724588 CH07 10/25/00 5:09 PM Page 255
11 0789724588 CH08 10/25/00 5:04 PM Page 256
11 0789724588 CH08 10/25/00 5:04 PM Page 257
8
COM+ Security
Chapter 5, “Using the Component Services MMC Snap-In,” briefly intro-
duced how to configure COM+ security. In this chapter, you’ll explore a rich
set of security features of COM+ in great detail.
This chapter teaches you the following:
• How the COM+ security architecture works
• What role-based security is
• What authorization and authentication are
• How impersonation works
• How to implement security in three-tiered applications
• Other security issues such as audit trails, library applications security,
and QC security
11 0789724588 CH08 10/25/00 5:04 PM Page 258
EXAMPLE Principal
Request
Reference
Monitor
(COM + Policy
Executive)
Resources
Role-Based Security
The automatic security services provided by COM+, which are based on the
successful security model introduced in Microsoft Transaction Server
(MTS), greatly simplify the otherwise daunting task of implementing
sophisticated security mechanisms for distributed applications. At the core
of the COM+ security model is role-based security.
A role is a logical grouping of users who share the same security privilege.
A role is usually mapped to a specific type of user of your application. For
example, a banking application might have bank clerks, tellers, and man-
agers. So, you can create three corresponding roles—Clerks, Tellers, and
Managers—that map to these three groups of users. Each individual user
account can then be assigned to a specific role. For example, you might
assign user Joe to the Clerks role, user Mark to the Managers role, and
users Jennifer and Mary to the Tellers role.
In MTS, you can define roles for an MTS package and then associate them
with the components inside the MTS package. In COM+, you can associate
roles to a COM+ application, the components, the interfaces, all the way
down to the methods of each interface.
Designing Roles
The process of defining and categorizing users is a very important step in
the design and analysis of an application (or a project), especially when
security is one of the major concerns. Let’s look at the security require-
ment/user profile part of a design document for a commission tracking sys-
tem to see how to fulfill the security requirements of the application. This
commission tracking system tracks information on consultants for an IT
staffing company. This information includes their pay rates, billable hours,
and so on and applies some business rules to the data to calculate commis-
sions for recruiters and account managers. It also supports flexible report-
ing that will facilitate the statistic commission analysis and tracking.
The following statements are abstracted from the design document. They
describe the users and their privileges in this commission tracking system:
1. Recruiters can enter, modify, and review the basic information of the
consultants.
2. Account Managers can enter, modify, and review the billing informa-
tion of the consultants.
3. Time Sheet Data Entry Personnel can enter, modify, and review bill-
able-hour information.
11 0789724588 CH08 10/25/00 5:04 PM Page 260
Statements 1 through 5 are suitable for using roles because they clearly
define the users and what kinds of actions they can perform in the commis-
sion tracking system. You can directly map these users into corresponding
roles.
The last two statements, however, are not suitable for using role-based
security because the access decision relies on the characteristics of a partic-
ular piece of data. In this case, security checking must often be performed
at the database level or using other mechanisms, such as parameterized
stored procedures, that you’ll learn about later in this chapter.
CREATING ROLES
You can create roles administratively by using the Component Services
snap-in or programmatically by using the scriptable COM+ administration
object model. You’ll learn how to declare roles in Visual Basic code in
Chapter 11, “Administering COM+ Applications Programmatically.” This
section demonstrates how you can declare roles by using the Components
Services snap-in.
From the Component Services snap-in, create an empty COM+ server
application called Commission Tracking. Expand the Commission Tracking
COM+ application you just created, right-click the Roles folder, and choose
EXAMPLE New, Role, as shown in Figure 8.2.
Enter Recruiters as the name of the new role; then click OK. In the same
manner, create four other roles—Account Managers, Time Sheet Clerks,
HR Personnel, and Financial Personnel—as shown in Figure 8.3.
11 0789724588 CH08 10/25/00 5:04 PM Page 261
After creating the roles, you can assign user accounts to appropriate roles.
For this example, assign user pli to the Financial Personnel role. To assign
accounts, expand the Financial Personnel folder under the Roles folder,
right-click the Users folder, and choose New, User, as shown in Figure 8.4.
When the Select Users or Groups dialog box appears, select pli from the
users list at the top of the dialog box and click the Add button. The user pli
then appears at the bottom of the dialog box (see Figure 8.5).
Click OK to close the dialog box, and you will notice that the user pli has
been added to the Financial Personnel role (see Figure 8.6).
11 0789724588 CH08 10/25/00 5:04 PM Page 262
Figure 8.6: User pli has been assigned to the Financial Personnel role.
11 0789724588 CH08 10/25/00 5:04 PM Page 263
ASSIGNING ROLES
In COM+, you can assign roles at three different levels: component, inter-
face, and method. To see how to assign a role to an appropriate level, create
three business objects (components) for your commission tracking system.
EXAMPLE
Figures 8.7 through 8.9 show the interface and methods of these compo-
nents.
CTS_Biz.Consultant
Add()
Remove()
Update()
CTS_Biz.TimeSheet
Add()
Remove()
Update()
CTS_Biz.Billing
Add()
Remove()
Update()
NOTE
Role assignments are inherited down the natural chain of inclusion. That is, if you
assign a role to a component, it is implicitly assigned to every interface and method
exposed by that component. In this example, you explicitly assign a role to a compo-
nent, an interface, and methods solely for the purpose of demonstration.
NOTE
Before you assign roles, make sure that you enable access checking at the COM+
application level (see Figure 8.11).
Figure 8.11: Enabling access checking for the Commission Tracking COM+
application.
11 0789724588 CH08 10/25/00 5:04 PM Page 265
In a similar fashion, you can assign the Recruiter role to the _Consultant
interface or any of its methods (Add, Remove, or Update), as shown in Figures
8.13 and 8.14.
NOTE
In Figures 8.13 and 8.14, the Recruiters role appears in the Roles Inherited by
Selected Item(s) section, as explained earlier.
TIP
When you compile your COM component to an ActiveX DLL (or ActiveX EXE), Visual
Basic will append an underscore “_” to the default interfaces. For example, the
Consultant interface becomes _Consultant in OLE Viewer or in COM+ Component
Services snap-in. Strictly speaking, a default interface (such as the Consultant inter-
face) is not an “interface” because it carries implementation. This is not allowed in
other programming languages, such as C++, in which you must separate the interface
from the implementation.
As you can see from Figure 8.15, one report engine component,
ReportEngine.Report, supports a single method, RunReport(). The report
engine interacts with two other reporting components to process different
reports. The Report.Commission and Report.HeadCount components are
responsible for processing Commission reports and HeadCount reports,
respectively. Each reporting component also supports a RunReport()
method. In this example, the implementations of RunReport at each report-
ing component (Report.Commission or Report.HeadCount) are similar. They
simply write a message to the event log, indicating a specific type of report
has been generated (see Listing 8.1).
Listing 8.1 The RunReport() Methods of Report.Commission and Report.HeadCount
‘=======================================================
‘The RunReport() method of the Report.HeadCount object
‘=======================================================
Public Function RunReport() As Boolean
‘You can implement the actual code here
‘to process the Commission reports
‘=======================================================
‘The RunReport() method of the Report.Commission object
‘=======================================================
Public Function RunReport() As Boolean
11 0789724588 CH08 10/25/00 5:04 PM Page 268
Exit Function
RunReport_Err:
Err.Raise Err.Number
End Function
You then can install the three components into the Commission Tracking
COM+ application (see Figure 8.16).
Figure 8.16: Installing the three reporting components into the Commission
Tracking COM+ application.
Now it’s time to test the code. Start a standard Visual Basic EXE project,
name it TestingSecurity, and name the form frmTestingSecurity. Then set
a reference to the report engine, as shown in Figure 8.17.
Figure 8.17: Setting a reference to the report engine in the testing project.
11 0789724588 CH08 10/25/00 5:04 PM Page 270
Place a command button on the form and name it cmdRunReport. Then add
the code in Listing 8.3 in the Click event procedure of the command button.
Listing 8.3 The Click Event Procedure of the Command Button
EXAMPLE
Private Sub cmdRunReport_Click()
On Error GoTo Report_Err
Screen.MousePointer = vbHourglass
Dim oReport As Object ‘ ReportEngine.Report object
Set oReport = CreateObject(“ReportEngine.Report”)
oReport.RunReport
Set oReport = Nothing
Screen.MousePointer = vbDefault
MsgBox “Report has been generated!”
Exit Sub
Report_Err:
Screen.MousePointer = vbDefault
MsgBox Err.Description
End Function
Now press F5 to start the testing application and then click the command
button. You get the error message Permission denied (see Figure 8.18).
Figure 8.20: A message box indicates that the report has been generated.
Now remove the user pli (which is the account you logged in earlier) from
the Financial Personnel role, add it to the HR Personnel role, and try the
testing application. Again, you get a confirmation message like the one in
Figure 8.20. If you check the event log, however, you will find that this time
a HeadCount report has been generated instead of the Commission report
(see Figure 8.22).
Figure 8.22: After you reassign the user pli to the HR Personnel role,
a HeadCount report is generated instead.
Now go to the extreme by reassigning the user pli to the Recruiters role
and add the Recruiters role to the ReportEngine.Report component (see
Figure 8.23). Then try the testing application again.
Figure 8.23: Reassigning user pli to the Recruiters role and adding the
Recruiters role to the ReportEngine.Report component.
11 0789724588 CH08 10/25/00 5:04 PM Page 273
This time, you get a customized error message (see Figure 8.24), as defined
in Listing 8.2, because the Recruiters role falls into the Else part of the
If...ElseIF...Else...End If structure of the listing and the error is
raised.
Figure 8.26: Configuring the authentication level for a client using the
DCOM configuration utility, Dcomcnfg.exe.
If the client and the server are set to different authentication levels, COM+
negotiates the two authentication levels based on the following formula:
Final Authentication Level = Maximum(Client, Server)
Impersonation
Impersonation is the ability of a thread to execute in a security context that
is different from the context of the process that owns the thread. Imperson-
ation across the network is called delegation. The server uses an access
token that represents the client’s credentials so that it can access exactly
the same resources as the client.
11 0789724588 CH08 10/25/00 5:04 PM Page 275
Impersonation 275
Impersonating Clients
In some scenarios, a server application may try to access resources under
the identity of its client. The access check is thus performed against the
client’s identity instead of the server’s. This action is known as impersonat-
EXAMPLE ing the client. Figure 8.27 illustrates impersonation and delegation.
COM+Application
Delegation Identify
Component A of
Client A Client A
Machine A Machine B
Cloaking
During impersonation/delegation, the server can mask its own identity
when making calls on the client’s behalf; this capability is known as
cloaking.
Cloaking can be either static or dynamic. In static cloaking, the original
client identity, realized as the server access token, can be presented once to
a downstream server on the first call, and the same thread token will be
used on all subsequent method calls. In dynamic cloaking, on the other
hand, the original client identity is resolved as the server thread token on a
per-call basis to the downstream server. Whereas this “late resolving” fea-
ture of dynamic cloaking offers more flexibility than static cloaking, it also
results in considerable performance overhead.
11 0789724588 CH08 10/25/00 5:04 PM Page 276
Audit Trails
Listing 8.2 used the SecurityCallContext object of the COM+ Services Type
Library to check whether a user was in a specific role and, based on the
results, to decide whether the user should perform certain activities.
In addition to SecurityCallContext, COM+ provides a few more security-
related objects and interfaces that allow you to perform several security
functions programmatically. For example, you can use the Security prop-
EXAMPLE erty of the current object’s ObjectContext to identify the direct caller and
original callers of the method call. This way, you can track all the upstream
callers down the entire calling chain, providing a great way to implement
audit trails in your application. The code fragments in Listing 8.4 show
11 0789724588 CH08 10/25/00 5:04 PM Page 278
how to retrieve the direct caller and original caller information by using
ObjectContext.
End Function
End Function
What’s Next
This chapter discussed the automatic security services provided by COM+.
Chapter 9, “Queued Components,” will introduce you to one of the most
exciting new features of COM+. By using the Queued Component architec-
ture, you can develop powerful asynchronous processing applications in
much the same way as you do in DCOM.
12 0789724588 CH09 10/25/00 4:57 PM Page 280
12 0789724588 CH09 10/25/00 4:57 PM Page 281
9
Queued Components
Chapter 3, “Introduction to Microsoft Message Queuing Services (MSMQ),”
showed you how to use the asynchronous programming model of MSMQ to
improve the availability and robustness of distributed applications. In this
chapter, you’ll learn about one of the key features provided by COM+:
Queued Components, or QC.
This chapter teaches you the following:
• The basics of Queued Components
• How to develop Queued Components
• How to handle exceptions in Queued Components
• Security in Queued Components
12 0789724588 CH09 10/25/00 4:57 PM Page 282
DCOM Limitations
Using DCOM, a client can transparently invoke a method of a COM object
that resides on a remote machine. The client doesn’t care about exactly
where the COM object resides. The client will treat the remote COM object
as if it resides at the same process. DCOM takes care of this. Although the
location transparency offered by the DCOM protocol greatly simplifies the
programming model from the client perspective, it has its own limitations.
The biggest problem with the DCOM programming paradigm is its synchro-
nous processing model. When using DCOM, all the parties involved in a
process must be available at the same time. The lifetime of the server (the
COM object in this case) is completely controlled by the client. The server is
tied up for the entire duration between the first line and last line of the
client code. As you can see, the DCOM programming model offers poor
availability, reliability, and scalability.
Listener
Client
Player Server
Record
Queue
AVAILABILITY
The asynchronous capability of the underlying message queuing system of
Queued Components improves the server component availability. The client
can now continuously take requests without having to ensure that the
server component is running. All the calls to the server component are
queued for processing as soon as the server component is available. For
example, an online ordering system can take orders from customers even
though the server that processes the orders may or may not be available
immediately.
SCALABILITY
In the Queued Components system, the server component is decoupled from
the client. After making a call, the client no longer has to wait until the
server finishes processing; it can immediately turn around to serve other
requests. Therefore, scalability is greatly improved. This capability is espe-
cially important for Web-based applications. The Internet Information
Server (IIS) supports only a limited number of client threads that can be
executed simultaneously, somewhere between 20 to 50. Consider the online
ordering application; there can be a maximum of only 50 instances of the
Order object (the server component) running at a given time. Therefore,
shortening the lifetime of a server component means it can serve more
requests in a given time period.
NOTE
IIS implemented a thread pooling mechanism to support a maximum number of concur-
rent clients. The size of the thread pool is configurable through a registry setting. In IIS
4.0, the default thread pool size is 10; you can finely tune this default pool size
between 5 and 20. In IIS 5.0, the default thread pool size is 25 and the recommended
maximum thread pool size is 100.
For information about thread pooling of IIS 4.0, please refer to Ted Pattison’s article,
“IIS Threading and State Management,” available at http://www.microsoft.com/
Mind/0299/basics/basics0299.htm. For information about threading pooling of IIS
5.0, see the Microsoft Knowledge Base article, “How to Tune the
ASPProcessorThreadMax,” Q238583, available from Microsoft Support at http://
support.microsoft.com/support/kb/articles/Q238/5/83.ASP.
12 0789724588 CH09 10/25/00 4:57 PM Page 285
OFFLINE SUPPORT
Queued Components can support disconnect scenarios in which sales repre-
sentatives use mobile computing devices, such as laptops, notebooks, and
palm computers, to take orders offline and later connect to the server to
process the orders.
SERVER SCHEDULING
In a Queued Components environment, the server component operates
independently of the client. This architecture separates time-critical tasks
from non–time-critical ones and allows the non–time-critical tasks to be
completed at a later, off-peak period. In the online ordering scenario, taking
order requests is a time-critical operation, whereas processing the order is
not. On the other hand, processing orders usually takes longer than taking
the order requests. For a Web-based online ordering application, the actual
time of taking the order request usually starts when a customer clicks the
Submit button. If the application is designed using Queued Components, as
far as the customer is concerned, the order is complete as soon as the Web
server receives the request. However, processing orders may involve several
processes, such as checking inventory, verifying customers’ credit, generat-
ing invoices, and arranging shipping. So processing orders could be a very
time-consuming process compared to taking orders. By separating the order
taking and order processing into two independent processes, you can allow
the order requests to build up (queue) so that the server component can
process them at its own pace.
Shipping object then processes the shipping and updates the Shipping
database. Figures 9.2 and 9.3 illustrate the transaction boundaries of this
ordering/shipping application in both the DCOM and QC scenarios.
Order Order
Database Database
Transaction Boundary
Transaction Transaction
Order Order
Client
Object QC Object
Transaction
Order Shipping
Database Database
In the DCOM scenario shown in Figure 9.2, both ordering and shipping
processes are wrapped into a single transaction. Each subprocess, including
the network connection, is a possible point of failure. So, DCOM is not a
very robust solution in this scenario.
In the QC scenario shown in Figure 9.3, however, the single transaction in
DCOM is split into three individual transactions. The first transaction
12 0789724588 CH09 10/25/00 4:57 PM Page 287
involves the Order object and the Order database. MSMQ moves the mes-
sage from one queue to another queue, using its transacted capabilities to
guarantee delivery only once. If the order request is successfully taken, the
Order database is updated, and the message is sent to the input queue. If
the message does not reach the queue, it is aborted and the database
update is rolled back. The second transaction in the QC scenario happens
when the queue manager discovers that the server is available, perhaps at
a later time. A messaging mover moves the message from the client queue
manager to the server queue manager. The third transaction involves the
Shipping object and the Shipping database. If the shipper component fails
in the middle of the transaction, the database update is rolled back, and the
message is not lost. Therefore, the data integrity is protected. These three
transactions are independent of one another. In this scenario, the Shipping
object is a Queued Component, and the Order object is its client.
NOTE
If the transaction attribute of the Queued Component is set to Required or Supported,
the MSMQ queue is transacted. In this case, MSMQ accepts delivery of the message
only if the client-side transaction commits. If the transaction attribute of the Queued
Component is configured as Requires New, MSMQ accepts the message even if the
client-side transaction aborts.
Poison Messages
Sometimes a message cannot be processed for some reason, such as a prob-
lem with the server or queuing system. This unprocessed message is called
a poison message. When a poison message is discovered, the transaction is
rolled back, and the message is put back on the top of the queue. It is
dequeued again if the same condition is repeated. This message can con-
tinue looping for some time until the problem is corrected. Queued
Components handle poison messages by a series of retries. If all the retries
are exhausted, the message is moved to a final resting queue. At that point,
the Queued Components search for an exception class; if they find an
exception class, they invoke the FinalServerRetry() or FinalClientRetry()
method of the IPlayBackControl interface that the exception class imple-
ments, depending on whether the problem is on the client side or on the
server side. The exception handling mechanism of Queued Components pro-
vides a powerful means by which you can be quickly notified about a poison
message, and it allows you to implement creative solutions to correct prob-
lems. You’ll learn about exception handling and exception classes later in
this chapter.
12 0789724588 CH09 10/25/00 4:57 PM Page 288
dispatch it to the method call specified by the client. Because all the input
parameters must be passed by value in Queued Components, but COM+
does not support pass-by-value semantics for standard COM objects, the
object being passed must support the IPersistStream interface in order for
the player to re-instantiate it. Because the lifetime of the server component
in Queued Components is independent of the client, the object must not
make assumptions about when it will be re-instantiated.
ADO recordsets and OLE DB rowsets both support the IPersistStream
interface, so they can be passed to Queued Components. Because server-
side recordsets or rowsets cannot be marshaled using IPersistStream, you
must use client-side recordsets or rowsets. For ADO recordsets, you need to
specify the Cursorlocation property of the recordset to adUseClient.
Using Visual Basic 6.0, you can build persistable objects by specifying the
Persistable property of a class module as 1-Persistable (see Figure 9.4).
TIP
MSDN knowledge base article Q246627, “Pass Objects as Parameters to COM+
Queued Components,” demonstrates how to create a persistable object in Visual Basic
and pass it to a Queued Component. Search MSDN for Q246627 to obtain this article.
Rename the Class1 class module to QC1 and add the simple RunQC() method
shown in Listing 9.1.
Listing 9.1 The RunQC() Method of the QCDemo.QC1 Component
Public Sub RunQC()
App.LogEvent “QC Request Received @” & Now() & “.”
End Sub
As you can see, only one line of code in the RunQC() method of this
QCDemo.QC1 component is implemented as a Public Sub. It just writes a
string to the event log, indicating a QC request has been received and post-
ing a timestamp that denotes when it was received.
Now compile the component into QCDemo.dll, and you are ready to config-
ure your new Queued Component.
Right-click the QC Demo COM+ application you just created and select
Properties. Click the Queuing tab of the Properties page. Then select the
“Queued - This Application Can Be Reached by MSMQ Queues” check box
and the “Listen - This Application, When Activated, Will Process Messages
That Arrive on Its MSMQ Queue” check box, as shown in Figure 9.7.
12 0789724588 CH09 10/25/00 4:57 PM Page 291
Figure 9.7: Ensuring that the QC Demo COM+ application can be queued.
NOTE
The MSMQ Workgroup configuration does not permit Queued Components to support
application security. If you have Windows 2000 installed with the Workgroup configuration
(i.e., not installed as a Domain Controller), you must disable security on each Queued
Application accessed in this configuration. This includes disabling security on both the
client and server applications if the application proxy has already been exported.
Otherwise, you get a Permission denied error. To disable security for your COM+ applica-
tion, right-click the application, select Properties, and then select the Security tab. Finally,
change the Authentication Level for Calls setting to None (see Figure 9.8).
12 0789724588 CH09 10/25/00 4:57 PM Page 292
Now you have an empty, queueable COM+ application. It’s time to install
the component and configure it as queueable. To do so, expand the QC
Demo COM+ application, right-click the Components folder, and then select
New, Component. Next, install QCDemo.dll as a new component. Expand
the QCDemo.QC1 component you just installed and select the Interfaces
folder. Then right-click the _QC1 interface on the right pane and select
Properties (see Figure 9.9).
On the Properties page of the _QC1 interface, click the Queuing tab and
select the Queued check box in the Queuing Properties group, as shown in
Figure 9.10.
And that’s it! Now you have a fully functional COM+ application that uti-
lizes a Queued Component.
12 0789724588 CH09 10/25/00 4:57 PM Page 293
or
Dim oQC As Object
Set oQC = GetObject(“new:{73A0803F-D06F-465E-822E-24BEB7AB0735}”)
or
Dim oQC As Object
Set oQC = GetObject(“new:73A0803F-D06F-465E-822E-24BEB7AB0735”)
12 0789724588 CH09 10/25/00 4:57 PM Page 294
Table 9.1 queue Moniker Parameters That Affect the Destination Queue
Parameter Description
ComputerName Specifies the computer name portion of an MSMQ queue path-
name. If this parameter is not specified, the computer name
associated with the configured application is used. Refer to
Chapter 3 for the syntax of the PathName property of
MSMQQueueInfo.
QueueName Specifies the queue name portion of an MSMQ queue path-
name. If this parameter is not specified, the queue name asso-
ciated with the configured application is used. Refer to Chapter
3 for the syntax of the PathName property of MSMQQueueInfo.
PathName Specifies the MSMQ queue pathname. If this parameter is not
specified, the MSMQ queue pathname associated with the con-
figured application is used. Refer to Chapter 3 for the syntax of
the PathName property of MSMQQueueInfo.
FormatName Specifies the MSMQ format name—for example,
DIRECT=OS:peishu\PRIVATE$\QC Demo. Refer to Chapter 3 for
the syntax of the FormatName property of MSMQQueueInfo.
Table 9.2 queue Moniker Parameters That Affect the MSMQ Message
Parameter Description
AppSpecifc Specifies an integer that can be used to identify the message
so that the receiver can filter out only the messages from a
specific application.
AuthLevel Specifies the message authentication level. An authenticated
message is digitally signed and requires a certificate for the
user sending the message. You can specify the AuthLevel to
either MQMSG_AUTH_LEVEL_NONE(0) or
MQMSG_AUTH_LEVEL_ALWAYS(1). For a Workgroup configuration,
you should always use MQMSG_AUTH_LEVEL_NONE.
Delivery Specifies the message delivery option, whether you want the
message to be persistent and survive system failures and
machine reboots. This value is ignored for transacted queues.
Acceptable values include MQMSG_DELIVERY_EXPRESS(0) and
MQMSG_DELIVERY_RECOVERABLE(1).
12 0789724588 CH09 10/25/00 4:57 PM Page 295
Answer No to the dialog that asks whether you want to save the
Application log before clearing it.
Now go back to the client project and press F5 to start it. When you click
the command button, you get a confirmation message that says the QC
request has been sent (see Figure 9.12).
Now switch back to the Event Viewer. You’ll find no message appears in the
event log at this point. So what’s wrong?
Well, the problem has something to do with the way in which the Queued
Component starts. Making method calls on a Queued Component does not
execute the method immediately. Instead, the call is recorded by the QC
recorder, marshaled, and sent to MSMQ. Unlike when you activate a
12 0789724588 CH09 10/25/00 4:57 PM Page 297
Figure 9.13: The method call is converted into an MSMQ message and
temporarily stored in the application-specific queue.
Double-click the message on the right pane of the Event Viewer to open
the message. You’ll see that it is the message you’re looking for (see
Figure 9.16).
QC OR NOT QC
Although using Queued Components offers a number of benefits to distrib-
uted application development, under some circumstances Queued
Components may not be a durable solution. When considering whether your
application can take advantage of Queued Components, you need to think
in terms of critical path and time-independence. A critical path is an activ-
ity that is critical to a business. In the online ordering application, the
activity of taking orders is a critical path. Activities that can be taken out
of the critical path are good candidates for using Queued Components. For
example, the credit card checking and shipping arrangement in an online
ordering system are not part of the critical path and can take advantage of
Queued Components.
Another important consideration is time-independence. If some processes
can be identified as time-independent of other processes, these processes
usually lend themselves to being Queued Components. For example, ship-
ping and order processing can be processed in a time-independent manner,
so you can enable queuing for the shipping component.
HANDLING RESPONSES
In some situations, a client may request a response from a server compo-
nent. For example, the client placing an order may want to know the status
of that order. The requests for responses are handled differently in asyn-
chronous processing models such as Queued Components and synchronous
processing systems such as DCOM. In a Queued Components application,
the communication is one-way only. This means that when the server sends
a response upon a request from the client, there is no guarantee that the
same instance of the client will be receiving the response because the client
and server components have different and usually un-overlapped lifetimes.
When the server sends back the response, sometimes the original requestor
is gone (being terminated, for example). Therefore, the responses from the
server component are usually handled as another one-way messaging, as
opposite to the requests. The server can create a response object. You’ll see
an example of using a Notify object, a Queued Component, as a response
from the server later in this chapter when you look at the queueable
Northwind ordering application.
12 0789724588 CH09 10/25/00 4:57 PM Page 300
Exception Handling
If, for some reason, a message cannot be successfully delivered to its
intended destination, a poison message is generated, as you learned earlier
in this chapter. Queued Components handle poison messages by a series of
retries. As soon as you enable queuing in your COM+ application (refer to
Figure 9.7), the Queued Components generate a set of queues for your
Queued Components application. In the QC Demo example, the Queued
Components generated seven queues, as shown in Figure 9.17.
Figure 9.17: Seven queues are created for the QC Demo sample application.
The seven queues generated by the Queued Components fall into three cat-
egories:
• An application queue, also referred to as an input queue, for storing
messages from the recorder—the qc demo queue
• Five intermediate queues used for possible retries—qc_demo_0 to
qc_demo_4
NOTE
The queue names are case sensitive. The name of the application-specific queue is
actually QC Demo instead of qc demo. In the Computer Management snap-in, the queue
names are displayed in all lowercase, so lowercase is used to refer to queue names for
the sake of consistency.
When the listener fails three retry efforts in a particular retry queue,
COM+ moves the message to the next retry queue. If all the retry queues
are exhausted and the problem still persists, COM+ moves the message to
the final resting queue. At this point, the player issues a COM+ event to
notify interested parties that the message cannot be played. If an exception
class was specified for the Queued Component, the player calls the
FinalServerRetry function of the IPlaybackControl interface implemented
by the exception class. Exception classes will be discussed shortly.
TIP
You can selectively delete any or all the retry queues from the Computer Management
snap-in to customize the retry times and intervals to fit your specific needs.
12 0789724588 CH09 10/25/00 4:57 PM Page 302
In the General Declaration section of the class module, enter the following
line of code:
Implements IPlaybackControl
12 0789724588 CH09 10/25/00 4:57 PM Page 303
Click the Object drop-down list of the code window and select the
IPlaybackControl interface to generate the skeleton of the
IPlaybackControl_FinalClientRetry() subroutine. Select FinalServerRetry
from the Procedure drop-down list on the top-right corner of the code win-
dow to generate the skeleton of the IPlaybackControl_FinalServerRetry()
subroutine, as shown in Figure 9.19.
Now you are ready to add code to the subroutines that implements specific
business logic for handling exceptions. This example simply displays a mes-
sage box, indicating that either a client or a server final retry has failed.
Listing 9.3 illustrates the complete code in the Exception.cls.
Listing 9.3 Functions in Exception.cls
Option Explicit
Implements IPlaybackControl
You can specify either the ProgID or the CLSID of the exception class in
this box.
As you can see in Figure 9.21, you will add a couple of COM components,
Northwind_Ship.Ship (in the center of the illustration) and Northwind_
Notify.Notify (on the bottom-left side of the Northwind_Ship.Ship compo-
nent). Both are designed as Queued Components. You’ll also add a SQL
Server database named Shipping.
Figure 9.22 shows a simplified workflow focusing on the Queued
Components.
After the Order2 object finishes placing an order, including updating the
stock and checking the credit, it calls the ScheduleShipping() method
Queued Component, the Ship object. The Ship object is responsible for
inserting a shipping record into the Shipping table of the Shipping data-
base. Then the Ship object invokes a Notify() method of another Queued
Component, the Notify object. The Notify object displays a confirmation
message, indicating the shipping for a specific order has been arranged.
12 0789724588 CH09 10/25/00 4:57 PM Page 306
Order2 Ship
Client
Object Object
Shipping
Record
Record
Order
Northwind Shipping
Database Database
EXAMPLE
select ‘ShippingID’=@@identity
go
12 0789724588 CH09 10/25/00 4:57 PM Page 308
You also create a DSN named Shipping that points to the Shipping data-
base.
‘Process Shipping
sSQL = “Exec Shipping_Schedule @OrderID =” & iOrderID _
& “, @ProductName = ‘“ & sProductName & “‘“ _
& “, @Quantity = “ & iQuantity _
& “, @Shipper = ‘“ & sShipper & “‘“ _
12 0789724588 CH09 10/25/00 4:57 PM Page 309
‘Send Notification
sShipStatus = “Shipping has been scheduled for OrderID “ _
& CStr(iOrderID) & vbCrLf _
& “Shipping ID: “ & CStr(iShippingID) & “.”
Exit Sub
ScheduleShipping_Err:
App.LogEvent “Northwind_Ship.Ship Error #: “ _
& CStr(Err.Number) & vbCrLf _
& Err.Description & “.”
End Sub
The completed COM+ Queued Components application looks like the one in
Figure 9.24.
‘Arrange Shipping.
Set oShip = GetObject(“queue:/new:Northwind_Ship.Ship”)
oShip.ScheduleShipping iOrderID, _
sProductName, _
12 0789724588 CH09 10/25/00 4:57 PM Page 313
PlaceOrder = True
Exit Function
PlaceOrder_Err:
PlaceOrder = False
If Not oObjectContext Is Nothing Then
oObjectContext.SetAbort
End If
Err.Raise Err.Number
End Function
If you check the shipping database, you will find that the shipping record
has been added to the Shipping table with Shipping ID 10002 and Order ID
11113 (see Figure 9.27).
Figure 9.27: A shipping record has been inserted into the Shipping table.
12 0789724588 CH09 10/25/00 4:57 PM Page 315
Now start the Northwind Shipping COM+ application from the Component
Services by right-clicking the application and selecting Start. A notification
message from Northwind_Notify.Notify indicates that the shipping has
been scheduled (see Figure 9.28).
What’s Next
In this chapter, you learned about one of the most important features of
COM+: Queued Components. You also learned how easily you can add QCs
into an existing application. In Chapter 10, “COM+ Events,” you’ll learn
about another key feature of COM+: Loosely Coupled Events (LCEs).
13 0789724588 CH10 10/25/00 5:02 PM Page 316
13 0789724588 CH10 10/25/00 5:02 PM Page 317
10
COM+ Events
This chapter introduces another important feature of COM+: Loosely
Coupled Events, or LCE. COM+ events differ from traditional Tightly
Coupled Events (TCE) in that they extend the COM+ programming model
to support late bound events. COM+ Events service allows you to build
extensible event-driven applications in a configurable way.
This chapter teaches you the following:
• COM+ event basics
• How to build a simple LCE program
• How to incorporate LCE with the Northwind application
• Some advanced COM+ event topics
• When not to use LCE
13 0789724588 CH10 10/25/00 5:02 PM Page 318
Publisher Subscriber
In a TCE system, the publisher and subscriber must have great knowledge
of each other. The publisher and subscriber have overlapping lifetimes; they
both have to be up and running at the same time. The mechanism used in
the communication must be known in advance. In addition, TCE cannot fil-
ter events from either the publisher end nor the subscriber end. The events
13 0789724588 CH10 10/25/00 5:02 PM Page 319
Subscriber
Publisher Subscriber
COM+ Event System
Subscriber
NOTE
This chapter demonstrates how to use the Component Services snap-in to create a
COM+ event application, install and configure components, subscribe to events, and so
on. Chapter 11, “Administering COM+ Applications Programmatically,” discusses how to
manage COM+ events programmatically through the COM+ Administration object model.
This layer of abstraction decouples the tight relationship between the pub-
lisher and the subscriber in a TCE model and results in a number of advan-
tages. First, the event publisher and subscriber no longer need to be aware
13 0789724588 CH10 10/25/00 5:02 PM Page 320
of each other as long as both agree to a special event interface that is regis-
tered and managed by the COM+ event system. As such, the publisher and
the subscriber can be developed independently.
Second, the publisher and the subscriber can have totally different life-
times. A publisher can fire an event without having the subscriber up and
running and vice versa.
Finally, the communication and notification semantics are taken care of by
the COM+ event system, which greatly simplifies the event programming
model. The COM+ event system allows you to “configure” the event (such as
specify the event class, associate the event publishers and event sub-
scribers, and so on). In traditional event programming models (TCEs),
everything has to be predefined, more or less hard coded. In addition,
COM+ events also support event filtering, a way to designate event notifica-
tion only when certain events happen or when certain criteria are reached.
IYourEvent EventClass
IYourEvent Subscriber
Subscription
EVENTS
An event is a specific method defined in one of the interfaces of EventClass.
The same constraints as in Queued Components (QC) apply here as well.
That is, the event, or method, must have only input parameters and no
return values. In Visual Basic, this means you have to declare the argu-
ments of the event method as ByVal. The method has to be defined as a
Public Sub.
EVENT SUBSCRIBERS
A event subscriber is a normal COM component that implements the inter-
faces defined in EventClass. You write code in the corresponding event pro-
cedures that are responsible for processing the events according to specific
business requirements. The subscribers need to be installed into a COM+
application so that they can subscribe to appropriate events, either declara-
tively or programmatically.
SUBSCRIPTIONS
A subscription is stored in the COM+ event store. Subscribers create sub-
scriptions so that the COM+ event system can manage and deliver events.
Subscriptions contain information about the identity (SubscriptionID) and
location (MachineName) of subscribers, delivery method, event method
(MethodName), EventClass, and PublisherID property of an event class so
that the subscriber can receive events.
13 0789724588 CH10 10/25/00 5:02 PM Page 322
NOTE
Transient subscription cannot be created through the Component Services snap-in. You
have to use the COM+ Administration object model, which is described in Chapter 11,
“Administering COM+ Applications Programmatically.”
The third type of subscription, the per-user subscription, can deliver events
only for subscribers logged in to the machine where the event system
resides. When a subscriber logs on, the event system enables all the sub-
scriptions whose PerUser property is set to True and whose UserName prop-
erty is set to the name of the user who logged on. When the subscriber logs
off, the subscription is disabled.
NOTE
Per-user subscriptions work only in scenarios in which the publisher and subscribers
are on the same machine. The event system can detect logon and logoff only at the
publisher’s machine, not on the machine where the subscribers reside.
13 0789724588 CH10 10/25/00 5:02 PM Page 323
EVENT PUBLISHERS
An event publisher can be any application that is capable of interacting
with COM objects. Applications developed using Visual Studio tools, such as
Visual Basic, Visual C++, and Visual InterDev, can publish events. Other
examples of event publishers are Microsoft SQL Server and Microsoft
Exchange Server. To publish an event (sometimes called fire an event), all
the publisher needs to do is create an instance of the EventClass object and
call one of its methods.
These two events are similar. Both require two arguments: a string variable
for the stock symbol and a single variable for the stock value. Because
IEvent is an interface, an abstract class, you don’t have any implementation
for both events.
Next, compile the project to create the EventStock.dll. Now that you have
EventClass in place, you can register it with the COM+ event system. First,
in the Component Services snap-in, create an empty server COM+ applica-
tion and name it Stock EventClass.
Next, you need to register the EventClass object you just created. To do so,
expand the Stock EventClass COM+ application, right-click the
Components folder, and choose New, Components. After you view the
Welcome screen of the COM Component Install Wizard, click the Install
New Event Class(es) button (see Figure 10.5) and select EventStock.dll.
That’s basically all you need to do to get an event class registered with the
COM+ event system.
Now expand the Components folder; right-click the EventStock.IEvent com-
ponent, and select Properties. When you click the Advanced tab of the
Properties page, you will notice that a new section, LCE, has been added
(see Figure 10.6).
The Subscriber
With EventClass in place, you are now ready to build the subscriber. A sub-
scriber is an ActiveX DLL that implements the interfaces defined in
EventClass. Start a new ActiveX DLL project in Visual Basic, name it
EXAMPLE StockSubscriber, and name the class module Subscriber, as shown in
Figure 10.7.
Then set a reference to the EventClass object you created and registered
earlier (see Figure 10.8).
In the General Declaration section, add this one line of code:
Implements IEvent
This line allows you to expose the events defined in the interface in the
subscriber.
Listing 10.2 illustrates the implementation of the StockHigh and StockLow
events.
13 0789724588 CH10 10/25/00 5:02 PM Page 326
This listing simply displays a message box and also writes the event log for
debugging and troubleshooting purposes.
Now compile the project to make StockSubscriber.dll. Then you are ready to
subscribe to the event. To do so, create an empty server application in the
Component Services snap-in and name it Stock Event Subscriber. After
installing StockSubscriber.dll into this COM+ application, expand the
Components folder, right-click the Subscriptions folder, and choose New,
Subscription, as shown in Figure 10.9.
The Publisher
The event publisher in this example, the Stock Watcher program, is a
Standard EXE Visual Basic project. It has only one form with a timer con-
trol, as shown in Figure 10.14.
EXAMPLE
Set the Interval property of the timer to 2000 (2 seconds) and add the code
in the Timer event as in Listing 10.3.
13 0789724588 CH10 10/25/00 5:02 PM Page 330
This listing uses the timer control along with the Visual Basic Rnd function
to simulate three fictitious stock changes. It also declares a static counter
(an integer) to persist its value. Each time the Timer event fires, the
13 0789724588 CH10 10/25/00 5:02 PM Page 331
You can open the event log to see a confirmation event, as shown in
Figure 10.16.
Listing 10.5 contains the code for the subscriber; it simply displays a mes-
sage passed to use through the event. Of course, you need to set a reference
to the event you’re trying to implement (see Figure 10.21).
set nocount on
if @hr <> 0
begin
exec sp_displayoaerrorinfo @oInventory, @hr
return
end
return
end
This listing uses several SQL Server extended stored procedures for OLE
automation. sp_OACreate() allows you to create an instance of a COM
object; its function is similar to the CreateObject() function in Visual Basic.
sp_OAMethod lets you invoke the method of the COM object, using the object
token returned by sp_OACreate. The sp_OADestroy extended stored proce-
dure allows you to release the COM object; it works the same way as you
13 0789724588 CH10 10/25/00 5:02 PM Page 338
set the object to Nothing in Visual Basic. Due to the scope of this book, I
will not cover the details of how this OLE automation extended stored pro-
cedure works. You can refer to SQL Server Books Online for more infor-
mation.
TIP
If you study the OLE automation examples in the SQL Server documentation, you will
find that the extended stored procedure sp_OAGetErrorInfo is used to report errors
that occur during the OLE automation calls. Unfortunately, the information regarding the
errors returned by this stored procedure is too generic to be useful. Therefore, in this
stored procedure, you use another stored procedure for reporting errors, sp_dis-
playoaerrorinfo. This stored procedure, in turn, calls another stored procedure,
sp_hexadecimal, for converting the error code to hex values. You can find a detailed
explanation of these two stored procedures in the Microsoft SQL Server Programmer’s
Toolkit in the Microsoft Platform SDK documentation, which is available on the MSDN
Web site. The code for these two stored procedures appears in Listings 10.8 and 10.9.
You need to compile the code in these listings before you can compile the code in
Listing 10.7.
Now you’re ready to create the trigger that actually monitors the units in
stock for the Products table. Name the trigger tr_Products_U. Listing 10.10
contains the code of the trigger.
Listing 10.10 The tr_Products_U Trigger
if exists(select * from sysobjects where id =
object_id(‘tr_Products_U’))
drop trigger tr_Products_U
go
@BackOrder varchar(255),
@ProductName varchar(40)
/*****************************************************
Note: This trigger only takes care of single row
update. For multiple-row updates, we need to
use other techniques such as cursor to handle
each product.
******************************************************/
select @ProductID = i.ProductID,
@UnitsInStock = i.UnitsInStock
from inserted i
if @UnitsInStock < 10
begin
select @ProductName = ProductName from Products
where ProductID = @ProductID
select @BackOrder = ‘Product ID# ‘ +
convert(varchar(10),@ProductID) + ‘: ‘ + char(34)
+ @ProductName + char(34) + ‘ has only ‘ +
convert(char(1),@UnitsInStock)
+ ‘ units on stock, back order requested.’
exec Inventory_BackOrder @BackOrder = @BackOrder
end
go
To simplify the discussion, this listing deals only with single row updates in
the trigger. You may notice that an inserted table is referenced in this trig-
ger. This logic table is available only from triggers. An update in SQL
Server is treated by a delete followed by an insert. Before the update is per-
manently committed to the database, SQL Server keeps both copies of the
data in its transaction log and exposes the old data as a deleted table
(another logic table) and the new data as the inserted table. These two logic
tables, inserted and deleted, are available only from the triggers so that
you can check their values to decide whether you want to commit the
update or abort the change (called roll back in SQL Server). In this trigger,
you compare the new data with the lowest threshold, which is 10. If the
value after the update is lower than 10, the trigger fires an event by calling
the stored procedure Inventory_BackOrder.
A Test Drive
Figure 10.24 illustrates the results of the test of the Northwind application
with LCE enabled.
13 0789724588 CH10 10/25/00 5:02 PM Page 341
Event Filtering
COM+ provides event filtering capabilities, including publisher-side event
filtering and parameter filtering, which are explained in the following sec-
tions.
PUBLISHER FILTERING
A publisher can decide which subscriber should receive a specific event by
filtering the order and firing off an event method defined in an EventClass.
COM+ provides publisher filtering with a filter component that supports
one of the two publisher filtering interfaces.
If you want EventClass to be queueable, you must implement the
IMultiInterfacePublisherFilter interface by specifying the
MultiInterfacePublisherFilterCLSID property of the EventClass.
Next, set a reference in the project to the COM+ event system type library,
as shown in Figure 10.26.
Figure 10.26: Set a reference to the COM+ Event System Type Library in
the EventFilter project.
In the General Declaration section of the class module, enter the following
lines of code:
Private m_oEventControl As IEventControl
Private m_oSubscriptions As IEventObjectCollection
Implements IPublisherFilter
13 0789724588 CH10 10/25/00 5:02 PM Page 343
The first two lines in this code segment declare two module-level variables
to hold references to the IEventControl and IEventObjectCollection
objects. The last line implements the IPublisherFilter interface.
Listing 10.11 illustrates the implementation of the Initialize() and
PrepareToFire() methods.
Listing 10.11 The Initialize() and PrepareToFire() Methods of the Filter Component
Private Sub IPublisherFilter_Initialize(ByVal methodName As String, _
ByVal dispUserDefined As
Object)
End Sub
Dim i As Integer
For i = 0 To m_oSubscriptions.Count - 1
If m_oSubscriptions.Item(i).Name = “StockWatch” Then
firingControl.FireSubscription m_oSubscriptions(i)
End If
Next i
End Sub
For i = 0 To oApps.Count - 1
Debug.Print oApps.Item(i).Name
If oApps.Item(i).Name = “Stock EventClass” Then
Set oComps = oApps.GetCollection(“Components”,
oApps.Item(i).Key)
oComps.Populate
Exit For
End If
Next
For i = 0 To oComps.Count - 1
If oComps.Item(i).Name = “EventStock.dll” Then
Set oEvent = oComps.Item(i)
oEvent.Value(“MultiInterfacePublisherFilterCLSID”) _
= “EventFilter.Filter”
oComps.SaveChanges
Exit For
End If
Next
oApps.SaveChanges
PARAMETER FILTERING
The parameter setting can be set on the subscription side. To add filter cri-
teria, right-click the subscription and select Properties. Select the Options
tab and enter the Filter criteria, as shown in Figure 10.27.
13 0789724588 CH10 10/25/00 5:02 PM Page 345
NOTE
Setting the FireInParallel property of EventClass to True does not necessarily guar-
antee that the event will be delivered at the same time to multiple subscribers. It sim-
ply gives the COM+ event system the permission to do so.
Other Considerations
Although COM+ events provide a powerful means and simplified program-
ming model for developers to incorporate LCE into applications, you need to
be aware of a number of performance implications before you decide to use
LCE in these applications.
One of the major limitations of LCE is that calls from EventClass to sub-
scribers are synchronous. As a result, EventClass may be blocked from
other subscribers at a given time until it is done with a specific subscriber.
This blocking can propagate all the way back to the publisher, blocking the
event firing itself. Therefore, LCE is not suitable for multicast event notifi-
cation scenarios.
Another performance issue involved in LCE is that as long as EventClass
creates an instance of the subscriber, the subscriber will reside in memory
until the event system is shut down. Because the initiation of subscribers is
controlled internally by the COM+ event system, you cannot easily change
this behavior in code.
What’s Next
So far, you have learned about most of the COM+ features, including
Transaction Services in Chapter 6, the Compensating Resource Manager
(CRM) in Chapter 7, Queued Components (QC) in Chapter 9, and the
Loosely Coupled Events (LCE) in this chapter. Chapter 11, “Administering
COM+ Applications Programmatically,” introduces you to the COM+
Administration object model and teaches you how to perform administra-
tive tasks that may or may not be possible through the Component Services
snap-in.
13 0789724588 CH10 10/25/00 5:02 PM Page 347
14 0789724588 CH11 10/25/00 5:13 PM Page 348
14 0789724588 CH11 10/25/00 5:13 PM Page 349
11
Administering COM+ Applications
Programmatically
In Chapter 5, “Using the Component Services MMC Snap-In,” you learned
how to create and configure COM+ applications by using the Component
Services snap-in. In this chapter, you’ll learn how to programmatically
administer COM+ applications through the COM+ Administration object
model (or COMAdmin object model).
This chapter teaches you the following:
• How to use the COM+ catalog
• How the COMAdmin objects work
• How to perform basic administrative tasks by using the COMAdmin
objects
• How to create and configure COM+ applications for the Northwind
sample application
• How to use the Windows Scripting Host (WSH) to automate COM+
administration
14 0789724588 CH11 10/25/00 5:13 PM Page 350
Authority Concerns
You need to have Administrator’s privileges on any machine for which you
want to change any data to the COM+ catalog. This applies to using both
the Component Services snap-in and the COMAdmin objects.
objects, you can also set some configurations that you cannot do by using
the Component Services snap-in, such as creating a transient event sub-
scription.
Most programming object models you are familiar with offer a hierarchical
representation of the entities they map to. For example, in the Jet Data
Access Objects (DAO), you specify a Workspace object through the top-level
DBEngine object. Then you call a CreateDatabase() method of the Workspace
object to instantiate an instance of the Database object. From there, you can
access the table objects, query objects, and so on by calling the appropriate
methods of the Database objects. In the Remote Data Access Objects (RDO),
you use the Environment, Connection, and Resultset objects to access data-
base objects in a hierarchical manner, using a specific object at a specific
hierarchical level. Other examples include ActiveX Data Objects (ADO) and
SQL Server Distributed Management Objects (SQL-DMO).
NOTE
The COMAdminCatalogCollection and COMAdminCatalogObject objects are “generic”
objects, meaning that they can be used to represent any objects in the COMAdmin
object model hierarchy (except for the root object, COMAdminCatalog) in a recursive
manner.
COM+ Catalog
Applications
Components
InterfacesForComponent
MethodsForInterface
RolesForMethod
RolesForInterface
RolesForComponent
SubscriptionsForComponent
PublisherProperties
SubscriberProperties
Roles
UsersInRole
TransientSubscriptions
PublisherProperties
SubscriberProperties
Root
ApplicationCluster
ComputerList
DCOMProtocols
InProcServers
LocalComputer
is stored in a memory cache. All the changes you made through the
COMAdmin objects do not persist to the COM+ catalog data store until you
call the SaveChanges method of the collection. The Populate method
retrieves the data from the COM+ catalog data store and refreshes the data
stored in the cache. The effect of the Populate method is similar to the
Requery method of the ADO Recordset object. That is, if the collection is cre-
ated the first time, a call to Populate “populates” the cache with the data
retrieved from the catalog data store. After you manipulate the data in the
cache and call the Populate method before calling the SaveChanges method,
all the changes to the cache are lost, and the data retrieved from the cata-
log data store overwrites the data you changed in the cache. On the other
hand, this result can be useful if you want to discard the changes. You can
simply call Populate before calling SaveChanges.
CAUTION
One mistake developers often make is forgetting to call the Populate method after
retrieving a collection. The collection remains empty unless you call Populate. The
problem is that this mistake is very difficult to detect. Any attempt to enumerate the
collection doesn’t generate an error. You don’t get reliable results either.
Listing 11.1 Enumerating the COM+ Applications, Components, Interfaces, Methods, Roles,
Users, and Event Subscriptions in a Computer
Private Sub DisplayCOMPlusInfo()
Dim oCatalog As COMAdminCatalog
Dim oApps As COMAdminCatalogCollection
Dim oApp As COMAdminCatalogObject
Dim oComps As COMAdminCatalogCollection
Dim oComp As COMAdminCatalogObject
Dim oIFs As COMAdminCatalogCollection
Dim oIF As COMAdminCatalogObject
Dim oMethods As COMAdminCatalogCollection
Dim oMethod As COMAdminCatalogObject
Dim oRoles As COMAdminCatalogCollection
Dim oRole As COMAdminCatalogObject
Dim oUsers As COMAdminCatalogCollection
Dim oUser As COMAdminCatalogObject
Dim oSubs As COMAdminCatalogCollection
Dim oSub As COMAdminCatalogObject
Dim sComplus As String
Screen.MousePointer = vbHourglass
‘Get connect to the COM+ Catalog Server.
Set oCatalog = New COMAdmin.COMAdminCatalog
oComp.Key)
‘Populate the subscriptions.
oSubs.Populate
‘Loop through the subscriptions.
For Each oSub In oSubs
sComplus = sComplus & vbTab & vbTab _
& “Subscription: “ & oSub.Name & vbCrLf
Next
Next
Screen.MousePointer = vbDefault
MsgBox “COM+ Applications information in this machine is saved in “ _
& sFile & “.”
End Sub
The output of the code in Listing 11.1 is written to an external text file
named ComplusApps.txt. Figure 11.3 shows a portion of the
ComplusApps.txt file that lists the components, interfaces, methods, and
event subscriptions in the Northwind Inventory Subscriber COM+ applica-
tion you created in Chapter 10, “COM+ Events.”
Figure 11.4 shows what the same COM+ application looks like in the
Component Services snap-in.
14 0789724588 CH11 10/25/00 5:13 PM Page 359
Figure 11.3: Part of the file generated by the code in Listing 11.1 that lists
information about the Northwind Inventory Subscriber COM+ application.
Let’s see how it works. The first part of Listing 11.1 declares local variables
used in this routine:
Dim oCatalog As COMAdminCatalog
Dim oApps As COMAdminCatalogCollection
Dim oApp As COMAdminCatalogObject
Dim oComps As COMAdminCatalogCollection
Dim oComp As COMAdminCatalogObject
Dim oIFs As COMAdminCatalogCollection
Dim oIF As COMAdminCatalogObject
Dim oMethods As COMAdminCatalogCollection
Dim oMethod As COMAdminCatalogObject
Dim oRoles As COMAdminCatalogCollection
Dim oRole As COMAdminCatalogObject
Dim oUsers As COMAdminCatalogCollection
Dim oUser As COMAdminCatalogObject
Dim oSubs As COMAdminCatalogCollection
Dim oSub As COMAdminCatalogObject
Dim sComplus As String
You can see that except for the oCatalog object that is declared as the
COMAdminCatalog object and the sComplus string variable that is used to
store the content of the COM+ catalog returned by this routine, most of the
variables are pairs of COMAdminCatalogCollection and
COMAdminCatalogObject objects. These same types of objects are used in this
routine over and over to traverse the COM+ catalog hierarchical structure
to retrieve information at different levels.
14 0789724588 CH11 10/25/00 5:13 PM Page 360
After the variable declaration, the following line of code creates an instance
of the COMAdminCatalog object and establishes a session with the catalog
server:
Set oCatalog = New COMAdmin.COMAdminCatalog
Then the following code returns the Applications collection by calling the
GetCollection method of the COMAdminCatalog object:
Set oApps = oCatalog.GetCollection(“Applications”)
oApps.Populate
Notice that the Populate method is called to guarantee that the oApps col-
lection isn’t empty. The oApps collection is the top-most collection in this
sample routine; it represents all the COM+ applications installed in the
machine where this routine is running.
Then you start this top-most loop to enumerate all the COM+ applications
installed in this machine:
For Each oApp In oApps
sComplus = sComplus & vbCrLf & “Application: “ _
& oApp.Name & vbCrLf
Inside the top-most loop, you return Components for each application by call-
ing the GetCollection method of the Applications collection of that applica-
tion:
Set oComps = oApps.GetCollection(“Components”, oApp.Key)
oComps.Populate
Here, you pass the name of the collection as Components and a key value of
the application. Again, you called the Populate method right after the col-
lection is returned.
Then you loop through each component and append the information about
components to the string variable:
For Each oComp In oComps
sComplus = sComplus & vbTab & “Component: “ _
& oComp.Name & vbCrLf
sComplus = sComplus & vbTab & “Role: “ & oRole.Name & vbCrLf
Set oUsers = oRoles.GetCollection(“UsersInRole”, oRole.Key)
oUsers.Populate
For Each oUser In oUsers
sComplus = sComplus & vbTab & vbTab & “User: “ & oUser.Name & vbCrLf
Next
Next
Notice that you reuse the oRole and oRoles variables in this routine three
times.
After you finish enumerating collections and items for the COM+ applica-
tions, you write the contents to an external text file like this:
Open sFile For Output As #1
Print #1, sComplus
Close #1
Figure 11.3 displays only a partial list of the contents of the output file,
with one COM+ application shown in the figure.
NOTE
If you use early binding to create the COMAdminCatalog object (by using the New key-
word), you need to set a reference to the COM+ 1.0 Admin Type Library, as shown in
Figure 11.5, in your project.
Figure 11.5: Setting a reference to the COM+ 1.0 Admin Type Library in
the project.
Listing 11.2 Displaying the Detailed Subscription Information for the StockWatch Subscription
Private Sub GetStockWatchSubscription()
On Error GoTo COMAdmin_Err
COMAdmin_Err:
MsgBox Err.Description
End Sub
Figure 11.6 shows the contents of the debug window after the execution of
the routine in Listing 11.2.
Figure 11.6: The debug window displays the properties of the StockWatch
subscription.
Registering a Subscription
Listing 11.3 demonstrates how you can create the StockWatch subscription
by using the COM+ 1.0 Admin Type library. In Chapter 10, you used the
EXAMPLE Component Services snap-in.
Listing 11.3 Creating a StockWatch Subscription
Private Sub CreateStockWatchSubscription()
On Error GoTo COMAdmin_Err
oSubs.SaveChanges
MsgBox “The StockWatch subscription is created!”
Exit Sub
COMAdmin_Err:
MsgBox Err.Description
End Sub
Here, you first loop through the Applications collection to find the Stock
Event Subscriber COM+ application and then search the Components col-
lection to get the StockSubscriber.Subscriber component. Then you call the
GetCollection method against the Components collection and ask to return
a Subscriptions collection by specifying the name of the collection as
SubscriptionsForComponent:
Set oSubs = oComps.GetCollection(“SubscriptionsForComponent”, oComp.Key)
14 0789724588 CH11 10/25/00 5:13 PM Page 365
After setting the properties for the subscription, you persist the change
back to the COM+ catalog data store by calling the SaveChange method
against the Subscriptions collection:
oSubs.SaveChanges
CAUTION
Before you run the code in Listing 11.3, I recommend that you back up the existing
COM+ application by exporting the Stock Event Subscriber, as I described in Chapter 5.
Debug.Print oColInfo.Name
Next
End Sub
Figure 11.7 shows the results in the debug window. You can see that the
Applications collection has five related collections: Components, Roles,
RelatedCollectionInfo, PropertyInfo, and ErrorInfo.
Figure 11.7: The debug window displays the results of the code in
Listing 11.4.
You are going to create and configure five COM+ applications for the
Northwind application by using the COMAdmin objects. Figure 11.9 shows
the COM+ applications you created in previous chapters by using the
Component Services snap-in.
CAUTION
Before you test the code here, back up all five Northwind COM+ applications by export-
ing the Stock Event Subscriber as described in Chapter 5, “Using the Component
Services MMC Snap-In.” Preserving the existing configuration for the Northwind applica-
tion is critical because you will use all the COM+ services you built for the Northwind
application in Chapter 13, “Northwind Traders Online: A COM+ Enabled Windows DNA
2000 Application.”
Configuring Transactions
For this example, you are going to create a COM+ application named
Northwind and install several transactional components in it. Configuring
EXAMPLE transactional components is straightforward. If you design your compo-
nents appropriately, COM+ automatically picks up the transactional attrib-
utes you set at design time which is the MTSTransactionMode property of the
Visual Basic class module.
14 0789724588 CH11 10/25/00 5:13 PM Page 368
The code in Listing 11.5 shows how to create a COM+ application and
install components into it.
Listing 11.5 Creating the Northwind COM+ Application and Installing Transactional Components
Private Sub cmdNorthwind_Click()
Dim oCatalog As COMAdminCatalog
Dim oApplications As COMAdminCatalogCollection
Dim oApplication As COMAdminCatalogObject
Dim sApplication As String
Dim i As Integer
“C:\QUE\Chap06\Src\Northwind_Product.dll”, _
“”, _
“”
oApplications.SaveChanges
Screen.MousePointer = vbDefault
MsgBox “Components Installed”
Exit Sub
COMAdminNorthwind_Err:
Dim oErrors As COMAdminCatalogCollection
Dim oError As COMAdminCatalogObject
Dim sError As String
Set oErrors = oApplications.GetCollection(“ErrorInfo”, “”)
oErrors.Populate
sError = Err.Description & vbCrLf
For Each oError In oErrors
sError = sError & oError.Name & vbCrLf
Next
Screen.MousePointer = vbDefault
MsgBox sError
End Sub
In the first part of the code in Listing 11.5, you check for the existence of
the application you’re trying to create. If one is found, you delete it by call-
ing the remove method of the Applications collection, passing the index of
the application in the collection. Then you save the changes back to the
COM+ catalog data store:
sApplication = “Northwind”
Then you add the application to the Applications collection returned earlier
by calling the Add method, specifying the name of the application, and
saving the changes:
‘Add a new COM+ application
Set oApplication = oApplications.Add
oApplication.Value(“Name”) = sApplication
oApplications.SaveChanges
After the COM+ application is created, you install three ActiveX DLLs into
the COM+ application:
‘Install the Data.Access component.
oCatalog.InstallComponent sApplication, _
“C:\QUE\Chap06\Src\Data.dll”, _
“”, _
“”
Because COM+ can pick up the transactional attributes set up for compo-
nents at design time, you do not explicitly specify these transaction attrib-
utes for the components in code. If you want to set the transaction
attributes to something different than the design-time settings, you need to
specify them in code by setting the Transaction property of the components.
For example, the following code segment sets the transaction attribute for
the Northwind_Order.Order component to Requires New:
Set oComponents = oApplications.GetCollection(“Components”, oApplication.Key)
For Each oComponent In oComponents
If oComponent.Name = “Northwind_Order.Order” Then
14 0789724588 CH11 10/25/00 5:13 PM Page 371
oComponent.Value(“Transaction”) = COMAdminTransactionRequiresNew
Exit For
End If
Next
One more thing I want to mention about this example is its error handler.
The following segment demonstrates how you can use the ErrorInfo
collection:
COMAdminNorthwind_Err:
Dim oErrors As COMAdminCatalogCollection
Dim oError As COMAdminCatalogObject
Dim sError As String
Set oErrors = oApplications.GetCollection(“ErrorInfo”, “”)
oErrors.Populate
sError = Err.Description & vbCrLf
For Each oError In oErrors
sError = sError & oError.Name & vbCrLf
Next
Screen.MousePointer = vbDefault
MsgBox sError
If you want to get the ErrorInfo collection, you call the GetCollection
method of the collection for which you want to collect errors; then you spec-
ify ErrorInfo as the name of the collection and an empty string (“”) as the
key value. The error handler appends any error in the ErrorInfo collection
to the standard error to give you a comprehensive error report when the
error occurs.
After executing the code in Listing 11.5, you can check the results in the
Component Services snap-in.
“”, _
“”
oApplications.SaveChanges
Screen.MousePointer = vbDefault
MsgBox “CRM Components Installed”
Exit Sub
COMAdminNorthwindCRM_Err:
Dim oErrors As COMAdminCatalogCollection
Dim oError As COMAdminCatalogObject
Dim sError As String
Set oErrors = oApplications.GetCollection(“ErrorInfo”, “”)
oErrors.Populate
sError = Err.Description & vbCrLf
For Each oError In oErrors
sError = sError & oError.Name & vbCrLf
Next
Screen.MousePointer = vbDefault
MsgBox sError
End Sub
The code in Listing 11.6 is similar to the code in Listing 11.5 with one
exception—it specifies that the COM+ application is CRM-enabled:
oApplication.Value(“CRMEnabled”) = True
You can verify the results by checking the Northwind CRM COM+ applica-
tion in the Component Services snap-in.
Installing an EventClass
For the example shown in Listing 11.7, you are going to create a COM+
application named Northwind Inventory EventClass and install an
EXAMPLE EventClass named Northwind_Inventory.Inventory in the COM+ appli-
cation.
Listing 11.7 Creating the Northwind Inventory EventClass COM+ Application and installing the
Northwind_Inventory.Inventory EventClass
Private Sub cmdNorthwindInvEvent_Click()
Dim oCatalog As COMAdminCatalog
Dim oApplications As COMAdminCatalogCollection
Dim oApplication As COMAdminCatalogObject
14 0789724588 CH11 10/25/00 5:13 PM Page 374
oApplications.SaveChanges
Screen.MousePointer = vbDefault
MsgBox “Inventory EventClass Installed”
Exit Sub
COMAdminNorthwindInvEvent_Err:
Dim oErrors As COMAdminCatalogCollection
Dim oError As COMAdminCatalogObject
Dim sError As String
Set oErrors = oApplications.GetCollection(“ErrorInfo”, “”)
oErrors.Populate
sError = Err.Description & vbCrLf
For Each oError In oErrors
sError = sError & oError.Name & vbCrLf
Next
14 0789724588 CH11 10/25/00 5:13 PM Page 375
Screen.MousePointer = vbDefault
MsgBox sError
End Sub
Again, the code here is straightforward and similar to the two examples
you saw earlier. The only exception is that this time you call the
InstallEventClass method instead of the InstallComponent method of the
COMAdminCatalog object:
oCatalog.InstallEventClass sApplication, _
“C:\QUE\Chap10\Src\Northwind_Inventory.dll”, _
“”, _
“”
After running the code in Listing 11.7, you can verify that the component is
actually installed as an EventClass by checking the Advanced tab of its
Properties page in the Component Services snap-in. You should see the
LCE group on the Advanced tab, as shown in Figure 11.10.
End If
Next
Screen.MousePointer = vbDefault
MsgBox “Subscriber Component Installed”
Exit Sub
COMAdminNorthwindSubscriber_Err:
Dim oErrors As COMAdminCatalogCollection
Dim oError As COMAdminCatalogObject
Dim sError As String
Set oErrors = oApplications.GetCollection(“ErrorInfo”, “”)
oErrors.Populate
sError = Err.Description & vbCrLf
For Each oError In oErrors
sError = sError & oError.Name & vbCrLf
Next
Screen.MousePointer = vbDefault
MsgBox sError
End Sub
The first half of this example is again straightforward; you should be able
to understand it without requiring any further explanation. The second half
adds an event subscription. It is similar to the earlier example of adding
the StockWatch event subscription. The difference is that in the StockWatch
example you added an event subscription for an existing component. Here,
you install the component first and then add the subscription.
Notice that you also add the following line of code:
oSubscription.Value(“MethodName”) = “InventoryLow”
This line names the specific method (or event) you’re interested in. Naming
the method is not necessary in this example because the EventClass has
only one event: InventoryLow. If the EventClass has multiple events, as in
the StockWatch example, and you don’t specify MethodName, you end up
14 0789724588 CH11 10/25/00 5:13 PM Page 378
“C:\QUE\Chap09\Src\Northwind_Notify.dll”, _
“”, _
“”
‘Get the Northwind_Notify.Notify component we just installed.
Set oComponents = oApplications.GetCollection(“Components”, _
oApplication.Key)
oComponents.Populate
Screen.MousePointer = vbDefault
MsgBox “QC Components Installed”
Exit Sub
COMAdminNorthwindShipping_Err:
Dim oErrors As COMAdminCatalogCollection
Dim oError As COMAdminCatalogObject
14 0789724588 CH11 10/25/00 5:13 PM Page 381
Second, you need to set the QueuingEnabled property for the interface to
True:
oInterface.Value(“QueuingEnabled”) = True
To verify the result of the code, open the Component Services snap-in.
Locate and right-click the Northwind Shipping application you just created;
then select the Queuing tab. Both the Queued and Listen check boxes
should be checked, as shown in Figure 11.11.
You can also verify that the Queued property of the _Ship and _Notify inter-
faces are also set, as shown in Figure 11.12.
14 0789724588 CH11 10/25/00 5:13 PM Page 382
To execute the scripting file, you can simply double-click it from the
Windows Explorer. Alternatively, you can execute the following command
from the DOS prompt:
Cscript //i \C:\Que\Chap11\Src\InstallNorthwindInvSubscriber.vbs
The DOS prompt window in Figure 11.13 shows the results of the script
upon execution.
What’s Next
Chapter 12, “More on COM+ Programming”, will introduce some more
COM+ features. Visual Basic 6.0 does not support some of these COM+ fea-
tures. The next chapter will also explain what happens to some other fea-
tures introduced in the beta version of COM+ specification. You’ll also
learn about some other issues involved in COM+ programming.
15 0789724588 CH12 10/25/00 5:08 PM Page 386
15 0789724588 CH12 10/25/00 5:08 PM Page 387
12
More on COM+ Programming
This last chapter in Part II, “Developing COM+ Application Components,”
will introduce you to some other COM+ features, including something that
has already been introduced in the Microsoft Transaction Server (MTS). It
also covers material that was originally available in the beta release of
COM+ 1.0 but has either been redeployed or simply been removed from the
final release.
This chapter teaches you the following:
• The basics of the object constructor string
• The basics of the Shared Property Manager (SPM)
• How to debug in COM+
• How object pooling works
• Some missing pieces from COM+ 1.0 beta release
15 0789724588 CH12 10/25/00 5:08 PM Page 388
If object construction is enabled and the construction string is set for the
component in the COM+ application, the Construct method is called when
an instance of the Data.Access component is created. When the Construct
method is called, it stores the construction string in the module-level vari-
able m_sConnection so that it becomes available for other methods that will
use the connection string to connect to the database.
15 0789724588 CH12 10/25/00 5:08 PM Page 389
Now you need to modify the two methods of the Data.Access component.
Listing 12.1 shows the modified methods, with the newly added code high-
lighted.
Listing 12.1 The Modified ExecSQL Method and the GetResult Method of the Data.Access
Component
‘******************************************************************************
‘ ExecSQL()
‘ Purpose: Execute SQL statements and/or stored procedures.
‘ Inputs: sConnection -- ADO connection string.
‘ sSQL -- SQL statement.
‘ Outputs: sErrorMessage -- Error details.
‘ Returns: A boolean variable indicates if the action is successful.
‘ Modification History
‘ Date Initials Description
‘ 05/08/00 PLI Created.
‘ 07/10/00 PLI Added Object Constructor String
‘******************************************************************************
‘Clean up.
If Not oConnection Is Nothing Then Set oConnection = Nothing
End Function
‘******************************************************************************
‘ GetResult()
‘ Purpose: Get an ADO recordset from a SQL action.
‘ Inputs: sConnection -- ADO connection string.
‘ sSQL -- SQL statement.
‘ Outputs: lRows -- Rows returned.
‘ sErrorMessage -- Error details.
‘ Returns: An ADO Recordset as a result of a SQL action.
‘ Modification History
‘ Date Initials Description
‘ 05/08/00 PLI Created.
‘ 07/10/00 PLI Added Object Constructor String
‘******************************************************************************
.CursorLocation = adUseClient
.CursorType = adOpenStatic
.LockType = adLockBatchOptimistic
.ActiveConnection = oConn
.Open sSQL
‘Clean up.
On Error Resume Next
Set oConn = Nothing
Set rsGetResult = Nothing
End Function
As you can see, both methods work in the same way in respect to using the
constructor string. If the module-level connection string variable is set, the
methods use it to overwrite the input parameter named sConnection.
Now you can save the project, recompile Data.dll, and refresh the
Data.Access component in the COM+ application named Northwind. The
most reliable way of refreshing an object in a COM+ application is to delete
it from the COM+ application and reinstall it (you’ll learn about this task
later in this chapter).
Next, you need to enable object construction and set the constructor string
for the Data.Access component in the Northwind COM+ application. To do
so using the Component Services, expand the Northwind COM+ applica-
tion, right-click the Data.Access component, and select Properties. Click the
Activation tab, select the Enable Object Construction check box, and enter
a ADO connection string, as shown in Figure 12.2.
Figure 12.2: Enabling object construction and setting the constructor string
on the Properties page.
Listing 12.2 Creating the Northwind COM+ Application and Installing Transactional Components
with Object Construction Enabled and the Constructor String Set
Private Sub cmdNorthwind_Click()
Dim oCatalog As COMAdminCatalog
Dim oApplications As COMAdminCatalogCollection
Dim oApplication As COMAdminCatalogObject
Dim sApplication As String
Dim i As Integer
“C:\QUE\Chap06\Src\Northwind_Product.dll”, _
“”, _
“”
oApplications.SaveChanges
Screen.MousePointer = vbDefault
MsgBox “Components Installed”
Exit Sub
COMAdminNorthwind_Err:
Dim oErrors As COMAdminCatalogCollection
Dim oError As COMAdminCatalogObject
Dim sError As String
Set oErrors = oApplications.GetCollection(“ErrorInfo”, “”)
oErrors.Populate
sError = Err.Description & vbCrLf
For Each oError In oErrors
sError = sError & oError.Name & vbCrLf
Next
Screen.MousePointer = vbDefault
MsgBox sError
End Sub
15 0789724588 CH12 10/25/00 5:08 PM Page 396
TIP
Prior to COM+, storing and retrieving the database connection string in a three-tiered
application could be a little tricky. You can store the connection string in many places.
For example, you can store it in the System Registry or in a system DSN or a file DSN.
The clients or other components then can retrieve the connection information from
either the System Registry or from a DSN and pass it to the data object. Using the
object construction feature of COM+, however, you can remove the responsibility of stor-
ing the connection string from the component developer. At deployment time, the sys-
tem administrator can dynamically decide the connection string and administratively set
it up for the data access component using object construction. This capability offers
better performance and flexibility.
SharedPropertyGroupManager
SharedPropertyGroup
SharedProperty
First, set a reference to the COM+ Services Type Library in the Visual
Basic project and set the MTSTransactionMode property of the class module
to 4-RequriesNewTransaction.
Add code in the class module to implement a method named
GetNextTransNo() for the component, as shown in Listing 12.3.
Process, _
bResult)
oSPMProperty.Value = oSPMProperty.Value + 1
If Not oObjectContext Is Nothing Then
oObjectContext.SetComplete
End If
GetNextTransNo = oSPMProperty.Value
Exit Function
ErrorHandler:
If Not oObjectContext Is Nothing Then
oObjectContext.SetAbort
End If
GetNextTransNo = -1
Err.Raise Err.Number, m_sObjectName, Err.Description
End Function
The code first creates an instance of the shared property group manager,
oSMP, using the New keyword. It then creates a shared property group
named TransNo by calling the CreatePropertyGroup method of the Shared
Property Manager.
The CreatePropertyGroup method takes four parameters. The first parame-
ter defines the name of the shared property group.
The second parameter defines the locking behavior of the Shared Property
Manager. You can set it to either LockSetGet (0, the default) or LockMethod.
LockSetGet locks a property during a Value call (get_Value or put_Value),
ensuring that every get or set operation is atomic. This guarantees that no
two clients can access the same property at the same time but still allows
other clients to concurrently access other properties in the same group. The
LockMethod locks all the properties in the shared property group for exclu-
sive use by the caller as long as the caller’s current method is executing.
15 0789724588 CH12 10/25/00 5:08 PM Page 399
The third parameter specifies the effective release mode for a shared prop-
erty group, either Standard (0, the default), which automatically destroys
the property group after all clients have released their references on the
property group; or Process (1), which doesn’t destroy the property group
until the process that creates it has terminated.
The fourth parameter is an output parameter, a Boolean type that indicates
whether the property group you tried to create already exists. It returns
True if the property group already exists and False if it does not.
After creating the property group, you then create a shared property named
TransNo by calling the CreateProperty method of the shared property group.
The CreateProperty method takes two parameters: an input parameter that
specifies the name of the property you want to create and an output para-
meter that indicates whether the property already exists. If the TransNo
property does not exist prior to the call, you set the initial transaction num-
ber to 10000.
The next line of code increments the TransNo by one each time the method
is called.
Now you’re ready to compile BankTrans.dll and test it. To do so, start
another Visual Basic Standard EXE, place a command button on the form,
and put some code in the Click event of the command button like this:
Private Sub Command1_Click()
Dim oSPM As Object
Dim lRetVal As Long
lRetVal = oSPM.GetNextTransNo()
Name both the project and the form TestSPM and make an executable
named TestSPM.exe.
COM+ greatly enhanced the debugging capability for Visual Basic, even let-
ting you test the SPM behavior without installing the component into a
COM+ application. To test it, go back to the BankTrans.vbp project and
press Ctrl+F5 to start the component. Now start two instances of
TestSPM.exe and click the command buttons on each form in turn. You will
notice that the transaction number keeps increasing with every click of the
command button, no matter which command button you click.
15 0789724588 CH12 10/25/00 5:08 PM Page 400
NOTE
If you are not running the Visual Basic instance of the BankTrans.TransNumber com-
ponent and try to test it without installing the component into a COM+ application, you
will get an error, as shown in Figure 12.5.
Figure 12.5: Testing the SPM without running a Visual Basic instance of
the component and installing it into a COM+ application will result in an
error.
After fully testing the component, you can install it in a COM+ application.
You can create an empty server application named SPM and installed the
BankTrans.TransNumber component there (see Figure 12.6).
TIP
The SPM stores the shared property data in the memory of the process address space
of the COM+ application that hosts the component. As a result, if the COM+ process
shuts down by itself due to the timeout, every shared property is gone. In the example,
the transaction number is reset to 10000 again. To prevent this situation from happen-
ing, especially during the development of the components, set the timeout period to a
longer time than the default 3 minutes. To do so, right-click the COM+ application,
select Properties, and then click the Advanced tab. Change Minutes Until Idle
Shutdown to a number greater than the default 3 or select Leave Running When Idle if
you are in the development stage (see Figure 12.7).
15 0789724588 CH12 10/25/00 5:08 PM Page 401
‘............
‘do some work here
‘............
ErrorHandler:
‘if something is wrong, rollback the transaction.
‘check to make sure the Object exists first.
15 0789724588 CH12 10/25/00 5:08 PM Page 402
This workaround is no longer necessary for the COM+ component. You can
directly call the SetComplete or SetAbort method by using the following
shortcut syntax, even though the component has not been installed into a
COM+ application yet:
GetObjectContext.SetComplete
or
GetObjectContext.SetAbort
In COM+, this is no longer the case. You can directly put your startup code
inside the Class_Initialize event and the shutdown code inside the
Class_Terminate event. You can also set breakpoints inside these events
and use other Visual Basic IDE debugger facilities such as watches.
COM+ also allows you to debug multiple components. A client can call any
number of DLLs running in the same project group or in separate project
15 0789724588 CH12 10/25/00 5:08 PM Page 403
groups. The objects in the grouped DLL projects can call each other arbi-
trarily, flowing context as needed. This was not possible in the MTS world.
In addition, as I mentioned earlier, COM+ also supports debugging for the
SPM components.
NOTE
When the components are running under debug mode, COM+ treats them as if they
were running inside a COM+ library application. All limitations related to library applica-
tions apply.
NOTE
If you have the Visual Basic IDE and COM+ installed on the same machine, a
Component Services add-in shows up under the Visual Basic IDE’s Add-Ins menu, as
shown in Figure 12.8. It replaces the Microsoft Transaction Service add-in for MTS
under Windows NT 4.0. The purpose of this add-in is to update the Registry each time
you recompile a component that is installed in an MTS package or a COM+ application.
This add-in facility is no longer reliable for COM+ components, however, because COM+
may store configuration information in both the System Registry and COM+ registration
database (RegDB). It is strongly recommended that you manually uninstall and reinstall
the components after each recompilation.
Figure 12.8: The Component Services add-in in the Visual Basic IDE.
In spite of the great enhancements of COM+, the Visual Basic IDE still has
some debugging limitations, including multithreading, component tracking,
remote calls, and process isolation. Under these circumstances, you may
need to use other tools to overcome the limitations of the Visual Basic
debugger. One of these tools is Visual C++ 6.0, another member of the
Visual Studio 6.0 suite.
You can also leverage the debugging capability of Visual C++ 6.0 to debug
the compiled components—that is, the DLLs.
15 0789724588 CH12 10/25/00 5:08 PM Page 404
Let’s use the Data.Access example again to see how to debug a compiled
Visual Basic component using the Visual C++ debugger. First, you need to
set up several properties for the component in the Visual Basic project. To
get started, open the BankTrans.vbp project in Visual Basic, select File, and
click Make Data.dll.... In the Make Project dialog, click the Options button
and then select the Compile tab. Select Compile to Native Code, No
Optimization, and Create Symbolic Debug Info, as shown in Figure 12.9.
Click OK to close the Project Properties dialog box. Click OK again to make
BankTrans.dll; overwrite the old one when prompted. Next, close the Visual
Basic project. Then delete the original Data.Access component from the
Northwind COM+ application and reinstall it from Data.dll.
Now open a new Visual Basic Standard EXE project, name it
VCDebuggerTest, and name the form frmVBDebuggerTest. Place a command
button on the form and type the code in the Click event of the command
button as shown in Listing 12.4.
Listing 12.4 The Click Event of the Testing Client
Private Sub Command1_Click()
Dim oData As Object ‘Data.Access
Dim oRS As Object ‘ADO Recordset.
Dim sSQL As String
Dim lRows As Long
End Function
15 0789724588 CH12 10/25/00 5:08 PM Page 405
Set the same compilation configurations as you did for Data.dll (Compile to
Native Code, No Optimization, and Create Symbolic Debug Info) and com-
pile the VCDebuggerTest.exe. Then close the Visual Basic project.
Now you are ready to debug Data.dll by using the Visual C++ IDE. To do
so, start the Visual C++ 6.0 IDE from the Visual Studio 6.0 groups and
select File, Open Workspace. Then specify All Files (*.*) as the Files of type,
browse to Data.dll, and click Open. A workspace is created, as shown in
Figure 12.10.
Select File, Open and specify All Files(*.*). Browse to the Access.cls file and
click Open. The source code of the Access.cls class module then becomes
available in the Visual C++ IDE. You can set the breakpoint by placing
the cursor in the line where you want to set it and pressing F9 (see
Figure 12.11).
Select Project, Settings. Next, select the Debug tab and specify the fully
qualified path for Dllhost.exe, followed by the ProcessID, as in the following
syntax:
C:\WINNT\System32\Dllhost.exe /ProcessID:{ProcessID}
You can obtain the ProcessID from the Component Services snap-in. To do
so, expand the Northwind COM+ application and right-click the
15 0789724588 CH12 10/25/00 5:08 PM Page 406
Then switch back to the Visual C++ IDE and paste the ProcessID as part of
the parameters for Dllhost.exe, as shown in Figure 12.13.
Figure 12.13: Specifying the path for Dllhost.exe in the Executable for
Debug Session box.
Now you can start debugging the compiled Data.dll by pressing F5. All the
Visual C++ debugging features become available, such as multithreading
debugging.
Object Pooling
Object pooling is also a new feature of COM+. It is used to improve perfor-
mance by creating a pool of objects (instances of components). Instead of
physically destroying an object after the last client releases a reference to
the object, COM+ places the object in a pool. The next time another client
15 0789724588 CH12 10/25/00 5:08 PM Page 407
(or the same client) wants to use the object, COM+ hands it one of the
unused objects from the pool. This process is completely transparent from
the client. The client still has the illusion that it created a new instance of
the object.
By reusing existing live objects instead of reinstantiating them from
scratch, you can reduce overhead to a certain extent (it is not completely
eliminated, though, because getting an object from the pool also involves
some overhead).
The pool size is adjustable. You can specify the minimum and maximum
number of objects in a pool.
Unfortunately, object pooling is out of the reach of Visual Basic 6.0 based on
the limitations of the threading model supported by VB. In Chapter 1,
“What COM+ Is All About,” and Chapter 4, “Introduction to Visual Basic
COM Programming,” I described the different threading models and men-
tioned that Visual Basic supports only the Single-threaded Apartment
(STA) model. The STA model requires that all the calls to the object must
be made through a single thread in the main apartment (see Chapter 1).
This is called thread affinity. In contrast, if you want to enable object pool-
ing, the object itself must not be tied to any specific threads. The object
should be accessible by any threads. This requires that the object must be a
free-threaded model, a threading model that is not supported in Visual
Basic 6.0.
NOTE
According to a recent Microsoft announcement, the next version of Visual Basic, as part
of VisualStudio.NET, will support free-threaded models, among other exciting features.
So, the ability to create poolable components in Visual Basic is almost a certainty in
the near future.
In addition, keep in mind that using object pooling makes sense only in certain circum-
stances, such as when construction of an object is computationally intensive, construc-
tion of an object fetches initialization information from a file or a database, or
construction of an object involves establishing a non-COM connection to another
process or computer. Even under these situations, object pooling may or may not be
the optimal solution. For example, using a dedicated local SQL Server database as a
data cache usually offers higher performance over other approaches. You should always
perform some benchmark testing to evaluate different alternatives.
What’s Next
This chapter ends Part II of this book. In Part III, “Building Real-World
COM+ Services Applications,” you will push the Northwind Traders appli-
cation to the Internet and build an Enterprise Application Integration (EAI)
solution by using COM+ and XML.
16 0789724588 Part III 10/25/00 5:14 PM Page 409
Part III
13
Northwind Traders Online: A COM+
Enabled Windows DNA 2000
Application
In previous chapters, you learned about all the services provided by COM+.
You saw the Northwind Ordering System evolving into a fully COM+
enabled application, including automatic Transaction Services, the
Compensating Resource Manager (CRM), Queued Components (QC), and
Loosely Coupled Events (LCE). In this chapter, you’ll learn how to make
Northwind a Web-enabled Windows DNA 2000 application.
This chapter teaches you the following:
• The Northwind Traders Online system architecture
• All the COM+ applications and components used in Northwind
Traders Online
• How to build the Northwind Traders Online Web site
17 0789724588 CH13 10/25/00 5:01 PM Page 412
412 Chapter 13: Northwind Traders Online: A COM+ Enabled Windows DNA 2000 Application
A Brief Tour
The Northwind Traders Online Web application is designed to allow
Northwind customers to order products from the Northwind Web site,
reducing the cost of the call center. The Web site you will build duplicates
the basic functionality of the Visual Basic ordering application built in pre-
vious chapters. This duplication of functionality is only at a conceptual
level because Web interface behaviors are completely different from a
Win32 GUI interface, such as a Visual Basic application. The Northwind
Traders Online Web application contains four Web pages although you cre-
ated only one form in the Visual Basic application.
A Web application should be secured, so you need a login page. Figure 13.1
shows the first page, the login page, of the Northwind Traders Online Web
site.
After a customer types a customer ID and password, the Product Selection
page opens, as shown in Figure 13.2.
The product IDs in the Product Selection list are hyperlinks. When the cus-
tomer clicks the product ID of his or her choice, the Place an Order page
opens, displaying the ID, name, and unit price of the product the customer
chose (see Figure 13.3).
17 0789724588 CH13 10/25/00 5:01 PM Page 413
Figure 13.1: The login page of Northwind Traders Online Web site.
Figure 13.2: The Product Selection page, from which the customer can
select a product he or she wants to order.
After specifying the quantity he or she wants to order, the customer clicks
the Check Out button to submit the order. If everything goes fine, a confir-
mation page is displayed to the customer, summarizing customer, order,
and shipping/billing information and indicating that the order has been
processed (see Figure 13.4.)
As you can see, a single Visual Basic form is converted into several Web
pages as a result of the different GUI behavior between the Web interface
and the Win32 interface.
17 0789724588 CH13 10/25/00 5:01 PM Page 414
414 Chapter 13: Northwind Traders Online: A COM+ Enabled Windows DNA 2000 Application
Northwind_biz.lookup
Northwind_Product.Product
GetProduct()
GetCustomer()
GetEmployee() UpdateStock()
Northwind Order Form (VB Application) GetShipper() Northwind(SQL Server)
Northwind_Credit.Credit
Data.Access
Northwind_Order.Order2 CheckCredit()
GetResults()
PlaceOrder() ExecSQL()
Northwind_Login.Login
ValifyLogin()
Northwind_Invoice(Access)
Northwind_Invoice.Invoice CRMInvoice.CRMInvoiceWorker
CRMInvoice.CRMInvoiceCompensator
ICRMCompensatorVariants_AbortRecordVariants()
The Workflow
Figure 13.6 illustrates the workflow of the Northwind Traders Online Web
application.
The following are the steps of the workflow:
1. A customer enters customer ID and password information and clicks
the Login button. This login page initiates the Login object and calls
the VerifyLogin() method. The Login object, in turn, creates an
instance of the Data.Access object and executes a stored procedure
through the Data.Access object to verify the customer ID and pass-
word. If the customer successfully logged in, the product list page is
displayed for the customer.
2. The product list page creates an instance of the Lookup object, which,
in turn, submits a stored procedure call to the Data.Access object to
return a list of products. The customer can click one of the hyperlinks
on the product list page to display the Place an Order page.
17 0789724588 CH13 10/25/00 5:01 PM Page 416
416 Chapter 13: Northwind Traders Online: A COM+ Enabled Windows DNA 2000 Application
1 3
HTML 3.2 Client Win32 Client
(Web Browser) (VB Application)
8
Login Lookup Order2
Object Object Object
7
4 5 6
InventoryLow
Event
Product Invoice Credit
Object Object Object
InvSubscriber
Object
Notify
Object
3. After the customer specifies the quantity he or she wants to order and
clicks the Check Out button, the order Web page initiates an Order2
object to process the order. (For a description of the Order2 object, read
Chapter 7). This action starts a transaction. The Order2 object is the
root of the transaction.
4. The Order2 object inserts an order (populating both Orders and Order
Details tables) by calling a stored procedure through the Data.Access
object.
5. After inserting an order into the database, the Order2 object needs to
update the units in stock of the Products table. It does so by creating a
Product object, which, in turn, calls a stored procedure through the
Data.Access object. At this point, the stored procedure compares the
quantity being ordered to the number of units in stock. If the quantity
is greater than the number of units in stock, an error is raised and the
transaction is aborted.
6. The Order2 object then creates an instance of the Invoice object to add
an invoice to the Invoice database through the Data.Access object.
Because the Invoice database is a Microsoft Access database, the
17 0789724588 CH13 10/25/00 5:01 PM Page 417
8. If the customer passes the credit verification, the Order2 object creates
a Queued Component, the Ship object, through the Queued
Components service of COM+ to send a shipping request. At this
point, if everything goes well, the Order2 object commits the transac-
tion and makes all the changes permanent.
9. The Ship Queued Component inserts a shipping record to the Shipping
database through the Data.Access object.
10. The Ship object creates another Queued Component, the Notify object,
to send a shipping notification.
11. An update trigger in the Products table of the Northwind database
watches the changes of the inventory. If the number of units in stock
for a specific product is lower than 10 as the result of an update, the
trigger calls a stored procedure, which creates an instance of a pub-
lisher object through OLE automation (InvPublisher). The publisher
object, in turn, initializes an instance of the EventClass and fires a
COM+ LCE event. The COM+ event system locates the subscriber
object (InvSubscriber) to deliver the event.
Now that you’ve walked through the entire order processing workflow, you
can see that the Northwind Traders Online application is a multitiered,
highly distributed application. The application takes advantage of all the
important COM+ services, including Transaction Services, the
Compensating Resource Manager, Queued Components, and Loosely
Coupled Events.
You should also notice that, because Northwind Traders Online is a
Windows DNA application, every request from the client to the database is
handled by middle-tier COM or COM+ components. The front ends (either
the HTML 3.2 client or the Win32 client) never directly interact with data-
bases. The database schemas and physical locations are totally transparent
to the clients.
In addition, the middle tier is further partitioned as two subtiers. The busi-
ness logic tier contains business objects such as Login, Lookup, Order2,
Product, Invoice, and Credit. The data access tier contains one generic
data access object, the Data.Access object, which encapsulates all the intri-
cate details of dealing with database back ends. In this Data.Access object,
17 0789724588 CH13 10/25/00 5:01 PM Page 418
418 Chapter 13: Northwind Traders Online: A COM+ Enabled Windows DNA 2000 Application
you use the Microsoft ActiveX Data Objects (ADO) object model as the data-
base-accessing mechanism to all the databases—either SQL Server data-
bases or Microsoft Access databases. The business logic tier objects access
databases through the Data.Access object.
Table 13.1 ASP Pages in the Northwind Traders Online Web Application
ASP Page Description
Login.asp Handles user logins, checks the customer ID and password,
and generates an HTML page as in Figure 13.1
Products.asp Displays a list of available products, generates an HTML page
as in Figure 13.2, and allows the customer to select a product
from the list
Order.asp Handles online orders and generates an HTML page as in
Figure 13.3
Confirmation.asp Displays a confirmation page to the customer about his or her
order by generating an HTML page as in Figure 13.4
You’ll learn about these ASP pages in detail later in this chapter as you
build your Web site.
NONCONFIGURED COMPONENTS
The Northwind Traders Online application has two nonconfigured compo-
nents: Northwind_Login.Login and Northwind_Biz.Lookup. The methods of
these components simply query the databases and get results returned
from databases. No database updating is involved in these components, so
you don’t need transactions here. Besides, these two components do not
need any other COM+ services, such as CRM, QC, and LCE. Therefore,
they are designed as nonconfigurable, regular COM components.
Tables 13.2 and 13.3 describe the methods supported in the
Northwind_Login.Login and Northwind_Biz.Lookup components, respectively.
420 Chapter 13: Northwind Traders Online: A COM+ Enabled Windows DNA 2000 Application
The HTML 3.2 Web client uses only the GetProduct() method of the
Northwind_Biz.Lookup component.
Figure 13.8: Northwind Traders Online COM+ applications and their com-
ponents in the Component Services snap-in.
You can create, install, and configure these COM+ applications and their
components by using the Component Services snap-in, as you did in
Chapters 6, “Writing Transactional Components,” 7, “Compensating
Resource Manager (CRM),” 9, “Queued Components,” and 10, “COM+
Events.” You also can create, install, and configure these COM+ applica-
tions and components programmatically through the COM+ Administration
object model, as you saw in Chapter 11, “Administering COM+ Applications
Programmatically.”
The following sections describe the Northwind Traders COM+ applications
and the components installed.
17 0789724588 CH13 10/25/00 5:01 PM Page 421
NORTHWIND
Northwind is a regular COM+ application that hosts the following compo-
nents that need transaction services:
• Data.Access—This generic data access component encapsulates all the
data access APIs and interacts between business tier components and
the data stores. The Data.Access component is configured as Support
Transaction so that it can be enlisted in the caller’s transaction con-
text when requested. See Chapter 6 for detailed information about
this component.
• Northwind_Order.Order—This legacy component supports existing ver-
sions of the Visual Basic Northwind Ordering clients. It is configured
as Requires Transaction and will be the root of the transaction. See
Chapter 6 for detailed information about this component.
• Northwind_Order.Order2—This component is an enhanced version of
the Northwind_Order.Order component. You created this separate
interface earlier to see how to use multiple interfaces to support com-
ponent evolution. The new functionalities added to this component
include a call that involves interaction with an Access database that is
protected by the Compensating Resource Manager. This component is
also configured as Requires Transaction and will be the root of the
transaction. See Chapter 7 for detailed information about this
component.
• Northwind_Product.Product—This component is used by the Order or
Order2 component to deduct the number of units in stock in the
Products table based on the quantity ordered. See Chapter 6 for
detailed information about this component.
NORTHWIND CRM
Northwind CRM is a COM+ application that has the Compensating
Resource Manager service enabled. It contains the following components:
• CRMInvoice.CRMInvoiceWorker and
CRMInvoice.CRMInvoiceCompensator—These components were devel-
oped to use the extended transaction services provided by the COM+
Compensating Resource Manager infrastructure. The
CRMInvoiceWorker component is configured as Support Transaction,
whereas the CRMInvoiceCompensator component is configured as
Disabled. See Chapter 7 for detailed information about these
components.
17 0789724588 CH13 10/25/00 5:01 PM Page 422
422 Chapter 13: Northwind Traders Online: A COM+ Enabled Windows DNA 2000 Application
NORTHWIND SHIPPING
The Northwind Shipping COM+ application contains two Queued
Components:
• Northwind_Ship.Ship—This QC is called by the
Northwind_Order.Order2 component asynchronously through the
COM+ QC service. It inserts shipping records in the Shipping data-
base and invokes another QC, Northwind_Notify.Notify, to send a
shipping notification message. See Chapter 9 for detailed information
about this component.
• Northwind_Notify.Notify—This QC is called by the
Northwind_Ship.Ship component through the COM+ QC Service to
send a shipping notification message. See Chapter 9 for detailed infor-
mation about this component.
NOTE
These applications also have another component: the event publisher named
Northwind_InvPublisher.Publisher. This publisher component is not shown in
Figure 13.8 because it is a nonconfigured component. See Chapter 10 for detailed
information.
424 Chapter 13: Northwind Traders Online: A COM+ Enabled Windows DNA 2000 Application
function ValidateFields(theForm) {
if (theForm.CustomerID.value == “”)
{
alert(“Customer ID is missing!”);
theForm.CustomerID.focus();
return (false);
}
if (theForm.Password.value == “”)
{
alert(“Password is missing!”);
theForm.Password.focus();
return (false);
}
return (true);
}
//-->
</script>
</head>
<body>
<center>
<IMG height=242 src=”images/northwindlogo.gif”
style =”HEIGHT: 126px; WIDTH: 138px” width=258 >
</center>
<center> </center>
<center><font color=”darkred” face=”Tahoma” size=”5”>
Welcome to Northwind Traders Online !</font></center>
<center> </center>
<center>Please Enter Your Customer ID and Password</center>
<center>Then Click the Login Button to login:</center>
<center> </center>
<center>
<form action=login.asp method=post id=frmLogin name=frmLogin
onsubmit =”return ValidateFields(this)”>
<table border=”0” cellPadding=”1” cellSpacing=”1” width=”75%”
style=”HEIGHT: 61px; WIDTH: 293px”>
<tr>
<td>
<p align=”right”><em>
17 0789724588 CH13 10/25/00 5:01 PM Page 425
end if
end if
%>
17 0789724588 CH13 10/25/00 5:01 PM Page 426
426 Chapter 13: Northwind Traders Online: A COM+ Enabled Windows DNA 2000 Application
The first part of the Login.asp code defines the JavaScript function
ValidateFields(). The function is enclosed inside the <script></script>
tags. It will be called inside the Web browser, not executed at the server
side. This is called client-side scripting. Client-side scripting is used for sim-
ple data validation and calculations that do not require a round trip to the
server to get feedback. I chose JavaScript instead of VBScript for client-side
scripting here because, unlike VBScript, which is somehow Internet
Explorer–specific, JavaScript is supported by most Web browsers.
Therefore, using JavaScript for client-side scripting extends browser inde-
pendence. The ValidateFields function checks whether the customer ID
and password are entered by the customer. If the customer has not com-
pleted this information and tries to click the Login button, a message box
pops up reminding the customer that some information is missing (see
Figure 13.9).
CreateObject() method. The Session object stores global data so that it can
be used by other pages. The script also uses the Response object. If the login
succeeds, the script displays the product list (Products.asp) by calling the
Redirect method of the Response object, as shown in Figure 13.2 earlier.
Otherwise, it calls the Write method of the Response object to display the
error on the login page (see Figure 13.10).
Notice that you use an HTML form object (frmLogin) to help collect infor-
mation and submit it to the Login.asp page using the Post method.
The ASP code relies on the Northwind_Login.Login object to perform the
login verification. Northwind_Login.Login is created in an ActiveX DLL pro-
ject that has one class module named Login.cls (see Figure 13.11).
428 Chapter 13: Northwind Traders Online: A COM+ Enabled Windows DNA 2000 Application
If lRows = 0 Then
sError = “Invalid login.”
Set VerifyLogin = Nothing
Else
Set VerifyLogin = oRs
End If
Exit Function
VerifyLogin_Err:
VerifyLogin = 0
sError = “Error “ & CStr(Err.Number) & “: “ & Err.Description & “.”
End Function
as
select CompanyName,
Address,
City,
Region,
PostalCode,
Country
from Customers
where CustomerID = @CustomerID
and Password = @Password
if @@rowcount = 0
raiserror (‘Invalid login!.’,16,1)
go
go
TIP
Prior to SQL Server 7.0, if you compiled a stored procedure that had a reference to a
nonexisting object, such as a column name or table name, you got a compile error. In
SQL Server 7.0, however, the names are not resolved at compile time. Rather, the
names are resolved the first time the stored procedure is executed. This new feature of
SQL Server 7.0 is called deferred name resolution. Refer to SQL Server Books Online
for more information.
17 0789724588 CH13 10/25/00 5:01 PM Page 430
430 Chapter 13: Northwind Traders Online: A COM+ Enabled Windows DNA 2000 Application
NOTE
Unlike Visual Basic, in ASP, every variable is a variant data type. For this reason, you
declare variables using only the Dim keyword without using As Datatype.
432 Chapter 13: Northwind Traders Online: A COM+ Enabled Windows DNA 2000 Application
onsubmit=”return ValidateForm(this)”>
<%
dim rsProduct
dim oData
dim sSQL
session(“ProductID”) = rsProduct(“ProductID”)
session(“ProductName”) = rsProduct(“ProductName”)
session(“UnitPrice”) = rsProduct(“UnitPrice”)
%>
<center>
<table border = 0 cellspacing=”5”>
<tr>
<td align=”right”><strong><em>Product ID:
</em></strong></td>
<td align=”left”><strong><%=rsProduct(“ProductID”)%></strong></td>
</tr>
<tr>
<td align=”right”><strong><em>Product Name: </em></strong></td>
<td align=”left”><strong><%=rsProduct(“ProductName”)%></strong></td>
</tr>
<tr>
<td align=”right”><strong><em>Unit Price :
</em></strong></td>
<td align=”left”><strong>$<%=rsProduct(“UnitPrice”)%></strong></td>
</tr>
<tr>
<td align=”right”><strong><em>Quantity: </em></strong></td>
<td align=”left”><input id=”Quantity” name=”Quantity” value=1
style=”HEIGHT: 22px; WIDTH: 50px”></td>
</tr>
</table>
</center>
<center> </center>
<center>
<input id=submit1 name=submit1 type=submit value=”Check Out”>
</center>
</form>
</body>
</html>
17 0789724588 CH13 10/25/00 5:01 PM Page 434
434 Chapter 13: Northwind Traders Online: A COM+ Enabled Windows DNA 2000 Application
As in the Login.asp page, the first part of this page is a client-side valida-
tion function named ValidateForm() written in JavaScript. This function
makes sure that a valid quantity value is entered before submitting the
request to the server for processing.
Again, you use an HTML form (frmOrder) with a Post method to collect the
information from the customer. The Confirmation.asp page is specified as
the destination page of the Post action.
In Order.asp, you create an instance of the Data.Access object and execute
a stored procedure by calling its GetResult() method. This returns a one-
row ADO recordset, with the information for the product specified by the
ProductID. Listing 13.7 shows the Products_GetByID stored procedure.
Listing 13.7 The Products_GetByID Stored Procedure
if exists(select * from sysobjects where id = object_id(‘Products_GetByID’))
drop proc Products_GetByID
go
The product information, such as product ID, product name, and unit price,
is listed in a borderless table, along with one field for the customer to spec-
ify the quantity of the product he or she wants to order.
<body>
<%
Response.Buffer = true
dim oOrder
dim iProductID
dim sProductName
dim cUnitPrice
dim iQuantity
dim sCustomerID
dim dOrderDate
dim dRequiredDate
dim dShippedDate
dim sCompanyName
dim sAddress
dim sCity
dim sRegion
dim sPostalCode
dim sCountry
dim cSubTotal
dim cTotal
iProductID = session(“ProductID”)
sProductName = session(“ProductName”) & “”
cUnitPrice = session(“UnitPrice”)
iQuantity = Request(“Quantity”)
sCustomerID = session(“CustomerID”) & “”
dOrderDate = date()
dRequiredDate = DateAdd(“d”, 21, dOrderDate)
dShippedDate = DateAdd(“d”, 14, dOrderDate)
sCompanyName = session(“CompanyName”) & “”
sAddress = session(“Address”) & “”
sCity = session(“City”) & “”
sRegion = session(“Region”) & “”
sPostalCode = session(“PostalCode”) & “”
sCountry = session(“Country”) & “”
cSubTotal = cUnitPrice * cint(iQuantity)
cTotal = cSubTotal + 15
436 Chapter 13: Northwind Traders Online: A COM+ Enabled Windows DNA 2000 Application
oOrder.PlaceOrder iProductID, _
cUnitPrice, _
iQuantity, _
sCustomerID, _
10, _
dOrderDate, _
dRequiredDate, _
dShippedDate, _
1, _
15, _
sCompanyName, _
sAddress, _
sCity, _
sRegion, _
sPostalCode, _
sCountry, _
sCompanyName, _
“Web Order”, _
“Speedy Express”, _
17 0789724588 CH13 10/25/00 5:01 PM Page 437
sProductName, _
cSubTotal, _
cTotal
if err.number<> 0 then
Response.Clear
Response.Write “<h3>The following error has occurred.<br>” _
& “Your order has been cancelled!</h3>”
Response.Write err.description
end if
Figure 13.12: The browser displays an error and tells the customer the
order has been canceled because of the error.
438 Chapter 13: Northwind Traders Online: A COM+ Enabled Windows DNA 2000 Application
client you are using, either a Win32 VB application or an HTML 3.2 Web
application. Let’s test the Web site to see if this is true.
First, test the transaction behavior, including the automatic transaction
service and the CRM. To do so, log in as customer Blondel père et fils
with customer ID BLONP and pick a product from the list. In this case,
choose Aniseed Syrup by clicking Product ID 3 on the Product Selection
page. The Order page then displays the product ID, name, and unit price
information; it also displays 1 as the default quantity, similar to the page
shown earlier in Figure 13.3. When you click the Check Out button, you see
a page like the one in Figure 13.13.
Figure 13.13: The browser displays an error, indicating the customer has a
bad credit history.
Recall that, in Chapter 7, you intentionally gave all the customers whose
names start with the letter B a bad credit history. So, this order is not
processed, nor is the invoice. Therefore, you know that both Transaction
Services and the CRM are working. You can confirm this fact by following
the procedures outlined in Chapter 7.
Now that you know the transaction is working for both traditional DTC-
based and new COM+ CRM-based transactions, what about the Queued
Components feature? To test the QC, log in as customer Cactus Comidas
para llevar with a customer ID of CACTU. Now try to order one item with
Product ID 3, Aniseed Syrup. Figure 13.14 show the results: a confirmation
page with a message from the Northwind_Notify.Notify Queued
Component.
You can query the Shipping database to confirm the order, as shown in
Figure 13.15.
So far, so good! The next thing you should check is the LCE event’s func-
tionality. If you check the Products table, you’ll find that the current inven-
tory level for Product ID 3 is 33. Now go back to the Order page by clicking
the Back button on the browser’s toolbar. This time, try to order 30 items of
the Aniseed Syrup. So, type 30 in the Quantity box and click the Check Out
button. This time, you get a message from the Inventory event subscriber,
as shown in Figure 13.16, indicating the inventory for Product ID 3 is low,
and you need to place a back order.
17 0789724588 CH13 10/25/00 5:01 PM Page 439
Figure 13.14: The message from the Queued Component indicates that the
shipping for the order has been scheduled.
Figure 13.15: Querying the Shipping database to confirm the shipping for
the order.
Figure 13.16: A message from the COM+ events services (LCE) indicates
that the inventory level for the ordered product is low.
You can see that the LCE works just as it is supposed to. Everything is in order!
17 0789724588 CH13 10/25/00 5:01 PM Page 440
440 Chapter 13: Northwind Traders Online: A COM+ Enabled Windows DNA 2000 Application
Conclusions
This chapter discussed in detail the architectural design of the Northwind
application you built in the preceding chapters. It also demonstrated how to
build a COM+ enabled, Windows DNA 2000 application by using Active
Server Pages and the COM+ components you built previously. The purpose
of this chapter was to demonstrate how to build a complete Windows DNA
2000 application that leverages all the important COM+ services.
The Web site you built in this chapter uses some very basic ASP and Web
development techniques. It provides only the minimal functionality to sup-
port this sample application. The focus was on how to use the COM+ ser-
vices from ASP pages.
I did not intend to teach you ASP programming. You can find plenty of
books and resources out there that cover these topics, so I don’t need to
repeat them here. For this reason, I did not cover the parts of a regular
Business-to-Consumers (B2C) commerce Web site, such as a shopping cart,
cookies, and page navigation. These topics are beyond the scope of this
book.
This chapter does, however, demonstrate the principles of good Windows
DNA application design—that is, separating business logic from the presen-
tation layer and putting it into the COM or COM+ components in the mid-
dle tier. In the ASP pages of this example, you can see that every request
for data accessing and manipulation is done through the middle-tier compo-
nents. The database back end is totally hidden from the ASP front end,
which makes the distributed application more scalable, more flexible, and
more maintainable. I have seen many poorly designed ASP applications in
which several stored procedure calls and event-embedded SQL statements
are put in the ASP pages themselves. This is actually a two-tiered
approach, and it suffers from many scalability and maintainability issues.
The sample Windows DNA application you built in this and the preceding
chapters will provide you with some general guidelines for developing dis-
tributed enterprise and Internet applications using COM+ services and the
Windows DNA framework. It will give you a good starting point for building
real-world Windows DNA 2000 applications using COM+ technology.
17 0789724588 CH13 10/25/00 5:01 PM Page 441
What’s Next
In this chapter, you had a chance to examine a complete Windows DNA
application using all the important COM+ services you learned in this book.
In Chapter 14, “A Case Study: COM+ and Enterprise Application
Integration (EAI),” you will learn how you can use COM+ services to solve
many problems presented in a real-world Enterprise Application
Integration (EAI) scenario.
18 0789724588 CH14 10/25/00 5:12 PM Page 442
18 0789724588 CH14 10/25/00 5:12 PM Page 443
14
A Case Study: COM+ and Enterprise
Application Integration (EAI)
In the preceding chapter, you built a complete Windows DNA 2000 applica-
tion using all the important COM+ services. In this chapter, you will learn
how you can use COM+ along with the eXtensible Markup Language (XML)
technology in the Enterprise Application Integration (EAI) arena to solve
many problems. You’ll use a real-world scenario for the case study.
This chapter teaches you the following:
• The fundamentals of EAI
• The issues involved in an EAI implementation
• How to use COM+ and XML to provide an EAI solution
• How to walk through the source code
18 0789724588 CH14 10/25/00 5:12 PM Page 444
444 Chapter 14: A Case Study: COM+ and Enterprise Application Integration (EAI)
The Background
The E-LAWS company is a national leader in electronic filing (E-File) of
legal documents through the Internet. The E-File services provided at the
company’s Web site replace the traditional method of filing, serving,
18 0789724588 CH14 10/25/00 5:12 PM Page 445
Original Design
Figure 14.1 illustrates the original design developed by one of the major
EAI venders in the country.
Let’s walk through a case activation scenario for this integrated system. A
state court clerk activates a case using a Web browser at the E-LAWS Web
site. This activity causes the ASP page running at the E-LAWS IIS Web
server to update the EFiling database and also sends a message to an
MSMQ message queue.
A queue listener application developed in Visual C++ as an NT Service lis-
tens to the queue and picks up the message when it arrives. The applica-
tion then instantiates one of the integration objects on-the-fly by using the
Label property of the MSMQ message as the ProgID and the message itself
as a single pipe-delimited (|) parameters string.
18 0789724588 CH14 10/25/00 5:12 PM Page 446
446 Chapter 14: A Case Study: COM+ and Enterprise Application Integration (EAI)
HTTP
ASP pages
Web Browser Court Clerk
IIS
HTTP
Protocol
Q Bridge
Integration Adapters
Objects
Rule
INPUT Engine OUTPUT
Queue Queue
(MQSeries) (MQSeries)
EFiling Database
(SQL Server)
Third-Party EAI Product
The integration object parses the message into parameters and calls a
stored procedure in the EFiling database to retrieve required data. Then it
packs them together as a double pipe-delimited (||) message and sends it
to the INPUT queue of an IBM MQSeries queue manager.
The EAI Rule Engine then picks up the message from the INPUT queue
and performs some lookup and validation tasks. It then sends a converted
message as a single pipe-delimited string to another IBM MQSeries queue
designated as the OUTPUT queue.
The EAI Queue Bridge listens to the OUTPUT queue, picks up the message
when it arrives, and then converts the message into a SQL statement
defined in one or more protocol adapters. The converted SQL statement is
then executed against the DB2 database through the Internet using a
third-party ODBC driver.
Several problems are associated with this design:
• The third-party EAI product supports only the IBM MQSeries as its
message queuing middleware. This support may be desirable in a non-
NT environment but does not make any sense in this scenario. Here,
everything runs under the NT platform, the vendor specific require-
ment will only increase E-LAWS’s cost for extra expensive MQSeries
licenses, whereas MSMQ definitely meets all the message queuing
functionalities the company requires. Besides, MSMQ has already
been used in other areas of E-LAWS’s applications.
18 0789724588 CH14 10/25/00 5:12 PM Page 447
• The Rule Engine and Queue Bridge used in this EAI product are both
implemented as NT Services. It is very difficult to separate data flows
(in-bound versus out-bound) and isolate failures.
• This EAI product doesn’t offer sophisticated exception handling.
Should the Queue Bridge fail to update the Court legacy database, the
message will be lost. No retry or notification mechanism (using email,
for example) is available.
• The protocol adapters store SQL statements, execution sequences, and
other information and provide this information to the Queue Bridge at
runtime. However, the database connection string (server name, data-
base name, user ID, and password,) are embedded or hard-coded in
these adapter files, making redeployment extremely difficult. There
are more than 80 adapter files, and each adapter file contains one or
more ODBC connection strings. If you want to move these files to
another environment (for example, move them from the development
servers to the quality control servers, or to the production servers),
you need to reconfigure all these 80+ files by using a GUI tool pro-
vided by this EAI product. This would be extremely difficult to
maintain!
• The MSMQ integration queue listener was developed in Visual C++ as
an NT service. Should something go wrong, it will stop listening to the
messaging events. Additionally, significant programming skills are
required to develop and debug NT services.
• Although message queuing is used in this EAI product, it is used only
on one side of the integration boundary—at the E-LAWS’s system. The
most critical path is the database update to the DB2 database through
the Internet. This is a direct, synchronous action. This architecture is
not robust. If the DB2 system is offline, all the attempts to update will
fail.
448 Chapter 14: A Case Study: COM+ and Enterprise Application Integration (EAI)
HTTP
ASP pages
Web Browser Court Clerk
IIS
Delimited
Parameters
LCE Events DB2 ODBC
Driver
Court Database
Subscriber (DB2/AS 400)
Case
Object
(QC)
XML
Integration Document
Objects
HTTP
(QC)
ASP pages
IIS
EFiling Database
(SQL Server)
Figure 14.2: The modified integration design using COM+ and XML.
When a state court clerk activates a case through a Web browser at the E-
LAWS Web site, the HTTP request is sent to an ASP page running under
the E-LAWS IIS Web server. After updating the EFiling database, instead
of sending a message to the MSMQ integration queue, the ASP page fires
an LCE event.
In response to the LCE event, the event subscriber makes a call to one of
the integration objects that is a Queued Component. Using Queued
Components in conjunction with LCE improves scalability. The processing
of the event messages do not block the events’ firing because they are
queued.
The integration object parses the input message into parameters and calls
a stored procedure to retrieve related information from the EFiling data-
base. It then generates an XML document based on the data returned from
the stored procedure.
The XML document itself is sent through the Internet via the HTTP proto-
col to another ASP page running at the state court. The ASP page parses
the XML document received, converts it to a SQL statement, and submits it
to the database by calling another Queued Component.
18 0789724588 CH14 10/25/00 5:12 PM Page 449
An XML Primer
Because eXtensible Markup Language technologies are used in this solu-
tion, I’ll briefly introduce some basic XML here. I cannot, however, provide
comprehensive coverage of XML. A more intensive study of XML is obvi-
ously beyond the scope of this book.
What Is XML?
Like Hypertext Markup Language (HTML), XML is also a tag-based lan-
guage. Unlike HTML, which is used for presenting the data, however, XML
is used for describing the data. In other words, HTML is about the repre-
sentation of the data, whereas XML is about the data itself. XML is
extremely useful in describing complicated hierarchically structured data.
XML has become an emerging standard for data exchange. The XML stan-
dards are governed by the World Wide Web Consortium (W3C).
Due to the scope of this book, the rest of this section will only give you a
quick introduction to XML so that you can better understand how XML is
used in our sample application. To fully master the XML language, I highly
recommend to you the book, XML by Example, also from Que.
18 0789724588 CH14 10/25/00 5:12 PM Page 450
450 Chapter 14: A Case Study: COM+ and Enterprise Application Integration (EAI)
An XML document that describes this book, for example, might look like
this:
<Book type=”Programming”>
<Title>Visual Basic and COM+ Programming By Example</Title>
<ISBN>0789724588</ISBN>
<Author>Peishu Li</Author>
<Price>$29.99</Price>
<Publisher>Que Publishing</Publisher>
</Book>
SAX. If you’re interested, refer to Appendix A of this book for more informa-
tion on SAX.
For this example, you will use used Microsoft’s DOM parser that is shipped
with Internet Explorer 5 (which comes with Windows 2000). So, the discus-
sion focuses on the MSXML DOM model.
TIP
You can download the most recent version of Microsoft XML parser, The July 2000
Microsoft XML Parser Beta Release from the MSDN Web site at
http://msdn.microsoft.com/xml/general/msxmlprev.asp.
The EventClass
The EventClass and its definition (the code) are shown in Figure 14.3. It
has an IEvent class model.
EXAMPLE
452 Chapter 14: A Case Study: COM+ and Enterprise Application Integration (EAI)
Now you can compile the project file into CaseEvent.dll and install it in an
empty COM+ server application as an EventClass.
At this point, set a reference to the EventClass and type the code in Listing
14.1 in the Subscriber.cls class module.
Listing 14.1 The Implementation of the CaseActivated Event in the Subscriber
Option Explicit
Implements IEvent
As you can see in Listing 14.1, the code simply creates a Queued
Component and calls its Process method by passing the message as its
input parameter.
Your next step is to compile the project to CaseEventSubscriber.dll and
install it in a regular COM+ server application. Then subscribe the event
defined by the CaseEvent EventClass.
Now set references to ADO 2.5 and MSXML 2.0 in the project, as shown in
Figure 14.6.
Figure 14.6: Setting the reference to ADO and MSXML in the project.
454 Chapter 14: A Case Study: COM+ and Enterprise Application Integration (EAI)
XML_SCHEMA)
If Len(sText) Then
oNode.Text = sText
End If
sCaseClass = vMessage(CASE_CLASS)
sCaseSequence = vMessage(CASE_SEQUENCE)
Exit Sub
Process_Err:
Err.Raise Err.Number
End Sub
456 Chapter 14: A Case Study: COM+ and Enterprise Application Integration (EAI)
the createNode() and AppendNode() methods to create nodes and build the
XML document.
After building the XML document, the Process method calls the Open
method of the XMLHTTPRequest object, specifies that the HTTP Post method
be used, and then sends the XML document object to the target URL—in
this case, the ProcessXMLDoc.asp page.
Now you can compile Integration.dll and install it in a COM+ server appli-
cation with queuing and listener enabled. Also, enable the queuing of the
_Case interface.
if oXMLDoc.parseError.errorCode = 0 then
‘Create the queued components.
Set oCaseXML = GetObject(“queue:/new:CaseXML.XMLDoc”)
Else
sError = “Error: “ & oXMLDoc.parseError.errorCode & vbcrlf _
& “Line: “ & oXMLDoc.parseError.Line & vbcrlf _
& “Reason: “ & oXMLDoc.parseError.reason & vbcrlf
End If
if sError = “” then
oCaseXML.Process oXMLDoc.XML
18 0789724588 CH14 10/25/00 5:12 PM Page 457
if err<> 0 then
sError = sError & err.description
end if
You may notice that Listing 14.3 doesn’t have any HTML tags at all. It is a
pure ASP scripting page. This page is designed to be called by the
Integration.Case component over the Internet and run completely behind
the scenes.
The script in ProcessXMLDoc.asp is straightforward. After creating an
instance of the DOMDocument object, this script sets the async property to
False, which forces the XML document to be loaded synchronously. If this
property is not specified, the default asynchronous loading behavior is used.
In this case, you are responsible for checking the loading status by query-
ing the onreadystatechange property until it is completed (it returns the
value 4). The ASP code then loads the XML DOMDocument object from the
request object. If the XML is successfully loaded, the ASP page will create
an instance of the Queued Component, CaseXML.XMLDoc, and pass the con-
tents of the XML document to its Process method.
NOTE
If you try to directly open ProcessXMLDoc.asp, it returns an error, as shown in Figure
14.7. When you directly open this page, no XML document has been passed to it.
458 Chapter 14: A Case Study: COM+ and Enterprise Application Integration (EAI)
Next, you must set references of the project to both ADO 2.5 and
MSXML 2.0.
The CaseXML.XMLDoc component implements a single method, Process, as
shown in Listing 14.4.
Listing 14.4 The Process Method of the CaseXML.XMLDoc Component
Public Sub Process(ByVal sXML As String)
Dim oXMLDoc As New MSXML.DOMDocument
Dim sRecType As String
Dim sCourtType As String
Dim iCourtLocation As Integer
Dim sCaseYear As String
Dim sCaseClass As String
Dim lCaseSequence As Long
Dim sEFileFlag As String
Dim sSQL As String
Dim oConnection As New ADODB.Connection
Dim sConnection As String
Dim sXMLError As String
Exit Sub
Process_Err:
If Err = 0 Then
Err.Raise vbObjectError + 500, “CaseXML.Process”, sXMLError
Else
Err.Raise Err.Number
End If
End Sub
Here, the Process method loads the XML string to the DOMDocument object
by calling the LoadXML method. Then it parses the DOM tree to retrieve the
18 0789724588 CH14 10/25/00 5:12 PM Page 460
460 Chapter 14: A Case Study: COM+ and Enterprise Application Integration (EAI)
The Databases
For this demonstration, you can set both databases on the SQL Server. This
strategy was used for the real-world development, too. Notice that you spec-
EXAMPLE ify the DSN in the connection string in Listing 14.4 so that during deploy-
ment all you need to do is reconfigure the DSN and point it to the DB2
database (using the DB2 ODBC driver, of course).
Listing 14.5 shows the Transact SQL (TSQL) scripts for creating and popu-
lating the CourtEFiles table on the EFiling database.
Listing 14.5 The CourtEFiles Table in the EFiling Database
use EFiling
go
‘DR’,
133689,
‘Y’)
go
set nocount on
select EFileFlag
from CourtEFiles
where CourtType = @CourtType
and CourtLocation = @CourtLocation
and CaseYear = @CaseYear
and CaseClass = @CaseClass
and CaseSequence = @CaseSequence
go
Listing 14.7 contains the TSQL scripts for creating and populating the
ECases table in the Court database.
Listing 14.7 The ECases Table in the Court Database
use Court
go
462 Chapter 14: A Case Study: COM+ and Enterprise Application Integration (EAI)
When the event fires, the subscriber calls the Queued Component,
Integration.Case, which parses the pipe-delimited string into individual
input parameters to call the Case_Activation stored procedure. It then uses
the returned EFileFlag (Y in this case) along with other parameters to gen-
erate an XML document. Figure 14.9 shows how the XML document it gen-
erated looks in Internet Explorer, which has a built-in XML parser.
Notice that the first line in the XML document points to an XML-Schema
document, CaseSchema.xml. The XML-Schema document defines all valid
elements allowed in the case XML document, as shown in Figure 14.10.
18 0789724588 CH14 10/25/00 5:12 PM Page 463
After the XML document is generated, the Process method calls the
ProcessXMLDoc.asp over the Internet, passing the XML document object.
The ASP page then passes the XML content to a Queued Component,
CaseXML.XMLDoc, which builds a SQL statement based on the data and sub-
mits it to the Court database. It updates the EFileFlag of a corresponding
row in the ECases table of the Court database to Y.
As you can see from this example, by using the COM+ services, you can
greatly simply your development task whereas providing more robust solu-
tions. E-LAWS gained numerous benefits by using the COM+ alternative in
their EAI efforts: significant cost saving by avoiding some expensive third
party products and tools, more robust and flexible exception handling by
utilize the built-in exception handling capabilities of COM+ Queued
Components, better fault tolerance by taking the advantage of the loosely
coupled architecture provided by Microsoft message queuing services.
18 0789724588 CH14 10/25/00 5:12 PM Page 464
464 Chapter 14: A Case Study: COM+ and Enterprise Application Integration (EAI)
What’s Next
In this book, you have read about all the aspects of COM+ programming
from a Visual Basic developer’s perspective. I have taught you how to pro-
gram COM+ in Visual Basic by showing you abundant examples through-
out the book. In the last part of the book, I provided a complete COM+
application and a real-world case study showing how to use COM+ to solve
business problems. In addition to COM and COM+, this book also taught
you other advanced technologies, including Windows DNA, ADO, MSMQ,
design principles, UML, and XML. Most of the code in this book reflects
code used in numerous real-world enterprise and e-commerce applications I
have built for my clients. After you finish reading this book, you should
have a solid starting point to build your own real-world COM+ applications
by using Visual Basic. Empowered with the knowledge you gained from this
book you will be able to build better solutions for your organization and/or
your clients, and a brighter career in the future.
19 0789724588 Part IV 10/25/00 5:03 PM Page 465
Appendix
Appendix A: COM+ and Related Web Resources
20 0789724588 AppA 10/25/00 5:03 PM Page 466
20 0789724588 AppA 10/25/00 5:03 PM Page 467
Appendix A
COM+ and Related Web Resources
This appendix lists some useful COM+ and other related Web resources.
20 0789724588 AppA 10/25/00 5:03 PM Page 468
468 Appendix A
Appendix A 469
Other Resources
A comprehensive XML Web site: http://www.xml.com
MSDN Online XML Developer Center:
http://msdn.microsoft.com/xml/default.asp
Index
A ActiveX Data Objects,
see ADO
abstract classes, 117 ActiveX DLLs (data
Access (Northwind link libraries)
application) in-process COM compo-
databases, preparation nents, 113-116
of, 227-228 projects
resources, implementing, building, 113-116
223-228 COM components,
accessing Internet writing, 118-119
Information Server properties, 113-116
(IIS), 65 adding
ACID (atomic, consis- QC to applications,
tent, isolated & 309-313
durable) transactions, subscriptions in COM+
174-175 applications, 375-378
activating QC AddRef( ) method,
monikers, 293-295 IUnknown interface,
Active Directory 11
(MSMQ), public administering COM+
queues, 76 catalog, 350-352
Active Server Pages Windows Scripting Host
(ASP), 43 (WSH), 382
Application object, 65 administration queues
Request object, 65 (MSMQ), 78
Response object, 65 ADO (ActiveX Data
Server object, 65 Objects), 45
Session object, 65 database connections
source code example, opening (OLE DB
Retrieving Author Provider for ODBC),
Names and Displaying 47-48
Them in an HTML opening (OLE DB
Table (Listing 2.11), Provider for SQL
66-67 Server), 49-50
Visual InterDev 6.0, 67
21 0789724588 index 10/25/00 4:56 PM Page 472
474 clients
installing 481
482 instances