AdvancedASPNET

asp.net

framework

Using COM+ Services with .NET

Web Services & XML

NET Framework Class Library

.NET

dotnet debugging

NET Framework Advanced Topics

sqlxml

Client-Side Code

NET_Patterns_MV

Intro ComInterop

HTTP Handlers and Modules

Working with HashTable

CLR Architectural Overview

Thread

MicrosoftAjax

Consuming RSS Content

Design Considerations

com+.net

click here to download

Using
COM+ Services in .NET









Summary:
Add new Microsoft .NET components to existing COM and COM+
applications and they will be able to work together; this will help
you if you need to develop a .NET application that can do things like
participate in transactions, take advantage of role-based security,
or interact with a queue.


Objectives



  • Learn
    about using COM+ Services in Microsoft® .NET.


  • Create
    a serviced component.


  • Deploy
    a serviced component.



Assumptions



The following should be
true for you to get the most out of this document:



  • You
    have used Microsoft Transaction Server (MTS) and distributed
    transactions in Microsoft Visual Basic® 6.0.


  • You
    have used role-based security with COM+ Services.


  • You
    have created and used queues in COM+ Services.


  • You
    are very familiar with .NET classes.


  • You
    are familiar with creating console applications in .NET.



Contents



Using COM+ Services in
.NET
Developing Transaction-Based Components
Role-Based
Security
Using Queued Components
What's Different in COM+ Since
Visual Basic 6.0?
Summary


Using
COM+ Services in .NET



You probably have used
COM+ Applications to host components that you have written in Visual
Basic or C++. COM+ offers many valuable services, such as
transactions, queued components, just-in-time activation, role-based
security, shared properties, etc. One of the main attractions of
using COM+ to host components was that you could change the way the
component behaved without having to write any code, such as marking a
component's transaction support as Required. By setting a radio
button on your COM+ component from within the Component Services MMC
snap-in, every time your component is created, it is created in the
context of a COM+ transaction. When a component uses COM+
transactions, any database transactions are handled by the
Distributed Transaction Coordinator (DTC). Figure 1 shows an example
of setting the Required Transaction option from within the Component
Services interface.





Figure
1. Sample COM+ component that requires a transaction



Setting the security
for your component is just as easy as setting the transaction
support. You can choose which users can run which components, and
even which methods, without recompiling your code. You choose by
using the COM+ Services snap-in.


.NET
Can Use All COM+ Services



In the .NET Framework,
you can still use all of the services that COM+ offers, as long as
your classes derive from the
System.EnterpriseServices.ServicedComponent class. Any class that
derives from the ServicedComponent class will be hosted by COM+
services and can use any of the available COM+ services. Table 1
shows all of the COM+ services supported in .NET, as well as a short
description of each service.



Table
1. COM+ services available


























































COM+
Service



Description



Automatic
Transaction Processing



Applies
declarative transaction-processing features



COM
Transaction Integrator (COMTI)



Encapsulates
CICS and IMS applications in Automation objects



Compensating
Resource Managers (CRMs)



Applies
atomicity and durability properties to non-transactional resources



Just-In-Time
Activation



Activates
an object on a method call and deactivates when the call returns



Loosely
Coupled Events



Manages
object-based events



Object
Construction



Passes
a persistent string value to a class instance on construction of
the instance



Object
Pooling



Provides
a pool of ready-made objects



Queued
Components



Provides
asynchronous message queuing



Role-Based
Security



Applies
security permission based on role



Shared
Properties



Shares
state among multiple objects within a server process



Synchronization
(Activity)



Manages
concurrency



XA
Interoperability



Supports
the X/Open transaction-processing model



Reasons
to Use COM+ Services in .NET



If you will be writing
a .NET application that needs to be able to do things like
participate in transactions, take advantage of role-based security,
or interact with a queue, you will use the COM+ services offered in
.NET. .NET makes these services easy to implement, as you will learn
in this document.



Tip   If
you do not need your .NET code to work with COM+ services, that is,
you are only going to be working in the .NET Framework, do not use
System.EnterpriseServices, because there is a performance penalty.


Overview
of COM+ Component Development



When creating
components in .NET that you want to interact with COM+ Services, you
perform the following steps. Table 2 includes an explanation of each
step.




  1. Create a Class
    Library.


  2. Create
    all classes so they inherit from the
    System.EnterpriseServices.ServicedComponents class.


  3. Create
    an assembly.


  4. Create
    a strong name.




Table
2. Definition of terms used in creating a .NET component





























Term



Description



Class
Library



A
.dll project type that contains classes. There is generally no
user interface with this type of project.



System.EnterpriseServices.ServicedComponents



A
class in the .NET Framework that is required for you to be able
to interact with COM+ Services



Assembly



A
description of all of the classes and interfaces within your
project



Strong
Name



Generates
a GUID from your assembly so that your component can be
registered with COM+ Services




Developing
Transaction-Based Components



In the first part of
this document, you will learn to create a .NET component that uses
the transactional services of COM+. You will learn how to write both
the component and the front-end application that interacts with this
component.


Creating
a COM+ Transaction Component



There are a few steps
that you need to go through to get a .NET component to run under COM+
services. To begin, you have to create a class that derives from the
System.EnterpriseServices.ServicedComponent class. This base class
gives you all of the appropriate methods and properties needed to
interact with COM+ services. You need to mark the class as requiring
a new transaction, and mark any methods you create as able to
automatically complete the transaction if no errors occur. Let's try
it.




  1. Open Microsoft Visual
    Studio® .NET and create a new project as a ClassLibrary project.


  2. Rename
    the Class1.vb file to COMPlusServices.vb.


  3. Open
    the COMPlusServices.vb file and change the class name from Class1 to
    COMPlusServices.


  4. Type
    in the code shown below into this new class.



  5. Imports
    System.EnterpriseServices



  6. Imports
    System.Reflection





  7. '********************************************



  8. 'COM+ Registration
    details





  9. 'Supply the COM+
    application name



  10. <Assembly:
    ApplicationNameAttribute("ComPlusExample")>





  11. 'Supply a strong-name
    assembly



  12. <Assembly: _



  13. AssemblyKeyFileAttribute("bin/ComPlusExample.snk")>



  14. '********************************************





  15. <TransactionAttribute(TransactionOption.Required)>
    _



  16. Public Class
    COMPlusServices



  17. Inherits
    ServicedComponent





  18. Public Sub New()



  19. MyBase.New()



  20. End Sub





  21. <AutoComplete()>
    Public Function DoTransaction() _



  22. As String



  23. Return "Success
    with COM+"



  24. End Function




End Class



This code starts out by
importing a couple of namespaces to eliminate some typing when
declaring components.




  1. Next are COM+
    registration details. Enter the following line of code:



  2. 'Supply the COM+
    application name




<Assembly:
ApplicationNameAttribute("ComPlusExample")>



This line assigns the
ApplicationNameAttribute a value of ComPlusExample. This is what the
name of the COM+ application will be when it is registered in the
COM+ Catalog. After the first time this component is called, when you
go into the COM+ Applications folder in the MMC snap-in, you will see
this as the application name.



The next part of the
code declares the AssemblyKeyFileAttribute attribute:



<Assembly: _



AssemblyKeyFileAttribute("bin/ComPlusExample.snk")>



This informs the COM+
catalog where the information about the strong name is located. You
will create the .SNK file in a later step, but it is the file that
describes the component to COM+.



You will now finally
declare the Class name, COMPlusServices, using the following code.



<TransactionAttribute(TransactionOption.Required)>
_



Public Public Class
COMPlusServices



The attribute in front
of this class name informs COM+ that you wish to set the transaction
attribute to Required. Adding this line of code is the same as going
into the COM+ Applications snap-in, as shown in Figure 1, and setting
this attribute manually.



The next line of code
in the class inherits from the ServicedComponent within the
System.EnterpriseServices namespace.



Inherits
ServicedComponent



If you do not include
this line, you will be unable to make this component work under COM+.


Add
a Transaction Method



Now that the setup of
this class is complete, you can create a method that will actually do
something. The function DoTransaction in the code you wrote returns a
string value, but shows the syntax you must use to have this method
participate in a transaction.



<AutoComplete()>
Public Function DoTransaction() As String



Return "Success
with COM+"



End Function



It is important that
you prefix this method with the <AutoComplete()> attribute.
This means that as long as there is not an exception in this method,
SetComplete will be automatically called when this method finishes.
If there is an exception in the method, the .NET runtime
automatically calls the SetAbort method. This is different than
writing COM components in Visual Basic 6.0, where you had to
explicitly call SetComplete and SetAbort on your own.


Create
a Strong Name



Before compiling your
component, you need to give your assembly for this component a strong
name. If you do not do this, the COM+ Catalog will not recognize your
component and will not be able to register it. Actually, you have
already done this via the AssemblyKeyFile attribute that you used
earlier, but now you need to create the strong name and associate a
GUID with your assembly by using the Strong Name Tool (Sn.exe).




  1. Open a command prompt.


  2. To
    create the strong name, type the following at a command prompt and
    then press
    Enter.




sn -k ComPlusExample.snk




  1. Copy the
    ComPlusExample.snk file from the root folder of your hard drive
    (most likely C:/) into the bin directory under the folder where your
    project is.




You now need to compile
this program so it can build the necessary files needed to register
this component with COM+. In Visual Studio .NET, on the
Build
menu, click
Build.


Build
a Client Test Application



Now that you have built
your component, you need to build a client application to call this
component and test it out. Create a simple console application where
the module file's Main method creates an instance of your new
component, and calls the DoTransaction() method. Here are the main
steps.




  1. In Visual Basic .NET,
    create a new console application project.


  2. Add
    a reference to the component that you just created.


  3. Type
    in the code shown below.



  4. Module modMain



  5. Sub Main()



  6. Dim objCOMPlus
    As New _



  7. COMPlusJumpStart.COMPlusServices()





  8. Console.WriteLine(objCOMPlus.DoTransaction)



  9. Console.ReadLine()



  10. End Sub




End Module


Try
It Out



You are finally ready
to run this application and see it work.




  1. Open the Component
    Services MMC snap-in and verify that your component was dynamically
    registered into the COM+ Catalog. You should see something that
    looks like Figure 2.


  2. Compile
    and run the console application.






Figure
2. The new .NET serviced component in the COM+ Catalog



Role-Based
Security



When you have many
users making calls to COM components that run under COM+, you need to
verify that only specific people have access to certain components.
COM+ allows you to define roles and assign NT users to those roles.
Once these roles are in place, you can assign which roles can run
which components, and even which methods on those components can be
run.



Let's add a method to
this same COMPlusServices class to incorporate role-based security.
You will create a role called Managers, and in the new method, test
to see if the caller is in the Managers role.


Steps
to Add Role-Based Security



Instead of directly
modifying the COM+ application from the Component Services MMC
snap-in to add the security role, you will add a new attribute to
your project. You will use the SecurityRoleAttribute class to add the
new Managers role. The constructor for this class has two arguments:
role (string) and everyone (Boolean). The
role
argument specifies the name of the role to be created, and the
everyone argument
specifies whether or not the built-in Everyone group is added to the
users of the role.




  1. Add a new security
    role to the COM+ application by typing in the following code just
    below the comment COM+ Registration details.



  2. '********************************************



  3. 'COM+ Registration
    details





  4. 'Role-based security
    attribute




<Assembly:
SecurityRoleAttribute("Managers", False)>




  1. Change the security
    level setting to perform access checks at the process and component
    level. This enables the COM+ application to have a security call
    context.


  2. Bring
    up the COM+ Services snap-in.


  3. Click
    the
    Security
    tab and change the security level, as shown in Figure 3.






Figure
3. Setting the security level property in the COM+ Catalog




As an alternative to
the manual process, you could add an attribute to your component
telling it to perform access-level checks. Below is the code to add
within the COM+ Registration detail section at the top of your
COMPlusServices class.



<Assembly:
ApplicationAccessControlAttribute



(AccessChecksLevel:=AccessChecksLevelOption.ApplicationComponent)>


Check
for a Security Role



You will now add a new
method to the class called IsManager. This method will check to see
if the user is a member of the role Managers. This method is a
function and returns a Boolean value indicating whether or not the
caller is part of the Managers role. In order to get access to the
security context of the user calling the method, you need to use the
SecurityCallContext class. You get the current caller's context by
calling the CurrrentCall method. Then, you call the IsCallerInRole
method, and pass in Managers as the name of the role.




  1. Add the method shown
    below to the COMPlusServices class.



  2. Public Function
    IsManager() As Boolean





  3. Dim objCallContext
    As SecurityCallContext = _



  4. SecurityCallContext.CurrentCall






  5. IsManager = _



  6. objCallContext.IsCallerInRole("Managers")






End Function



You will now need to
rebuild the component to try out this new method.




  1. From the Visual Studio
    .NET
    Build
    menu, click
    Rebuild
    Solution
    .



Try
It Out




  1. Modify the code in the
    Sub Main() method of your console client application. Here is what
    the code should look like:



  2. Sub Main()





  3. Dim objCOMPlus As
    New _



  4. COMPlusJumpStart.COMPlusServices()






  5. Console.WriteLine(objCOMPlus.DoTransaction)



  6. Console.WriteLine(objCOMPlus.IsManager().ToString)



  7. Console.ReadLine()






End Sub




  1. Run the console
    application from the command prompt by typing in the name of the
    executable you compiled.




The first time you run
the code, you should get an exception stating that access was denied
because no users were added to the Managers role. To correct this,
add yourself as a user to Managers, and re-run the application. This
time, there should not be any exceptions. You could also add
exception handling to your code. Here is what your client should like
with the exception handling code:



Sub Main()








Try








Dim objCOMPlus As
New _



COMPlusJumpStart.COMPlusServices()








Console.WriteLine(objCOMPlus.DoTransaction)



Console.WriteLine(objCOMPlus.IsManager().ToString)



Console.ReadLine()








Catch objException As
Exception



Console.WriteLine("An
error occurred. " _



& "Details:
" _



&
objException.Message)



Console.ReadLine()








End Try








End Sub


Using
Queued Components



In COM+ applications,
it is easy to add queuing support. You just make sure that the
application is running as a server application (out of process), and
then you set its Queued and Listen properties on the Queuing tab.
Once these settings are made, client applications can call your
components asynchronously or synchronously. The beauty of this
feature is that you do not have to change the code in your COM
object; you just change its properties in the COM+ Catalog.



The .NET Framework
supports queued components, and as you might expect, you can give
your components queuing support by using attributes instead of
manually changing the COM+ Catalog.



Let's add a method to
the COMPlusServices class, and call it asynchronously using COM+
Queued Components services from a .NET client application.




  1. Make your COM+
    application a server application (out of process). This is a
    requirement for Queued Components. To do this via attributes, add
    the following code to your project:



  2. '********************************************



  3. 'COM+ Registration
    details






<Assembly:
ApplicationActivationAttribute(ActivationOption.Server)>




  1. Add queuing support to
    your component. Make it accessible to MSMQ queues, and make it
    listen to its own queue to process messages. Here is the code to do
    this via attributes:



  2. '********************************************



  3. 'COM+ Registration
    details





  4. <Assembly:
    ApplicationQueuingAttribute(Enabled:=True,




QueueListenerEnabled:=True)>




  1. Add a method to your
    class called QueueTest. Make sure that it is a subroutine; it cannot
    have a return value. Have it write a message to the Windows
    Application Log. Here is what the code should look like:



  2. Public Sub QueueTest()



  3. System.Diagnostics.EventLog.WriteEntry(_



  4. "COMPlusServces",
    "Queue Test", _



  5. Diagnostics.EventLogEntryType.Error)




End Sub



That's it. That is all
you have to do to enable your component to be a COM+ Queued
Component.


Try
It Out



You should now try out
this queued component by creating another console application and
calling this component.




  1. Create a new console
    application.


  2. Add
    the following code to the Sub Main procedure of this console
    application.



  3. Sub Main()



  4. Dim objTest As
    COMPlusJumpStart.COMPlusServices



  5. Dim strMoniker





  6. strMoniker = _



  7. "queue:/new:COMPlusJumpStart.COMPlusServices"



  8. objTest =
    GetObject(strMoniker)



  9. objTest.QueueTest()




End Sub



This asynchronously
calls the QueueTest method on your component. If you wanted to call
your method synchronously, you would call it like you have all of the
other methods in your component.



You can now run this
console application to try out this queued component.


What's
Different in COM+ Since Visual Basic 6.0?



There are many things
that you already know from Visual Basic 6.0 or COM that are the same
in .NET. But now you have the benefit of working within the .NET
Framework, which helps objects interact smoothly, with a minimum of
coding and maintenance on your part.



Typical of this sort of
streamlining, in Visual Basic 6.0, you had to explicitly call
SetComplete and SetAbort on your own. In .NET, the SetComplete and
SetAbort are called by the <AutoComplete()> attribute.



Another difference
between Visual Basic .NET and Visual Basic 6.0 is that Visual Basic
6.0 components can't make use of COM+ object pooling because they use
the Single Threaded Apartment threading model. Visual Basic .NET
components are .NET components and as such, support the Any Apartment
threading model and hence can make use of COM+ object pooling.


Summary



As you can see, the
.NET Framework makes it easy to take advantage of the services that
COM+ offers. This means that you can add new .NET components to
existing applications written with COM and COM+, and they will be
able to work together. This is very important, because it means that
you do not have to throw away all of your existing COM and COM+ code.
If you are building a new application from scratch, it is highly
recommended that you build it entirely in the .NET Framework, as it
will be much more efficient.









1









COM vb.net



Suresh
Kumar








Modern
software systems are component-based. Components make large,
enterprise-wide software systems easier to manage since system
functionality is divided among several components. VB.NET and all
other .NET-supported languages have the ability to create these
components, which can be used and reused in a variety of projects,
including (but not limited to) ASP.NET projects, Windows
applications, and unmanaged code (which is code that executes outside
the .NET Framework).



This
chapter covers the following topics:




  • Managed
    code (the code that runs on the .NET Framework) and runtime
    environments


  • The
    Common Language Runtime and its role in the .NET Framework


  • Just-in-time
    compilation of managed code


  • Code
    assemblies


  • COM+
    Component Services and its role in the .NET Framework


  • Creation
    of managed components using VB.NET


  • Serviced
    components, which take advantage of the services provided by COM+
    Component Services



5.1 The
Concept of Managed Code Execution



Before
we get into making .NET components, we need to discuss a .NET concept
called managed code. Managed code is any source code you compile that
targets the .NET Framework. All the code we've examined so far in
this book (ASP.NET, console applications, and so on) has been managed
code.



The
.NET Framework is a runtime environment in which code executes.
Internal tasks, such as allocating and freeing memory, and software
services (like the kinds discussed in Chapter 4) are handled by the
.NET Framework as well. In general terms, a runtime environment
enables user programs to take advantage of services provided by the
host operating system. A runtime environment also supplies a layer of
abstraction between user code and those services, through either an
API or some other type of interface. Almost any program you write,
regardless of the platform or language, involves interaction with a
runtime environment. (An exception to this are programs written in
assembly language, in which case the programmer is calling on the
services of the microprocessor and memory storage in a direct,
low-level fashion.)



Many
programs written for the Windows platform use the C++ programming
language and its runtime library. Before other languages and
development systems became available, this was the only choice for
developers making applications for Windows. The C/C++ runtime code
shipped as a part of Windows as a series of dynamic link libraries
(known as DLLs or .dll files). As a result, many Windows applications
could be distributed with as little as one file, the .exe file that
contained the main program code. Since no other files were required,
many developers referred to these types of applications as native
code applications since the preinstalled runtime code was quite
small.



As
new languages became available for developing Windows applications,
new runtime environments needed to be developed. VB developers using
version 6.0 or earlier might be aware of special support files that
must be installed on the deployment computer in order for VB
applications to run. These .dll files make up the VB runtime
environment. Like the C/C++ runtime code mentioned above, it provides
a code wrapper around operating system internals and services.



Some
runtime environments provide an additional layer of abstraction over
another existing runtime environment. This creates an execution
environment that can exist on multiple platforms. Programs targeted
to such a runtime don't compile to machine object code. Instead, they
compile to another language, referred to as an intermediate language.
The runtime then executes this intermediate language using an engine
built for a particular operating system. The most popular type of
intermediate language system is Java. The intermediate language for
Java is referred to as bytecode. The .NET Framework works in a
similar manner. Compiled Microsoft .NET code is referred to as
Microsoft Intermediate Language (MSIL).


5.2
The Common Language Runtime



The
runtime environment for the .NET Framework is called the Common
Language Runtime (CLR). Managed code execution happens inside the CLR
space. The goal of the CLR is to provide an environment that includes
language integration, exception handling, security, versioning,
deployment, debugging, profiling, and component interaction. Most
importantly, all of these features need cross-language support. In
other words, all the features mentioned must work in the same manner
regardless of the language used.



Metadata
makes cross-language integration possible. When you compile .NET
managed code, the metadata gets stored along with the object code.
Metadata describes to the CLR various types of information (for
example, data types, members, and references) used in the code. This
data is used by the CLR to "manage" the code execution by
providing such services as memory allocation, method invocation, and
security enforcement. It also eases deployment since references to
other objects are included along with metadata. This ensures that
your application contains all up-to-date versions of all dependent
components.


5.2.1
The Common Type System



Along
with providing information about managed code through metadata, the
CLR implements a series of data types that are cross-language
compatible. That system of data types is known as the Common Type
System (CTS). CTS data types include simple value types, classes,
enumerated value types, interfaces, and delegates.



Simple
value data types include primitive types as well as user-defined
types. Primitive types include integers, Boolean values, and strings.
These types are included in the System namespace. The data types used
thus far in the VB.NET programs in this book are also included in
this namespace. When you use these primitive types in your programs,
the language you use may already have an equivalent native data type
that corresponds to a .NET Framework type. Table 5-1 shows some data
types and their VB.NET native language equivalents.


Table
5-1 Primitive Data Types and VB.NET Equivalents










































































NET
Data Type Class Name (System Namespace)



VB.NET
Data Type



Byte



Byte



SByte



Not
supported



Int16



Short



Int32



Integer



Int64



Long



UInt16



Not
supported



UInt32



Not
supported



UInt64



Not
supported



Single



Single



Double



Double



Object



Object



Char



Char



String



String



Decimal



Decimal



Boolean



Boolean







Occasionally
you may want to define your own data types. You can do this by using
the native features of the language with which you're working. If
you're using VB.NET, you can use the Structure statement to define a
structure. This custom type needs to be type-safe for the CLR, so
it's no coincidence that it inherits from the ValueType .NET class.



techTalk:
structures and classes



Structures
and classes are quite similar. They both have members, constructors,
events, properties, fields, and constants. They can both implement
interfaces as well.



There
are some differences. Structures don't allow for inheritance;
therefore they are referred to as sealed.



Structures
can't have any constructor code, that is, you can't define an
overloaded New() subroutine with your own initialization code like
you can with a class.



When
structures are used in procedure calls, the structure is passed by
value (like primitive data types are). Classes, on the other hand,
are always passed by reference. Whether to use classes or structures
for your user-defined data types depends mostly on the complexity of
your data types. Structures are useful for defining relatively simple
data types for which the members do not use much memory and no custom
initialization code is required. If your design requires more than
this, consider defining your custom data type as a class.



Let's
quickly look at a VB.NET structure definition. Structure definitions
are placed outside of procedure definitions. You can define them at
the module level, as shown below.


Code
Sample 5-1 Build instructions: vbc /r:system.dll cs5-01.vb



Module
Module1









Structure Student



Dim
FirstName As String



Dim
LastName As String



Dim
SSN As String



Dim
ClassRank As Integer



End
Structure








Sub
Main()








Dim
udtStudent As New Student()









With udtStudent



.FirstName
= "Matt"



.LastName
= "Crouch"



.SSN
= "888-88-1234"



.ClassRank
= 2



End
With








End
Sub







End Module



The
example shows a typical structure definition. The members are
enclosed in the Structure...End Structure block that begins in line
.
The VB.NET With...End With statement in line
is
used to save some typing. It allows you to refer to the individual
members of the structure without fully qualifying the names of the
structure members.



Since
the VB.NET structures you define automatically inherit from
System.ValueType, you can treat the value type as a ValueType object.
As a demonstration, let's create a function that lists all the
members of an arbitrary structure at runtime.


Code
Sample 5-2 Build instructions: vbc /r:system.dll cs5-02.vb



Public
Sub ValueTypeDemoFunction(ByVal udt As ValueType)








Dim
mi() As MemberInfo



Dim
srmMemberInfo As MemberInfo








Dim
typTmp As Type




typTmp =
udt.GetType(udt.ToString())




mi =
typTmp.GetMembers()








Console.WriteLine("Value
Type Information" & _



Chr(13)
& Chr(10))








For
Each srmMemberInfo In mi



Console.WriteLine(srmMemberInfo.Name)



Next







End Sub



The
function ValueTypeDemoFunction() takes a ValueType object as its
parameter. Thus we can pass a VB.NET structure to this function. The
GetType() function in line
,
which is a member of the System.Object namespace, returns a
System.Type object. We use the returned System.Type object in line
to
get the member names (an array of System.Reflection.MemberInfo
objects).



If
we modify Code Sample 5-1 to add a call to ValueTypeDemoFunction() as
shown below in boldface text, we'll obtain output similar to
Figure
5-1
.







Sub
Main()








Dim
udtStudent As New Student()








With
udtStudent



.FirstName
= "Matt"



.LastName
= "Crouch"



.SSN
= "888-88-1234"



.ClassRank
= 2



End
With








ValueTypeDemoFunction(udtStudent)







End Sub


5.2.2
Just-in-Time Code Compilation



Managed
code cannot be executed directly by the CPU. It must be converted to
native executable code before running. Just-in-time (JIT) compilation
compiles MSIL code right at the moment it is needed. Optimizations
exist in the JIT compiler to ensure that only code planned for
execution gets compiled. The JIT compiler also performs security
checks and verifies type-safety.


5.2.3
Code Assemblies



I've
mentioned the topic of component-based system architectures before,
and now it's time to introduce the .NET Framework concept of this
idea. The component (that is, the unit of reuse) in the .NET
Framework is the assembly. An assembly is a collection of files,
typically .dll files and any others relating to the assembly, such as
resource files. The assembly manifest contains metadata relating to
version information, security attributes, and external code
references. It also contains information on how the pieces in the
assembly relate to each other. The assembly manifest, therefore,
constructs a logical DLL around the assembly elements.


5.2.4
Application Domains



Modern
software systems run applications that are isolated from the internal
execution of the operating system and other programs. The reason for
this is to protect the operating system from crashing if the
application attempts to access memory being used by another
application. Another situation that could cause a crash is an
internal error in the application that causes the operating system to
crash. Fortunately, all versions of Windows offer process protection
to prevent this problem. Each application running under Windows has
its own memory space, and memory used by other running applications
is not visible from any other application.



The
.NET Framework extends the capabilities of protected process spaces
by building this functionality into the CLR. These protected spaces
are known as application domains. In addition to the fault tolerance
that process isolation provides, application domains can enforce
security policies, thereby granting or denying users and groups the
right to run the application. Application domains also consume fewer
system resources than traditional Windows processes because they can
provide fault tolerance by taking advantage of the inherit
type-safety of the .NET Framework code.


5.3 COM+
Component Services



The
.NET Framework leverages many existing Windows services to make it a
more robust application environment. A particular technology that
deserves attention is COM+ Component Services. These technologies
were the predecessors to the .NET Framework. To see how COM+
Component Services fits into the .NET Framework arena, let's explore
a little about these technologies.


5.3.1
Overview of COM



The
Component Object Model (COM) was designed to address the shortcomings
of conventional object-oriented languages like C++ and traditional
binary software distribution of code. COM is about not a particular
type of software but rather a philosophy of programming. This
philosophy is manifested in the COM specification. The specification
explicitly states how a COM object should be constructed and what
behaviors it should have.



COM
objects are roughly equivalent to normal classes, but COM defines how
these objects interact with other programs at the binary level. By
binary, I mean compiled code, with all the methods and member
variables of the class already built into an object. This "binary
encapsulation" allows you to treat each COM object as a "black
box." You can call the black box and use its functionality
without any knowledge of the inner workings of the object's
implementation. In the Windows environment, these binary objects (COM
objects) are packaged as either DLLs or executable programs. COM is
also backed by a series of utility functions that provide routines
for instantiating COM objects, process communication, and so on.



COM
was the first methodology to address object-oriented software reuse.
COM has enjoyed great commercial success; many third-party software
vendors provide COM objects to perform a wide range of tasks, from
e-mail to image processing. COM is also highly useful for creating
components called business objects. Business objects are COM objects
in the strict sense, but they are used to encapsulate business rules
and logic. Typically these business objects are tied to database
tables. The objects move around the database according to the
business rules implemented in the COM object.



Generally,
several smaller business objects work together to accomplish a larger
task. To maintain system integrity and to prevent the introduction of
erroneous data into the application, transactions are used. A
software service called Microsoft Transaction Server (MTS) is used to
manage these transactions. We'll cover the function of MTS (and its
successor, COM+) in Section 5.3.3.


5.3.2
Overview of Transactions



Simply
stated, a transaction is a unit of work. Several smaller steps are
involved in a transaction. The success or failure of the transaction
depends on whether or not all of the smaller steps are completed
successfully. If a failure occurs at any point during a transaction,
you don't want any data changes made by previous steps to remain. In
effect, you want to initiate an "undo" command, similar to
what you would do when using, say, a word processor. A transaction is
committed when all steps have succeeded. A failed transaction causes
a rollback to occur (the "undo" operation).



Well-designed
transactions conform to ACID principles. ACID is an acronym for
Atomicity, Consistency, Isolation, and Durability.




  • Atomicity
    means that either the operation that the component performs is
    completely successful or the data that the component operates on
    does not change at all. This is important because if the transaction
    has to update multiple data items, you do not want to leave it with
    erroneous values. If a failure occurs at any step that could
    compromise the integrity of the system, the changes are undone.


  • Consistency
    deals with preserving the system state in the case of a transaction
    failure.


  • Isolation
    means that a transaction acts as though it has complete control of
    the system. In effect, this means that transactions are executed one
    at a time. This process keeps the system state consistent; two
    components executed at the same time that operate on the same data
    can compromise the integrity of the system.


  • Durability
    is the ability of a system to return to any state that was present
    before the execution of a transaction. For example, if a hard drive
    crash occurs in the middle of a transaction, you can restore the
    original state from a transaction log stored on another disk to
    which the system recorded.




A
classic example of a transaction operation is a bank transfer that
involves a transfer of funds from one account to another (a credit
and a debit). Such a transaction moves through the following steps.




  1. Get
    the amount to be transferred, and check the source account for
    sufficient funds.


  2. Deduct
    the transfer amount from the source account.


  3. Get
    the balance of the destination account, and add the amount to be
    transferred to the balance.


  4. Update
    the destination account with the new balance.




Suppose
a system failure occurs at step 4. The source account had the
transfer amount deducted but the amount was not added to the
destination account. Therefore, the money from the source account
gets lost. Clearly, this is not good because the integrity of the
database has been damaged.



Each
of the account transfer's steps can be checked for success or
failure. If a failure occurs before all values have been updated, the
program needs to undo the deduction made to the source account.
That's a rollback. If every step succeeds, the program needs to apply
all the changes made to the database. That's when a commit operation
is performed.


5.3.3
Automatic Transactions



Transactions
have been in widespread use since the early days of enterprise
computing. Many database systems include internal support for
transactions. Such database systems contain native commands to begin,
abort, and commit transactions. This way, several updates to database
data can be made as a group, and in the event of a failure, they can
be undone. Using a database's internal transaction-processing system
is referred to as manual transaction processing.



Automatic
transactions differ from manual transactions because automatic
transactions are controlled by a system external to the database
management system (DBMS). Earlier versions of Windows (95/98/NT)
provide automatic transaction services using Microsoft Transaction
Server (MTS). MTS works by coordinating database updates made by COM
components grouped into a logical unit called a package. An MTS
package defines the boundary of the transaction. Each component in
the package participates in the transaction. After a component
performs a piece of work (such as updating the database), it informs
MTS that it successfully (or unsuccessfully) performed its share of
the transaction. MTS then makes a determination to continue based on
the success of the last component's signal of success or failure. If
the transaction step was unsuccessful, the transaction is aborted
immediately, and MTS instructs the DBMS to undo any changes made to
data. If the step was successful, the transaction continues with the
other steps. If all steps execute successfully, MTS commits the
transaction and tells the DBMS to commit changes to the data.


5.3.4
COM+ Applications



With
the release of Windows 2000 came the next version of COM, dubbed
COM+. COM+'s raison d'être is the unification of COM and MTS.
COM+ also offers performance improvements over MTS by implementing
technologies such as object pooling, which maintains an active set of
COM component instances. Other performance-enhancing features include
load balancing, which distributes component instances over multiple
servers, and queued components, which uses Microsoft Message Queue
Server to handle requests for COM+ components.



The
services that were formerly provided by MTS are known as COM+
Component Services in the COM+ model. COM+ Component Services works
in a similar manner to MTS. Packages are now referred to as COM+
applications. Participating transactional components are grouped into
applications in the same way components were grouped into packages
under MTS.



Individual
COM+ components in an application can be assigned different levels of
involvement in an automatic transaction. When setting up COM+
applications, each component can have the levels of automatic
transaction support shown in Table 5-2.



Table
5-2 COM+ Automatic Transaction Support Levels


































Transaction
Support



Description



Disabled



-No
transaction services are ever loaded by COM+ Component Services.



Not
Supported



-This
is the default setting for new MTS components. Execution of the
component is always outside a transaction regardless of whether
or not a transaction has been initiated for the component.



Supported



-You
may run the component inside or outside a transaction without any
ill effects.



Required



The
component must run inside a transaction.



Required
New



-The
component needs its own transaction in which to run. If the
component is not called from within a transaction, a new
transaction is automatically created.






5.3.5
COM+ Security



Security
is of paramount importance, especially for applications intended to
run on the Internet. In the past, programming security features into
an Internet application was largely a manual effort. Often it
consisted of custom security schemes that did not necessarily
leverage the existing security infrastructure provided by the
operating system. Besides being difficult to maintain, such security
systems are typically costly to develop.



COM+
Component Services provides a security infrastructure for
applications that uses Windows 2000/XP users and groups. COM+
security is declarative, which means you designate which users and
groups have permission to access a COM+ application. This is done by
defining roles for application access.



A
role is a defined set of duties performed by particular individuals.
For example, a librarian can locate, check out, and shelve books. The
person fulfilling the librarian role is permitted to perform such
duties under the security policies defined for that role. An
administrator is responsible for assigning users and groups to roles.
The roles are then assigned to a COM+ application.



This
role-based security is not only easy to implement (it can be done by
the system administrator) but it also typically doesn't require the
programmer to work on the components to implement any security code.
When a call is made to a component running under COM+ Component
Services, COM+ checks the user/group identity of the caller and
compares it against the roles assigned to the component. Based on
that comparison, the call is allowed or rejected.



You
can provide additional security checking by using procedural
security. This type of security is implemented programmatically using
special .NET classes designed for interaction with COM+ Component
Services.


5.3.6
.NET Classes and COM+ Component Services



Thus
far, our discussions about COM+ Component Services, transactions, and
security deal specifically with COM+ components. COM+ predated the
.NET Framework and has had much success in enterprise-wide
applications developed using Microsoft Visual Studio 6.0. But how
does .NET fit into all of this?



COM+
still remains a dominant technology and is a significant part of
Windows. The architecture for .NET managed components was designed to
take advantage of all the features COM+ Component Services has to
offer (object pooling, transaction processing, security, and so on)
by providing classes to implement those features. These concepts are
very important when developing Web applications, too.


5.4 Using
VB.NET to Develop Managed Components



In
this section I'll present the concepts you need to understand to
build managed components using VB.NET.


5.4.1
Defining Namespaces



Namespaces
are a way to hierarchically organize class names. Namespaces become
especially useful when the number of classes available to you is
quite large. You've been exposed to namespaces quite a bit already in
this book. Whenever you write VB.NET code, you use .NET classes in
the System namespace. For example, when you use the
Console.WriteLine() command, you are using an assembly called Console
that exists in the System namespace. In the code, Console is not
fully qualified because it is assumed to be a part of the System
namespace based on the uniqueness of the name Console. You could
fully qualify the statement this way:


System.Console.WriteLine(...)






When
you create a new .NET assembly (program), a default root namespace is
assigned to the assembly. This name is typically the name of your
project. However, you are free to assign your own namespace names.


5.4.2
Using the Class Library



Managed
components start with a VS.NET project called a class library. The
class library puts the infrastructure in place for creating an
assembly for packing a component.
Figure
5-2

shows the selections used to create a new class library project.







You
can specify several project properties and options for class library
projects. Of particular interest are the root namespace and the
assembly name. VB.NET creates default names for both of these
options. You can view and modify them by selecting the
Project'Properties menu. This opens the Property Pages dialog shown
in
Figure
5-3
.







By
default, VB.NET assigns the name you specified for the project as the
names for the assembly and the root namespace. You can override these
default names with better, more descriptive ones. Given these new
designations, references to classes inside the assembly would be made
in the following manner. (This example shows the usage of Dim to
declare an object for a particular class inside the assembly.)



Dim
objMyObject As objMyExample.SomeClassInTheAssembly



.NET
applications that you write need to include a reference to each
assembly you wish to use inside the main application. You can add a
reference to an assembly by right-clicking on the References folder
(see
Figure
5-4
)
and selecting Add Reference.... VS.NET displays a dialog with a list
of available assemblies (see
Figure
5-5
).
Those assemblies that are a part of the .NET Framework distribution
are displayed in the list. To add a reference to another assembly,
click the Browse... button. Then you'll be able to select the
assembly file you need.



The
Property Pages dialog



The
Add Reference dialog


5.4.3
Using Component "Plumbing" Code



When
class library projects are started in VB.NET, some stub code is
generated for you. A new, "empty" component's "plumbing"
code looks something like this:



Public
Class Class1








End
Class



The
VS.NET IDE adds this code for you for an empty class called Class1. A
.vb file is also generated that contains the class's code (which
VS.NET should be displaying in the front-most window). Give the class
another name (in the example below, we'll name it CTaxComponent) by
changing it in the editor and then renaming the class file in the
Solution Explorer to CTaxComponent. The code should now say:



Public
Class CTaxComponent








End
Class


5.4.4
Adding Initialization Code



Whenever
your component is instantiated, the New() subroutine is called. Any
initialization code you require goes here. As an illustration,
suppose your class contains a private member variable that you wish
to have an initial default value when the component is instantiated.
Such initialization code could look like the following.



Private
m_dblTaxRate As Double








Public
Sub New()



MyBase.New








'
Specify default percent tax rate



m_dblTaxRate
= 5








End
Sub


5.4.5
Creating Methods



In
Chapter 2 we discussed classes and several class concepts, such as
methods. When creating class libraries and components, you'll notice
that the same principles apply to components. Methods are simply
member functions and subroutines. In order for other .NET
applications to access methods you define, you need to expose them by
marking them for public access. Here's a public method definition and
implementation to illustrate.



Public
Function CalculatePriceAfterTax( _



ByVal
Amount As Double) As Double








CalculatePriceAfterTax
= Amount * (1 + (m_dblTaxRate / 100))








End
Function


5.4.6
Creating Properties



Component
properties are created in the same way as properties for regular
classes. Like methods, they should be marked for public access. The
property in the sample code below sets or gets the value of the
private class member variable used in the previous example.



Public
Property TaxRate() As Double



Get



TaxRate
= m_dblTaxRate



End
Get



Set(ByVal
Value As Double)



m_dblTaxRate
= Value



End
Set



End
Property


5.4.7
Using the Class Library in an Application



The
preceding sections described all the steps required to create a class
library that can function as an independent component in another
application. Now the class library needs to be tested. You can create
another .NET application that references the assembly and write code
to call the CTaxComponent class. The code below is a simple console
application that uses the class library.



Imports
System



Imports
SCDemoClasses



Imports
Microsoft.VisualBasic








Module
Module1








Sub
Main()



Dim
objTaxComponent As New CTaxComponent()



Dim
dblItemPrice As Double = 9.99



Dim
dblTotalPrice As Double








With
objTaxComponent



.TaxRate
= 7



dblTotalPrice
= _




.CalculatePriceAfterTax(dblItemPrice)



End
With








Console.WriteLine("{0}
after tax is {1}", _



FormatCurrency(dblItemPrice),
_



FormatCurrency(dblTotalPrice))








End
Sub








End
Module



This
code produces the following output (assuming U.S. currency):



$9.99
after tax is $10.69



WARNING



Before
this program can compile and run, a reference to the assembly DLL,
SCDemo (used in the example), must be added as a reference to the
hosting application (the sample console application) using the Add
Reference... command from the menu.


Figure
5-6

Database diagram of a supermarket ordering and inventory system


Table
5-3 Column Names and Data Types for the Inventory Table


























Column
Name



Data
Type



BinNumber



Varchar(255)



SKU



Varchar(255)



Quantity



Int







Table
5-4 Column Names and Data Types for the Product Table


























Column
Name



Data
Type



SKU



Varchar(255)



Description



Varchar(255)



UnitPrice



Decimal



StockingBinNumber



Varchar(255)






Table
5-5 Column Names and Data Types for the Orders Table


























Column
Name



Data
Type



OrderNumber



Varchar(255)



SupplierName



Varchar(255)



OrderReceived



Bit






Table
5-6 Column Names and Data Types for the OrderDetails Table






































Column
Name



Data
Type



OrderNumber



Varchar(255)



LineItemNumber



Int



SKU



Varchar(255)



QuantityReceived



Int



Quantity



Int



ID



Identity







TIP



Consult
your SQL Server documentation about creating a new database and
setting up tables if you are not familiar with the process. Recruit
the help of a database administrator if you need assistance in
obtaining authorization to create a database (if you're in an
enterprise/shared database environment). We will discuss database
access in detail in Chapter 7.


5.6 Building
VB.NET Serviced Components



Now
it's time for you to use serviced components to build the supermarket
ordering and inventory system outlined in the previous section!
You'll work through each step in the process, from designing to
coding and then testing the components in an ASP.NET application.


LAB 5-1
An Ordering and Inventory System Made with Serviced Components



STEP
1.
Create a new VB.NET class library project.




  1. Open
    VS.NET and select File'New'Project.


  2. Highlight
    Class Library and give the new project the name "SupermarketSystem".




STEP
2.
Design the components.




  1. Consider
    the design of your system. First, establish the users of the system
    and their roles:



    • Supervisors
      (adding new products, receiving orders, updating inventory)


    • Receiving
      Clerks (receiving orders)


    • Suppliers
      (shipping goods to the supermarket and supplying order information)



  2. Organize
    these functions into a series of classes for your class library.
    Four classes will handle the business logic of the different duties
    that each user will perform: Product, ReceiveOrder, UpdateInventory,
    and CreateOrder.
    Lab
    Figure 5-1

    shows the classes and the methods of each class.



ab
Figure 5-1

The classes and methods of the Supermarket program




  1. Adding
    a new product to the system, updating inventory, receiving an order,
    and creating an order all involve operations on the database tier of
    the application. For some of these operations, database updates can
    occur in multiple tables. It's important to keep data integrity
    across these tables. You also want to implement security features so
    the users perform only the functions that their roles designate.
    You'll see this functionality develop as you code the components.




STEP
3.
Write code for component functionality.



In
the process of implementing the components, we'll discuss these
topics:






  • How
    to use the System.EnterpriseServices namespace and one particular
    class: the ServicedComponent class



  • How
    to use class attributes to specify the type of COM+ support you want
    your components to have



  • How
    to specify an interface from which to call the components



  • How
    to control the transaction inside the components




  1. Create
    the classes outlined in the specifications in Step 2: Product,
    ReceiveOrder, UpdateInventory, and CreateOrder. For each of these
    classes, right-click the project in the Solution Explorer and select
    Add Class...from the menu. Name the classes as listed above. VS.NET
    will "stub out" an empty class definition for each class.


  2. Building
    serviced components requires support from COM+ Component Services.
    The .NET Framework implements this support through classes in the
    System.EnterpriseServices namespace. VS.NET doesn't include this
    reference by default, so you'll need to add it yourself. Select
    Project'Add Reference from the menu and select
    System.EnterpriseServices from the list. Click the Select button and
    then click OK.


  3. Now
    you're ready to add component functionality. Start by adding the
    following code for the Product class (Product.vb).



Lab
Code Sample 5-1



Imports
System



Imports
System.Reflection



Imports
System.EnterpriseServices



Imports
System.Data



Imports
System.Data.SqlClient









<Assembly:
ApplicationName("Supermarket")>




<Assembly:
ApplicationActivation(ActivationOption.Library)>




<Assembly:
AssemblyKeyFile("KeyFile.snk")>




Namespace
Supermarket









Public Interface
IProduct



Function
Create(ByVal SKU As String, _



ByVal
Description As String, _



ByVal
UnitPrice As Decimal, _



ByVal
StockingBinNumber As String) _



As
Boolean



End
Interface









<ConstructionEnabled(
_



[Default]:="Default
Construction String"), _



Transaction(TransactionOption.Required)>
_



Public
Class Product




Inherits
ServicedComponent




Implements
Supermarket.IProduct








Public
Sub New()








End
Sub








Protected
Overrides Sub Construct( _




ByVal
constructString As String)








End
Sub








Function
Create(ByVal SKU As String, _



ByVal
Description As String, _



ByVal
UnitPrice As Decimal, _



ByVal
StockingBinNumber As String) _



As
Boolean _



Implements
Supermarket.IProduct.Create








Dim
objCnn As SqlConnection



Dim
objCmd As SqlCommand



Dim
objParam As SqlParameter



Dim
intRowsReturned As Integer








Try



objCnn
= New SqlConnection()



objCnn.ConnectionString
= _



"Initial
Catalog=Supermarket;Data Source=localhost;uid=sa;pwd="



objCnn.Open()



objCmd
= objCnn.CreateCommand()



objCmd.CommandText
= _



"INSERT
INTO Product " & _



"(
SKU, Description, UnitPrice, StockingBinNumber ) " & _



"VALUES
( @sku, @description, @unitprice, @stockingbinnumber )"








objParam
= New SqlParameter()



With
objParam



.ParameterName
= "@sku"



.SqlDbType
= SqlDbType.VarChar



.Direction
= ParameterDirection.Input



.Value
= SKU



End
With



objCmd.Parameters.Add(objParam)








objParam
= New SqlParameter()



With
objParam



.ParameterName
= "@description"



.SqlDbType
= SqlDbType.VarChar



.Direction
= ParameterDirection.Input



.Value
= Description



End
With








objCmd.Parameters.Add(objParam)



objParam
= New SqlParameter()



With
objParam



.ParameterName
= "@unitprice"



.SqlDbType
= SqlDbType.Decimal



.Direction
= ParameterDirection.Input



.Value
= UnitPrice



End
With



objCmd.Parameters.Add(objParam)








objParam
= New SqlParameter()



With
objParam



.ParameterName
= "@stockingbinnumber"



.SqlDbType
= SqlDbType.VarChar



.Direction
= ParameterDirection.Input



.Value
= StockingBinNumber



End
With



objCmd.Parameters.Add(objParam)








intRowsReturned
= _



objCmd.ExecuteNonQuery()



Create
= True




ContextUtil.SetComplete()








Catch
E As Exception



Create
= False




ContextUtil.SetAbort()



Finally



objCnn.Close()



End
Try








End
Function








End
Class








End
Namespace



This
code implements a serviced component. As mentioned before, a serviced
component is a .NET class that uses COM+ Component Services. The
class becomes a serviced component when it derives from the
System.EnterpriseServices.ServicedComponent class. Before we talk
more about the ServicedComponent class, let's first investigate the
beginning of the code where some assembly-level attributes are
declared.



Making
a serviced component requires you to provide information to COM+
Component Services about the component's configuration. First you
designate to which package, or COM+ application, the component will
belong. That designation is made with the ApplicationName
assembly-level attribute shown in line
.
The name for the order and inventory application is "Supermarket".
COM+ applications are listed in the Component Services Console.



COM+
Component Services provides a runtime environment for assembly
components. You can also control where the components are
activated—in the same process as the creator of the object
(IIS) or in a separate system process (dllhost.exe). You can control
application activation by specifying the ApplicationActivation
assembly attribute shown in line
.
This code specifies ActivationOption.Library, which causes components
to be activated in the creator's process. So, if you were running
these components inside ASP.NET Web Forms, the creating process would
be the Web server, IIS. The ActivationOption.Server option provides
faster performance; this option, which runs the component in a system
process, provides more isolation, so the component's execution won't
adversely affect the execution of IIS. One advantage to using
ActivationOption.Library is to make debugging easier.



The
final assembly-level attribute, AssemblyKeyFile, specifies a shared
name for the assembly (see line
).
The shared name is sometimes referred to as a strong name. A shared
name ensures that a name assigned to a component is unique. This is
accomplished by using a public/private cryptographic key pair to sign
each shared component in the assembly. The public key is then
published with the assembly. Besides specifying this attribute in the
code, you'll need to actually generate the key file specified for the
assembly-level attribute. To do this, use sn.exe, the Strong Name
Utility.
Lab
Figure 5-2

shows how this is done.





  1. Lab
    Figure 5-2

    Specifying a strong name for the assembly using sn.exe




The
usage of sn.exe, as shown in
Lab
Figure 5-2
,
outputs a .snk file. This is the file name that you specify in the
AssemblyKeyFile attribute in the code (change the path to the
location of your generated key file). You must complete this step
before you compile your project. These assembly-level attributes
appear once in the project's code. The class file in which they
appear is irrelevant, but they must be declared once and only once in
the assembly code.



Warning



Never
let anybody else have access to your .snk file. It contains a private
key that is for your use only.



Let's
move on to the main part of the component code. Line
defines
the Supermarket namespace, which will contain all the components for
the Supermarket application. Line
declares
an interface you'll use in the client application (the ASP.NET Web
Form) to call the component. The interface has one function,
Create(), which you'll use to set up a new product in the Product
database table.



The
component's class definition comes next. The code beginning in line
shows
two class-level attributes for the Product class. The first one,
ConstructionEnabled, specifies that the class will be able to use
COM+ constructor strings. A constructor string is a string passed
into the activation procedure of the component. This string can be
specified by a system administrator inside the Component Services
Console. A constructor string can contain any information, but
typically you'll use it for passing initialization information to the
component. If no constructor string is given for the component in the
Component Services Console (see
Lab
Figure 5-3
,
which shows the Component Services Console dialog box for setting a
constructor string), a default constructor string is used by
specifying it in the attribute as shown in the continuation of line
.
The other class-level attribute specifies how the class will
participate in transactions. Use the Transaction attribute to specify
the participation level. In this code, Transaction uses
TransactionOption.Required. This indicates that the Product component
should participate in an existing transaction if one already exists.
If no transaction exists, a new transaction will begin and the
Product component will execute within the boundaries of that
transaction.




  1. Lab
    Figure 5-3

    Specifying a constructor string in Component Services




The
class definition continues with the specification of an inherited
class and an Implements statement. Since this will be a serviced
component, it needs to derive from the ServicedComponent class, as
shown in line
.
In line
the
class implements the IProduct interface specified earlier.



Line
provides
implementation support for constructor strings. Since the code
specified that the component will have support for constructor
strings with the class-level attribute ConstructionEnabled, here
there is an override method for the Construct() sub. This method will
be called upon object construction, and the constructor string
assigned to the component will be available in the constructString
variable.



Now
follows the implementation of the Create() method, which accepts a
new product's SKU number, product description, unit price, and
stocking bin number. The Create() method then executes the
appropriate ADO.NET code to insert a new row into the Product
database table. (ADO.NET code will be discussed in Chapter 7. For
now, just be aware that the Product component contains this method
that will be callable from an ASP.NET Web Form.)



Although
the discussion of the ADO.NET code details is deferred, it's
important to point out two details in the Create() method. These are
two methods of the ContextUtil object, SetComplete() in line
and
SetAbort() in line
.
These methods cast a vote in the current transaction. Typically, when
you have verified that all of the code inside a particular component
method has executed successfully, a call to SetComplete() is made.
This tells COM+ Component Services that a unit of work has succeeded
and that database integrity and consistency is assured for updates
made by the unit of work. It's a signal that the transaction can
continue running. Conversely, if a failure occurs (an exception or
other user-defined error or condition), the program needs to cast a
"fail" vote for the transaction by calling SetAbort(). This
will cause COM+ Component Services to stop the transaction and roll
back any changes made to the database by previous steps in the
transaction.




  1. Now
    that you understand the basic pieces of the Product class code, add
    the code for the remaining classes (CreateOrder, UpdateInventory,
    and ReceiveOrder). The implementations are all different, of course,
    but they follow pretty much the same conventions as the Product
    component.



Lab
Code Sample 5-2



Imports
System



Imports
System.Reflection



Imports
System.EnterpriseServices



Imports
System.Data



Imports
System.Data.SqlClient













Namespace
Supermarket








Public
Interface ICreateOrder



Function
Create(ByVal OrderNumber As String, _



ByVal
SupplierName As String) _



As
Boolean



Function
AddItems(ByVal OrderNumber As String, _



ByVal
SKU As String, _



ByVal
Quantity As Integer) _



As
Boolean



End
Interface








<ConstructionEnabled(
_



[Default]:="Default
Construction String"), _



Transaction(TransactionOption.Required)>
_



Public
Class CreateOrder



Inherits
ServicedComponent



Implements
ICreateOrder








Public
Sub New()








End
Sub








Public
Function Create(ByVal OrderNumber As String, _



ByVal
SupplierName As String) _



As
Boolean _



Implements
ICreateOrder.Create








Dim
objCnn As SqlConnection



Dim
objCmd As SqlCommand



Dim
objParam As SqlParameter



Dim
intRowsAffected As Integer








Try



objCnn
= New SqlConnection()



objCnn.ConnectionString
= _



"Initial
Catalog=Supermarket;Data Source=localhost;uid=sa;pwd="



objCnn.Open()



objCmd
= objCnn.CreateCommand()








objCmd.CommandText
= _



"INSERT
INTO Orders " & _



"(
OrderNumber, SupplierName, OrderReceived ) " & _



"VALUES
( @OrderNumber, @SupplierName, @OrderReceived )"








objParam
= New SqlParameter()



With
objParam



.ParameterName
= "@OrderNumber"



.SqlDbType
= SqlDbType.VarChar



.Direction
= ParameterDirection.Input



.Value
= OrderNumber



End
With



objCmd.Parameters.Add(objParam)








objParam
= New SqlParameter()



With
objParam



.ParameterName
= "@SupplierName"



.SqlDbType
= SqlDbType.VarChar



.Direction
= ParameterDirection.Input



.Value
= SupplierName



End
With



objCmd.Parameters.Add(objParam)








objParam
= New SqlParameter()



With
objParam



.ParameterName
= "@OrderReceived"



.SqlDbType
= SqlDbType.Bit



.Direction
= ParameterDirection.Input



.Value
= False



End
With



objCmd.Parameters.Add(objParam)








intRowsAffected
= objCmd.ExecuteNonQuery()








Create
= True



ContextUtil.SetComplete()



Catch
E As Exception



Create
= False



ContextUtil.SetAbort()



End
Try



End
Function








Public
Function AddItems(ByVal OrderNumber As String, _



ByVal
SKU As String, _



ByVal
Quantity As Integer) _



As
Boolean _



Implements
ICreateOrder.AddItems








Dim
objCnn As SqlConnection



Dim
objCmd As SqlCommand



Dim
objParam As SqlParameter



Dim
intMaxLineNumber As Integer



Dim
intRowsAffected As Integer



Dim
objTemp As Object








Try



objCnn
= New SqlConnection()



objCnn.ConnectionString
= _



"Initial
Catalog=Supermarket;Data Source=localhost;uid=sa;pwd="



objCnn.Open()



objCmd
= objCnn.CreateCommand()








objCmd.CommandText
= _



"SELECT
MAX( LineItemNumber ) " & _



"FROM
OrderDetails " & _



"WHERE
OrderNumber = @OrderNumber"



objParam
= New SqlParameter()



With
objParam



.ParameterName
= "@OrderNumber"



.SqlDbType
= SqlDbType.VarChar



.Direction
= ParameterDirection.Input



.Value
= OrderNumber



End
With



objCmd.Parameters.Add(objParam)








objTemp
= objCmd.ExecuteScalar()



If
TypeOf objTemp Is DBNull Then



intMaxLineNumber
= 1



Else



intMaxLineNumber
= CType(objTemp, Integer)



intMaxLineNumber
+= 1



End
If








objCmd
= objCnn.CreateCommand()



objCmd.CommandText
= _



"INSERT
INTO OrderDetails " & _



"(
OrderNumber, LineItemNumber, SKU, " & _



"QuantityReceived,
Quantity ) VALUES " & _



"(
@OrderNumber, @LineNumber, @SKU, " & _



"@QuantityReceived,
@Quantity )"



objParam
= New SqlParameter()



With
objParam



.ParameterName
= "@OrderNumber"



.SqlDbType
= SqlDbType.VarChar



.Direction
= ParameterDirection.Input



.Value
= OrderNumber



End
With



objCmd.Parameters.Add(objParam)








objParam
= New SqlParameter()



With
objParam



.ParameterName
= "@LineNumber"



.SqlDbType
= SqlDbType.Int



.Direction
= ParameterDirection.Input



.Value
= intMaxLineNumber



End
With



objCmd.Parameters.Add(objParam)








objParam
= New SqlParameter()



With
objParam



.ParameterName
= "@SKU"



.SqlDbType
= SqlDbType.VarChar



.Direction
= ParameterDirection.Input



.Value
= SKU



End
With



objCmd.Parameters.Add(objParam)








objParam
= New SqlParameter()



With
objParam



.ParameterName
= "@QuantityReceived"



.SqlDbType
= SqlDbType.Int



.Direction
= ParameterDirection.Input



.Value
= 0



End
With



objCmd.Parameters.Add(objParam)








objParam
= New SqlParameter()



With
objParam



.ParameterName
= "@Quantity"



.SqlDbType
= SqlDbType.Int



.Direction
= ParameterDirection.Input



.Value
= Quantity



End
With



objCmd.Parameters.Add(objParam)








intRowsAffected
= objCmd.ExecuteNonQuery()








AddItems
= True



ContextUtil.SetComplete()








Catch
E As Exception



AddItems
= False



ContextUtil.SetAbort()



Finally



objCnn.Close()



End
Try








End
Function








End
Class



End
Namespace


Lab
Code Sample 5-3



Imports
System



Imports
System.Reflection



Imports
System.EnterpriseServices



Imports
System.Data



Imports
System.Data.SqlClient








Namespace
Supermarket








Public
Interface IUpdateInventory



Function
Update(ByVal BinNumber As String, _



ByVal
SKU As String, _



ByVal
Quantity As Integer) _



As
Boolean



Function
GetStockingLocation(ByVal SKU As String) _



As
String



End
Interface








<ConstructionEnabled(
_



[Default]:="Default
Construction String"), _



Transaction(TransactionOption.Required)>
_



Public
Class UpdateInventory



Inherits
ServicedComponent



Implements
IUpdateInventory








Public
Sub New()








End
Sub








Public
Function GetStockingLocation( _



ByVal
SKU As String) As String _



Implements
IUpdateInventory.GetStockingLocation








Dim
objCnn As SqlConnection



Dim
objCmd As SqlCommand



Dim
objParam As SqlParameter



Dim
objTemp As Object








Try



objCnn
= New SqlConnection()



objCnn.ConnectionString
= _



"Initial
Catalog=Supermarket;Data Source=localhost;uid=sa;pwd="



objCnn.Open()



objCmd
= objCnn.CreateCommand()








objCmd.CommandText
= _



"SELECT
StockingBinNumber " & _



"FROM
Product WHERE SKU = @SKU"








objParam
= New SqlParameter()



With
objParam



.ParameterName
= "@SKU"



.SqlDbType
= SqlDbType.VarChar



.Direction
= ParameterDirection.Input



.Value
= SKU



End
With



objCmd.Parameters.Add(objParam)








objTemp
= objCmd.ExecuteScalar()



If
TypeOf objTemp Is DBNull Then



GetStockingLocation
= ""



Else



GetStockingLocation
= _



CType(objCmd.ExecuteScalar(),
String)



End
If



ContextUtil.SetComplete()








Catch
E As Exception



ContextUtil.SetAbort()



GetStockingLocation
= ""



Finally



objCnn.Close()



End
Try








End
Function








Private
Function InventoryRecExists( _



ByVal
SKU As String, _



ByVal
StockingBinNumber As String) _



As
Boolean








Dim
objCnn As SqlConnection



Dim
objCmd As SqlCommand



Dim
objParam As SqlParameter



Dim
intRowCount As Integer



Dim
objTemp As Object








Try



objCnn
= New SqlConnection()



objCnn.ConnectionString
= _



"Initial
Catalog=Supermarket;Data Source=localhost;uid=sa;pwd="



objCnn.Open()



objCmd
= objCnn.CreateCommand()








objCmd.CommandText
= _



"SELECT
COUNT(*) FROM Inventory " & _



"WHERE
SKU = @SKU AND " & _



"BinNumber
= @StockingBinNumber"



objParam
= New SqlParameter()



With
objParam



.ParameterName
= "@SKU"



.SqlDbType
= SqlDbType.VarChar



.Direction
= ParameterDirection.Input



.Value
= SKU



End
With



objCmd.Parameters.Add(objParam)








objParam
= New SqlParameter()



With
objParam



.ParameterName
= "@StockingBinNumber"



.SqlDbType
= SqlDbType.VarChar



.Direction
= ParameterDirection.Input



.Value
= StockingBinNumber



End
With



objCmd.Parameters.Add(objParam)








objTemp
= objCmd.ExecuteScalar()



If
TypeOf objTemp Is DBNull Then



intRowCount
= 0



Else



intRowCount
= CType(objTemp, Integer)



End
If








If
intRowCount > 0 Then



InventoryRecExists
= True



Else



InventoryRecExists
= False



End
If








ContextUtil.SetComplete()








Catch
E As Exception



InventoryRecExists
= False



ContextUtil.SetAbort()



Finally



objCnn.Close()



End
Try








End
Function








Private
Sub UpdateInventoryRecord( _



ByVal
BinNumber As String, _



ByVal
SKU As String, _



ByVal
Quantity As Integer)








Dim
objCnn As SqlConnection



Dim
objCmd As SqlCommand



Dim
objParam As SqlParameter



Dim
intRowCount As Integer








Try



objCnn
= New SqlConnection()



objCnn.ConnectionString
= _



"Initial
Catalog=Supermarket;Data Source=localhost;uid=sa;pwd="



objCnn.Open()



objCmd
= objCnn.CreateCommand()








objCmd.CommandText
= "UPDATE Inventory " & _



"SET
Quantity = Quantity + @Quantity " & _



"WHERE
BinNumber = @BinNumber AND SKU = @SKU"








objParam
= New SqlParameter()



With
objParam



.ParameterName
= "@Quantity"



.SqlDbType
= SqlDbType.Int



.Direction
= ParameterDirection.Input



.Value
= Quantity



End
With



objCmd.Parameters.Add(objParam)








objParam
= New SqlParameter()



With
objParam



.ParameterName
= "@BinNumber"



.SqlDbType
= SqlDbType.VarChar



.Direction
= ParameterDirection.Input



.Value
= BinNumber



End
With



objCmd.Parameters.Add(objParam)








objParam
= New SqlParameter()



With
objParam



.ParameterName
= "@SKU"



.SqlDbType
= SqlDbType.VarChar



.Direction
= ParameterDirection.Input



.Value
= SKU



End
With



objCmd.Parameters.Add(objParam)








intRowCount
= objCmd.ExecuteNonQuery()



ContextUtil.SetComplete()








Catch
E As Exception



ContextUtil.SetAbort()



Finally



objCnn.Close()



End
Try



End
Sub








Private
Sub InsertInventoryRecord( _



ByVal
BinNumber As String, _



ByVal
SKU As String, _



ByVal
Quantity As Integer)








Dim
objCnn As SqlConnection



Dim
objCmd As SqlCommand



Dim
objParam As SqlParameter



Dim
intRowCount As Integer








Try



objCnn
= New SqlConnection()



objCnn.ConnectionString
= _



"Initial
Catalog=Supermarket;Data Source=localhost;uid=sa;pwd="



objCnn.Open()



objCmd
= objCnn.CreateCommand()








objCmd.CommandText
= _



"INSERT
INTO Inventory " & _



"(
BinNumber, SKU, Quantity ) VALUES " & _



"(
@BinNumber, @SKU, @Quantity )"








objParam
= New SqlParameter()



With
objParam



.ParameterName
= "@BinNumber"



.SqlDbType
= SqlDbType.VarChar



.Direction
= ParameterDirection.Input



.Value
= BinNumber



End
With



objCmd.Parameters.Add(objParam)








objParam
= New SqlParameter()



With
objParam



.ParameterName
= "@SKU"



.SqlDbType
= SqlDbType.VarChar



.Direction
= ParameterDirection.Input



.Value
= SKU



End
With



objCmd.Parameters.Add(objParam)








objParam
= New SqlParameter()



With
objParam



.ParameterName
= "@Quantity"



.SqlDbType
= SqlDbType.Int



.Direction
= ParameterDirection.Input



.Value
= Quantity



End
With



objCmd.Parameters.Add(objParam)








intRowCount
= objCmd.ExecuteNonQuery()



ContextUtil.SetComplete()








Catch
E As Exception



ContextUtil.SetAbort()



Finally



objCnn.Close()



End
Try



End
Sub








Public
Function Update(ByVal BinNumber As String, _



ByVal
SKU As String, _



ByVal
Quantity As Integer) _



As
Boolean _



Implements
IUpdateInventory.Update








Dim
objCnn As SqlConnection



Dim
objCmd As SqlCommand



Dim
objParam As SqlParameter



Dim
strStockingLocation As String



Dim
intRowsAffected As Integer








Try



If
InventoryRecExists(SKU, BinNumber) Then



UpdateInventoryRecord(
_



BinNumber,
_



SKU,
_



Quantity)



Else



InsertInventoryRecord(
_



BinNumber,
_



SKU,
_



Quantity)



End
If








Update
= True



ContextUtil.SetComplete()








Catch
E As Exception



Update
= False



ContextUtil.SetAbort()



End
Try








End
Function



End
Class



End
Namespace


Lab
Code Sample 5-4



Imports
System



Imports
System.Reflection



Imports
System.EnterpriseServices



Imports
System.Data



Imports
System.Data.SqlClient








Namespace
Supermarket








Public
Interface IReceiveOrder



Function
GetNextLineItem(ByVal OrderNumber As String, _



ByRef
SKU As String) As Integer



Function
Receive(ByVal OrderNumber As String, _



ByVal
SKU As String, _



ByVal
LineNumber As Integer, _



ByVal
QuantityReceived As Integer) _



As
Boolean



End
Interface








<ConstructionEnabled(
_



[Default]:="Default
Construction String"), _



Transaction(TransactionOption.Required)>
_



Public
Class ReceiveOrder



Inherits
ServicedComponent



Implements
IReceiveOrder








Public
Sub New()








End
Sub








Private
Sub UpdateOrderDeatils( _



ByVal
OrderNumber As String, _



ByVal
LineNumber As Integer, _



ByVal
QuantityReceived As Integer)








Dim
objCnn As SqlConnection



Dim
objCmd As SqlCommand



Dim
objParam As SqlParameter



Dim
objSQLDr As SqlDataReader



Dim
intRowsAffected As Integer








Try



objCnn
= New SqlConnection()



objCnn.ConnectionString
= _



"Initial
Catalog=Supermarket;Data Source=localhost;uid=sa;pwd="



objCnn.Open()



objCmd
= objCnn.CreateCommand()








objCmd.CommandText
= _



"UPDATE
OrderDetails " & _



"SET
QuantityReceived = " & _



"QuantityReceived
+ @QuantityReceived " & _



"WHERE
OrderNumber = " & _



"@OrderNumber
AND LineItemNumber = @LineNumber"








objParam
= New SqlParameter()



With
objParam



.ParameterName
= "@QuantityReceived"



.SqlDbType
= SqlDbType.Int



.Direction
= ParameterDirection.Input



.Value
= QuantityReceived



End
With



objCmd.Parameters.Add(objParam)








objParam
= New SqlParameter()



With
objParam



.ParameterName
= "@OrderNumber"



.SqlDbType
= SqlDbType.VarChar



.Direction
= ParameterDirection.Input



.Value
= OrderNumber



End
With



objCmd.Parameters.Add(objParam)








objParam
= New SqlParameter()



With
objParam



.ParameterName
= "@LineNumber"



.SqlDbType
= SqlDbType.Int



.Direction
= ParameterDirection.Input



.Value
= LineNumber



End
With



objCmd.Parameters.Add(objParam)








intRowsAffected
= objCmd.ExecuteNonQuery()








ContextUtil.SetComplete()








Catch
E As Exception



ContextUtil.SetAbort()



Finally



objCnn.Close()



End
Try








End
Sub








Public
Function GetNextLineItem( _



ByVal
OrderNumber As String, _



ByRef
SKU As String) _



As
Integer Implements _



IReceiveOrder.GetNextLineItem








Dim
objCnn As SqlConnection



Dim
objCmd As SqlCommand



Dim
objParam As SqlParameter



Dim
objSQLDr As SqlDataReader








Try








objCnn
= New SqlConnection()



objCnn.ConnectionString
= _



"Initial
Catalog=Supermarket;Data Source=localhost;uid=sa;pwd="



objCnn.Open()



objCmd
= objCnn.CreateCommand()








objCmd.CommandText
= _



"SELECT
MAX(LineItemNumber), " & _



"SKU
FROM OrderDetails od, Orders o " & _



"WHERE
od.OrderNumber = @OrderNumber AND " & _



"o.OrderReceived
= 0 AND " & _



"o.OrderNumber
= od.OrderNumber GROUP BY SKU"








objParam
= New SqlParameter()



With
objParam



.ParameterName
= "@OrderNumber"



.SqlDbType
= SqlDbType.VarChar



.Direction
= ParameterDirection.Input



.Value
= OrderNumber



End
With



objCmd.Parameters.Add(objParam)








objSQLDr
= objCmd.ExecuteReader()



objSQLDr.Read()








If
Not objSQLDr.IsDBNull(0) Then



GetNextLineItem
= objSQLDr.GetInt32(0)



SKU
= objSQLDr.GetString(1)



Else



GetNextLineItem
= -1



SKU
= ""



End
If








ContextUtil.SetComplete()



objSQLDr.Close()








Catch
E As Exception



GetNextLineItem
= -1



SKU
= ""



ContextUtil.SetAbort()



Finally



objCnn.Close()



End
Try








End
Function








Public
Function Receive(ByVal OrderNumber As String, _



ByVal
SKU As String, _



ByVal
LineNumber As Integer, _



ByVal
QuantityReceived As Integer) _



As
Boolean _



Implements
IReceiveOrder.Receive








Dim
objCnn As SqlConnection



Dim
objCmd As SqlCommand



Dim
objParam As SqlParameter



Dim
objInvUpdate As IUpdateInventory



Dim
strBinNumber As String








Try



UpdateOrderDeatils(OrderNumber,
_



LineNumber,
_



QuantityReceived)








objInvUpdate
= New UpdateInventory()



strBinNumber
= _




objInvUpdate.GetStockingLocation(SKU)



If
objInvUpdate.Update(strBinNumber, _



SKU,
QuantityReceived) Then



Receive
= True



ContextUtil.SetComplete()



Else



Receive
= False



ContextUtil.SetAbort()



End
If








Catch
E As Exception



Receive
= False



ContextUtil.SetAbort()



End
Try



End
Function








End
Class



End
Namespace




  1. Now
    that you've entered the main code for the Supermarket COM+
    application, it's time to compile it. Choose Build'Build Solution
    from the menu to create the assembly file. In the next step, you'll
    create a project that references that assembly.




STEP
4.
Create the ASP.NET application.




  1. Create
    a new VB ASP.NET project called "SupermarketWeb".


  2. Add
    a reference to System.EnterpriseServices to the project by
    right-clicking the project icon and selecting Add Reference... from
    the pop-up menu.


  3. Add
    four new Web Forms (.aspx files) and their code-behind files (.vb
    files) to the project, named as follows:



  4. Lcs5-5.aspx,
    Lcs5-5.aspx.vb



  5. Lcs5-6.aspx,
    Lcs5-6.aspx.vb



  6. Lcs5-7.aspx,
    Lcs5-7.aspx.vb




Lcs5-8.aspx,
Lcs5-8.aspx.vb



These
Web Forms will test the different components and their functionality.


Product
Component Test Web Form



The
HTML for the Lcs5-5.aspx file appears below.


Lab
Code Sample 5-5



<%@
Page Language="vb"



AutoEventWireup="false"



src="Lcs5-05.aspx.vb"



Inherits="WebForm1"



Transaction="RequiresNew"%>



<html>



<head>



<title>Create
New Product</title>



</head>



<body>








<form
id="Form1"



method="post"



runat="server">








<p>SKU:



<asp:TextBox



id=txtSKU



runat="server">



</asp:TextBox></p>








<p>Description:



<asp:TextBox



id=txtDescription



runat="server">



</asp:TextBox></p>








<p>Unit
Price:



<asp:TextBox



id=txtUnitPrice



runat="server">



</asp:TextBox></p>








<p>Stocking
Location:



<asp:TextBox



id=txtStockLoc



runat="server">



</asp:TextBox></p>








<p>



<asp:Button



id=cmdAddProduct



runat="server"



Text="Add
Product">



</asp:Button>








<asp:CompareValidator



id=CompareValidator1



runat="server"



ErrorMessage="You
must enter a price (number)"



Type="Double"



ControlToValidate="txtUnitPrice"



Operator="DataTypeCheck">



</asp:CompareValidator></p>








</form>



</body>



</html>



ASP.NET
Web Forms can run inside the context of a COM+ Component Services
transaction. In this ASP.NET Web application, the Web Forms call the
serviced components in response to events raised by Web Controls
(buttons clicked and so on). Since the ASP.NET Web Form is the
initiator of calls made into a COM+ application, the Web Form is
considered the root of the transaction. It has the "final vote"
as to whether or not the transaction succeeds or fails.



In
order to designate that the Web Form will participate in a
transaction, you need to set the Transaction page attribute
(highlighted in bold above). This code specifies the level as
RequiresNew. This means that the page will always begin a new
transaction for any units of work executed during the lifetime of the
page.



Here
is the code-behind file, Lcs5-5.aspx.vb, for the preceding Web Form.



Imports
SupermarketSystem.Supermarket



Imports
System.EnterpriseServices



Imports
System.Reflection



Imports
System.ComponentModel








Public
Class WebForm1



Inherits
System.Web.UI.Page



Protected
WithEvents cmdAddProduct As _



System.Web.UI.WebControls.Button



Protected
WithEvents txtSKU As _



System.Web.UI.WebControls.TextBox



Protected
WithEvents txtDescription As _



System.Web.UI.WebControls.TextBox



Protected
WithEvents txtUnitPrice As _



System.Web.UI.WebControls.TextBox



Protected
WithEvents txtStockLoc As _



System.Web.UI.WebControls.TextBox



Protected
WithEvents CompareValidator1 As_



System.Web.UI.WebControls.CompareValidator








Private
Sub Page_Load(ByVal sender As System.Object, _



ByVal
e As System.EventArgs) Handles MyBase.Load








End
Sub








Private
Sub cmdAddProduct_Click( _



ByVal
sender As System.Object, _



ByVal
e As System.EventArgs) _



Handles
cmdAddProduct.Click








Dim
objProduct As IProduct



Dim
bln As Boolean








objProduct
= New Product()



bln
= objProduct.Create(txtSKU.Text, _



txtDescription.Text,
_



CDec(txtUnitPrice.Text),
_



txtStockLoc.Text)








If
bln Then



ContextUtil.SetComplete()



Else



ContextUtil.SetAbort()



End
If








End
Sub



End
Class



The
Click event for the cmdAddProduct button calls the Product component
to add a new product to the database. The code creates a new Product
object and obtains a reference to the IProduct interface. It then
calls the Create() method. If the call was successful (returned
True), SetComplete() is called to indicate to COM+ that this unit of
work in the transaction was successful. If not, SetAbort()stops the
transaction immediately.


CreateOrder
Component Test Web Form



The
code for the Lcs5-6.aspx file follows below. Note that again the
Transaction page attribute is specified and set to RequiresNew.


Lab
Code Sample 5-6



<%@
Page Language="vb"



AutoEventWireup="false"



src="Lcs5-06.aspx.vb"



Inherits="SupermarketCreateOrder"



Transaction="RequiresNew"%>



<html>



<head>



<title>Create
New Order</title>



</head>



<body>








<form
id="Form1"



method="post"



runat="server">








<p>Order
number:



<asp:TextBox



id=txtOrderNumber



runat="server">



</asp:TextBox></p>








<p>Supplier
Name:



<asp:TextBox



id=txtSupplierName



runat="server">



</asp:TextBox></p>








<p>



<asp:Button



id=cmdCreateOrder



runat="server"



Text="Add">



</asp:Button></p>








</form>



</body>



</html>



The
code-behind file, Lcs5-6.aspx.vb, contains the following code.



Imports
System.EnterpriseServices



Imports
System.Reflection



Imports
SupermarketSystem.Supermarket








Public
Class SupermarketCreateOrder



Inherits
System.Web.UI.Page



Protected
WithEvents txtOrderNumber As _



System.Web.UI.WebControls.TextBox



Protected
WithEvents txtSupplierName As _



System.Web.UI.WebControls.TextBox



Protected
WithEvents cmdCreateOrder As _



System.Web.UI.WebControls.Button








Private
Sub Page_Load(ByVal sender As System.Object, _



ByVal
e As System.EventArgs) Handles MyBase.Load








End
Sub








Private
Sub cmdCreateOrder_Click( _



ByVal
sender As System.Object, _



ByVal
e As System.EventArgs) _



Handles
cmdCreateOrder.Click








Dim
objCreateOrder As ICreateOrder



objCreateOrder
= New CreateOrder()








If
objCreateOrder.Create(txtOrderNumber.Text, _




txtSupplierName.Text)
Then



ContextUtil.SetComplete()



Else



ContextUtil.SetAbort()



End
If



End
Sub



End
Class



Similar
to the test Web Form for the Product component, this code creates a
CreateOrder object using New. The program calls the Create() method
and then calls SetComplete() or SetAbort() upon success or failure,
respectively.


AddToOrder
Test Web Form



The
HTML code for the AddToOrder Web Form (Lcs5-7.aspx) appears below.


Lab
Code Sample 5-7



<%@
Page Language="vb"



AutoEventWireup="false"



Codebehind="SupermarketAddToOrder.aspx.vb"




Inherits="SupermarketWeb.SupermarketAddToOrder"



Transaction="RequiresNew"%>



<html>



<head>



<title>Add
To Order</title>



</head>



<body>








<form
id="Form1"



method="post"



runat="server">








<p>Order
Number:



<asp:TextBox



id=txtOrderNumber



runat="server">



</asp:TextBox></p>








<p>SKU:



<asp:TextBox



id=txtSKU



runat="server">



</asp:TextBox></p>








<p>Quantity:



<asp:TextBox



id=txtQuantity



runat="server">



</asp:TextBox></p>








<p>



<asp:CompareValidator



id=CompareValidator1



runat="server"



ErrorMessage="Quantity
must be a whole number!"



ControlToValidate="txtQuantity"



Type="Integer"



Operator="DataTypeCheck">



</asp:CompareValidator></p>








<p>



<asp:Button



id=cmdAddToOrder



runat="server"



Text="Add
To Order">



</asp:Button></p>








</form>



</body>



</html>



Here
is the associated code-behind file (Lcs5-7.aspx.vb). AddItems() is a
method of the CreateOrder component, so the code is very similar to
the CreateOrder Web Form.



Imports
System.EnterpriseServices



Imports
System.Reflection



Imports
SupermarketSystem.Supermarket








Public
Class SupermarketAddToOrder



Inherits
System.Web.UI.Page



Protected
WithEvents txtOrderNumber As _



System.Web.UI.WebControls.TextBox



Protected
WithEvents txtSKU As _



System.Web.UI.WebControls.TextBox



Protected
WithEvents txtQuantity As _



System.Web.UI.WebControls.TextBox



Protected
WithEvents CompareValidator1 As _




System.Web.UI.WebControls.CompareValidator



Protected
WithEvents cmdAddToOrder As _



System.Web.UI.WebControls.Button








Private
Sub Page_Load(ByVal sender As System.Object, _



ByVal
e As System.EventArgs) Handles MyBase.Load








End
Sub








Private
Sub cmdAddToOrder_Click( _



ByVal
sender As System.Object, _



ByVal
e As System.EventArgs) _



Handles
cmdAddToOrder.Click








Dim
objCreateOrder As ICreateOrder



objCreateOrder
= New CreateOrder()








If
objCreateOrder.AddItems(txtOrderNumber.Text, _



txtSKU.Text,
CInt(txtQuantity.Text)) Then








ContextUtil.SetComplete()



Else



ContextUtil.SetAbort()



End
If



End
Sub



End
Class


ReceiveOrder
Component Test Web Form



Finally,
here is the code (Lcs5-8.aspx) for the Web Form that will receive
items for an order.


Lab
Code Sample 5-8



<%@
Page Language="vb"



AutoEventWireup="false"



src="SupermarketReceiveOrder.aspx.vb"



Inherits="SupermarketReceiveOrder"



Transaction="RequiresNew"%>



<html>



<head>



<title>Receive
Order</title>



</head>



<body>








<form
id="Form1"



method="post"



runat="server">








<p>Order
Number to Receive:








<asp:TextBox



id=txtOrderToReceive



runat="server">



</asp:TextBox>








<asp:Button



id=cmdGetOrder



runat="server"



Text="Get
Order">



</asp:Button></p>








<p>



<asp:Panel



id=Panel1



runat="server"



Width="399px"



Height="144px"



Enabled="False">








<p>



<asp:Label



id=lblOrderNumber



runat="server"



Width="184px"



Height="19px">



</asp:Label></p>








<p></p><p>



<asp:Label



id=lblReceiveSKU



runat="server"



Width="183px"



Height="19px">



</asp:Label></p>








<p>



<asp:Label



id=lblLineNumberReceive



runat="server"



Width="188px"



Height="19px">



</asp:Label></p>








<p>



<asp:Label



id=Label1



runat="server"



Width="128px"



Height="19px">



Quantity
To Receive:



</asp:Label>








<asp:TextBox



id=txtQuantityToReceive



runat="server">



</asp:TextBox>








<asp:Button



id=cmdReceive



runat="server"



Text="Receive">



</asp:Button>








</asp:Panel></p>








</form>



</body>



</html>



This
Web Form wraps page elements in a Panel Web Control. The panel is
initially disabled to avoid displaying or enabling the order
information until a valid order number is keyed into the Web Form.
Here's the code-behind file (Lcs5-8.aspx.vb).



Imports
System.EnterpriseServices



Imports
System.Reflection



Imports
SupermarketSystem.Supermarket








Public
Class SupermarketReceiveOrder



Inherits
System.Web.UI.Page



Protected
WithEvents cmdGetOrder As _



System.Web.UI.WebControls.Button



Protected
WithEvents Panel1 As _



System.Web.UI.WebControls.Panel



Protected
WithEvents lblOrderNumber As _



System.Web.UI.WebControls.Label



Protected
WithEvents Label1 As _



System.Web.UI.WebControls.Label



Protected
WithEvents txtOrderToReceive As _



System.Web.UI.WebControls.TextBox



Protected
WithEvents cmdReceive As _



System.Web.UI.WebControls.Button



Protected
WithEvents lblReceiveSKU As _



System.Web.UI.WebControls.Label



Protected
WithEvents lblLineNumberReceive As _



System.Web.UI.WebControls.Label



Protected
WithEvents txtQuantityToReceive As _



System.Web.UI.WebControls.TextBox








Private
Sub Page_Load(ByVal sender As System.Object, _



ByVal
e As System.EventArgs) Handles MyBase.Load








End
Sub








Private
Sub cmdGetOrder_Click( _



ByVal
sender As System.Object, _



ByVal
e As System.EventArgs) _



Handles
cmdGetOrder.Click








Dim
objReceiveOrder As IReceiveOrder



Dim
intLineNumber As Integer



Dim
strSKUToReceive As String








objReceiveOrder
= New ReceiveOrder()








intLineNumber
= _



objReceiveOrder.GetNextLineItem(
_



txtOrderToReceive.Text,
_



strSKUToReceive)








If
intLineNumber <> -1 Then



ViewState("OrderToReceive")
= txtOrderToReceive.Text



ViewState("SKUToReceive")
= strSKUToReceive



ViewState("LineNumber")
= intLineNumber



Panel1.Enabled
= True



lblLineNumberReceive.Text
= _



"Line
Number: " & intLineNumber



lblOrderNumber.Text
= _



"Order
Number: " & txtOrderToReceive.Text



lblReceiveSKU.Text
= "SKU: " & strSKUToReceive



Else



Panel1.Enabled
= False



End
If



End
Sub








Private
Sub cmdReceive_Click( _



ByVal
sender As System.Object, _



ByVal
e As System.EventArgs) _



Handles
cmdReceive.Click








Dim
objReceiveOrder As IReceiveOrder



objReceiveOrder
= New ReceiveOrder()








If
objReceiveOrder.Receive( _



ViewState("OrderToReceive"),
_



ViewState("SKUToReceive"),
_



ViewState("LineNumber"),
_



CInt(txtQuantityToReceive.Text))
Then








ContextUtil.SetComplete()



Else



ContextUtil.SetAbort()



End
If



End
Sub



End
Class



This
form uses two Button Web Controls, cmdGetOrder and cmdReceive. This
makes a two-step process for receiving items for an order. First, the
event handler for cmdGetOrder calls GetNextLineItem(), taking as
input the order number the user entered. If there is a line item to
receive for the order, the program displays the line-item information
in the Label Web Control contained within the Panel Web Control. The
Panel Web Control is then enabled, making the information visible to
the user. The line-item information is also copied to the ViewState
StateBag because the program will need this information on the
subsequent post-back that will occur when items are received.



The
event handler for cmdReceive calls the Receive() method. The
Receive() method updates the OrderDetails table as well as the
Inventory table. Using a transaction in this situation helps point
out any discrepancies between quantities in inventory and quantities
ordered. The Receive() method returns True on success and False on
failure, and the program makes an appropriate call to either
SetComplete() or SetAbort() as a result.




  1. Now
    you need to add a reference to the assembly DLL for the components.
    Right-click the References folder in the Solution Explorer, select
    Add Reference, browse to the compiled DLL, and select it. Click OK
    to add the reference to the selected assembly.




STEP
5.
Run the application.




  1. Now
    you're ready to build the application. Select Build'Build Solution
    from the menu. Select a start page for the application (like
    Lcs5-5.aspx) by right-clicking on a Web Form in the Solution
    Explorer and selecting Set As Start Page from the menu.


  2. Run
    the application by selecting Debug'Start Without Debugging.


  3. Test
    the application by entering some products. Then create a new order,
    add some items to the order, and run an order-receive process. This
    should complete a full test.




Something
important happened when you first called a component's method inside
the assembly. The system performed what is known as a lazy
registration. A lazy registration automatically places the components
in the assembly into a new COM+ application in the COM+ Component
Services Console. It does this based on the assembly-level attributes
specified in the component assembly code.
Lab
Figure 5–4

shows the Supermarket COM+ application in the COM+ Component Services
Console.



Figure
5-4

The Supermarket application within the COM+ Component Services
Console



STEP
6.
Add role-based security.




  1. One
    of the requirements for the application is that only certain
    categories of users should be allowed to run certain components. To
    enable role-based security for the Supermarket COM+ application,
    right-click on the Supermarket COM+ application icon and select
    Properties. Click the Security tab. Check the boxes and radio
    buttons as shown in
    Lab
    Figure 5-5
    .




  1. Figure
    5-5

    Enabling role-based security for the Supermarket application


  2. Now
    you can set up the user roles (Receiving Clerks, Supervisors, and
    Suppliers) inside the COM+ Component Services Console. For each
    role, right-click the Roles folder under the Supermarket application
    (see Lab
    Figure
    5-6
    ),
    select New'Role from the menu, and assign a name for the role.






  1. Figure
    5-6

    Adding roles for the Supermarket application


  2. Assign
    users to each role by right-clicking the User folder under the role
    and selecting New'User from the menu. Pick a Windows account name(s)
    or group(s) to assign to the role.
    Lab
    Figure 5-7

    shows users assigned to the various roles of the Supermarket
    application.




  1. Now
    you need to assign each role to a component. Right-click a component
    in the COM+ application and select Properties from the menu. Click
    the Security tab. Check Enforce component level access checks, and
    then check the roles you wish to assign to the component, as shown
    in
    Lab
    Figure 5-8
    .




  1. Figure
    5-8

    Assigning roles to components




STEP
7.
Test the role-based security.




  1. A
    convenient way to test the role-based security is to call the
    components from a console application and run the console
    application in the security context of a specific user. Normally,
    when you run an application from the console, Windows uses the
    security context of the currently logged-on user. By using the runas
    command, you can run an executable program using any account name
    (provided, of course, you have the password of that account!).
    Here's a simple console application you can run to test role-based
    security.



  2. Imports
    System



  3. Imports
    SupermarketSystem.Supermarket



  4. Imports
    System.EnterpriseServices





  5. Module
    Module1





  6. Sub
    Main()



  7. Dim
    objCreateOrder As ICreateOrder





  8. objCreateOrder
    = New CreateOrder()





  9. objCreateOrder.Create("834957239-1",
    "My Supplier")





  10. End
    Sub






End
Module



Compile
this small testing console application and run it using the runas
command below:



runas
/user:rmdevbiz01\matt roletester.exe



Given
the role configuration of the CreateOrder component, the username
"matt" should be the only user allowed to run the
application. Assuming that the user account "matt" has
appropriate Windows permissions set to run the program, it should
execute without errors.




  1. You
    can also perform a negative test by rerunning the program with an
    account name not included in the Supplier role.



5.7 Summary



Let's
recap what you've learned in this chapter about writing .NET managed
components.




  • Managed
    code is application code that executes within the .NET Framework.


  • Runtime
    systems enable application programs you write to access operating
    system–level services. Several different runtime environments
    are available for various systems. A runtime environment is usually
    specific to a programming language.


  • Intermediate
    languages provide an additional layer of abstraction over another
    existing runtime environment. The goal of intermediate languages is
    to provide portability of application code across operating systems.
    The intermediate language for the .NET Framework is called the
    Microsoft Intermediate Language (MSIL).


  • The
    runtime environment for the .NET Framework is called the Common
    Language Runtime (CLR). It supports self-describing code through
    metadata, a specification for the Common Type System (CTS),
    just-in-time compilation of code, and the concept of assembly
    manifests, which describe a packaged set of components, programs,
    files, and other dependencies an application needs in order to
    function properly.



  • COM
    is a programming model invented by Microsoft that allows for
    binary-level software component reuse.


  • Transactions
    provide consistency and data integrity to a software system. By
    checking the success or failure of smaller atomic tasks that make up
    the transaction, you can decide whether the transaction should
    succeed as a whole.


  • Windows
    provides programmers with easy transaction support for their
    applications through COM+ Component Services. COM+ Component
    Services provides transaction support, object pooling, and
    component-level runtime security.


  • Components
    are simply classes at their most basic level. Components have
    properties and methods, which roughly correspond to member functions
    and member variables, respectively.



  • Serviced
    components are classes that take advantage of COM+ Component
    Services. Security is provided by grouping users into roles and
    assigning those roles to components.














1


http://www.informit.com/articles/article.asp?p=29267&seqNum=7