You
can configure your serviced component to
use the five available COM+ transaction support options by using the
Transaction
attribute. The
Transaction
attribute’s
constructor
accepts
an
enum
parameter
of type
TransactionOption
, defined as:
public enum TransactionOption { Disabled, NotSupported, Supported, Required, RequiresNew }
For example, to configure your serviced component to require a
transaction, use the TransactionOption.Required
value:
[Transaction(TransactionOption.Required)] public class MyComponent :ServicedComponent {...}
The five enum values of TransactionOption
map to
the five COM+ transaction support options discussed in Chapter 4.
When you use the Transaction
attribute to mark
your serviced component to use transactions, you implicitly set it to
use JITA and require activity-based synchronization as well.
The Transaction
attribute has an overloaded
default constructor, which sets the transaction support to
TransactionOption.Required
. As a result, the
following two statements are equivalent:
[Transaction] [Transaction(TransactionOption.Required)]
Not
surprisingly,
you
use the ContextUtil
class to vote on the
transaction’s outcome. ContextUtil
has a
static property of the enum type
TransactionVote
called MyTransactionVote
.
TransactionVote
is defined
as:
public enum TransactionVote {Abort,Commit}
Example 10-5 shows a transactional serviced component
voting on its transaction outcome using
ContextUtil
. Note that the component still has to
do all the right things that a well-designed transactional component
has to do (see Chapter 4); it needs to retrieve
its state from a resource manager at the beginning of the call and
save it at the end. It must also deactivate itself at the end of the
method to purge its state and make the vote take effect.
Example 10-5. A transactional serviced component voting on its transaction outcome using the ContextUtil MyTransactionVote property
public interface IMyInterface { void MyMethod(long objectIdentifier); } [Transaction] public class MyComponent :ServicedComponent,IMyInterface { public void MyMethod(long objectIdentifier) { try { GetState(objectIdentifier); DoWork( ); SaveState(objectIdentifier); ContextUtil.MyTransactionVote
= TransactionVote.Commit; } catch { ContextUtil.MyTransactionVote
= TransactionVote.Abort; } //Let COM+ deactivate the object once the method returns finally { ContextUtil.DeactivateOnReturn
= true; } } //helper methods protected void GetState(long objectIdentifier){...} protected void DoWork( ){...} protected void SaveState(long objectIdentifier){...} }
Compare Example 10-5 to Example 4-3.
A COM+ configured component uses the returned
HRESULT
from the DoWork( )
helper method to decide on the transaction’s
outcome. A serviced component, like any other managed component, does
not use HRESULT
return codes for
error
handling; it uses
exceptions instead. In Example 10-5 the component
catches any exception that was thrown in the try
block by the DoWork( )
method and votes to abort
in the catch
block.
Alternatively, if you do not want to write exception-handling code,
you can use the programming model shown in Example 10-6. Set the context object’s
consistency bit to
false
(vote to abort) as the first thing the
method does. Then set it back to true
as the last
thing the method does (vote to commit). Any exception thrown in
between causes the method exception to end without voting to commit.
Example 10-6. Voting on the transaction without exception handling
public interface IMyInterface { void MyMethod(long objectIdentifier); } [Transaction] public class MyComponent :ServicedComponent,IMyInterface { public void MyMethod(long objectIdentifier) { //Let COM+ deactivate the object once the method returns and abort the //transaction. You can use ContextUtil.SetAbort( ) as well ContextUtil.DeactivateOnReturn = true;ContextUtil.MyTransactionVote = TransactionVote.Abort;
GetState(objectIdentifier); DoWork( ); SaveState(objectIdentifier);ContextUtil.MyTransactionVote = TransactionVote.Commit;
} //helper methods protected void GetState(long objectIdentifier){...} protected void DoWork( ){...} protected void SaveState(long objectIdentifier){...} }
Example 10-6 has another advantage over Example 10-5: having the exception propagated up the call chain once the transaction is aborted. By propagating it, callers up the chain know that they can also abort their work and avoid wasting more time on a doomed transaction.
Your serviced components can take advantage of COM+
method
auto-deactivation using the
AutoComplete
method attribute. During the
registration process, the method is configured to use COM+
auto-deactivation when AutoComplete
is used on a
method, and the checkbox “Automatically deactivate this object
when the method returns” on the method’s General tab is
selected. Serviced components that use the
AutoComplete
attribute do not need to vote
explicitly on their transaction outcome. Example 10-7
shows a transactional serviced component using the
AutoComplete
method attribute.
Example 10-7. Using the AutoComplete method attribute
public interface IMyInterface
{
void MyMethod(long objectIdentifier);
}
[Transaction]
public class MyComponent : ServicedComponent,IMyInterface
{
[AutoComplete
(true)]
public void MyMethod(long objectIdentifier)
{
GetState(objectIdentifier);
DoWork( );
SaveState(objectIdentifier);
}
//helper methods
protected void GetState(long objectIdentifier){...}
protected void DoWork( ){...}
protected void SaveState(long objectIdentifier){...}
}
When
you configure the method to use
auto-deactivation, the object’s interceptor sets the done and
consistency bits of the context object to true
if
the method did not throw an exception and the consistency bit to
false
if it did. As a result, the transaction is
committed if no exception is thrown and aborted otherwise.
Nontransactional JITA objects can also use the
AutoComplete
attribute to deactivate themselves
automatically on method return.
The AutoComplete
attribute has an overloaded
default constructor that uses true
for the
attribute construction. Consequently, the following two statements
are equivalent:
[AutoComplete] [AutoComplete(true)]
The AutoComplete
attribute can be applied on a
method as part of an interface definition:
public interface IMyInterface { //Avoid this: [AutoComplete] void MyMethod(long objectIdentifier); }
However, you should avoid using the attribute this way. An interface and its methods declarations serve as a contract between a client and an object; using auto completion of methods is purely an implementation decision. For example, one implementation of the interface on one component may chose to use autocomplete and another implementation on another component may choose not to.
A nontransactional
managed client creating a few transactional
objects faces a problem discussed in Chapter 4
(see Section 4.9). Essentially,
if the client wants to scope all its interactions with the objects it
creates under one transaction, it must use a middleman to create the
objects for it. Otherwise, each object created will be in its own
separate transaction. COM+ provides a ready-made middleman called
TransactionContext
. Managed clients can use
TransactionContext
as well. To use the
TransactionContext
object, add to the project
references the COM+ services type library. The
TransactionContext
class is in the
COMSVCSLib
namespace.
The TransactionContext
class is especially useful
in situations in which the class is a managed .NET component that
derives from a class other than ServicedComponent
.
Remember that a .NET component can only derive from one concrete
class and since the class already derives from a concrete class other
than ServicedComponent
, it cannot use the
Transaction
attribute. Nevertheless, the
TransactionContext
class gives this client an
ability to initiate and manage a transaction.
Example 10-8 demonstrates usage of the
TransactionContext
class, using the same use-case
as Example 4-6.
Example 10-8. A nontransactional managed client using the TransactionContext helper class to create other transactional objects
using COMSVCSLib
;
IMyInterface obj1,obj2,obj3;
ITransactionContext transContext;
transContext = (ITransactionContext) new TransactionContext( );
obj1 = (IMyInterface)transContext.CreateInstance("MyNamespace.MyComponent");
obj2 = (IMyInterface)transContext.CreateInstance("MyNamespace.MyComponent");
obj3 = (IMyInterface)transContext.CreateInstance("MyNamespace.MyComponent");
try
{
obj1.MyMethod( );
obj2.MyMethod( );
obj3.MyMethod( );
transContext.Commit( );
}
catch//Any error - abort the transaction
{
transContext.Abort( );
}
Note that the client in Example 10-8 decides whether to abort or commit the transaction depending on whether an exception is thrown by the internal objects.
Though this chapter focuses on serviced components, it is worth noting that COM+ transactions are used by other parts of the .NET framework besides serviced components—in particular, ASP.NET and Web Services.
Web
services are the most exciting piece of
technology in the entire .NET framework. Web
services allow a middle-tier component in one web site to
invoke methods on another middle-tier component at another web site,
with the same ease as if that component were in its own assembly. The
underlying technology facilitating web services serializes the calls
into text format and transports the call from the client to the web
service provider using HTTP. Because web service calls are text
based, they can be made across firewalls. Web services typically use
a protocol called
Simple Object Access Protocol
(SOAP) to represent the call, although other text-based protocols
such as HTTP-POST and HTTP-GET can also be
used. .NET successfully hides the required details from the client
and the server developer; a web service developer only needs to use
the WebMethod
attribute on the public methods
exposed as web services. Example 10-9 shows the
MyWebService
web service that provides the
MyMessage
web service—it returns the string
“Hello” to the caller.
Example 10-9. A trivial web service that returns the string “Hello”
using System.Web.Services; public class MyWebService : WebService { public MyWebService( ){} [WebMethod] public string MyMessage( ) { return "Hello"; } }
The web service class can optionally derive from the
WebService
base class, defined in the
System.Web.Services
namespace (see Example 10-9). The WebService
base class
provides you with easy access to common ASP.NET objects, such as
those representing application and session states. Your web service
probably accesses resource managers and transactional components. The
problem with adding transaction support to a web service that derived
from WebService
is that it is not derived from
ServicedComponent
, and .NET does not allow
multiple inheritance of implementation.
To overcome this hurdle, the WebMethod
attribute
has a public property called
TransactionOption
, of the enum
type Enterprise.Services.TransactionOption
discussed previously.
The default constructor of the WebMethod
attribute
sets this property to
TransactionOption.Disabled
, so the following two
statements are equivalent:
[WebMethod] [WebMethod(TransactionOption = TransactionOption.Disabled)]
If your web service requires a transaction, it can only be the root
of a transaction, due to the stateless nature of the HTTP protocol.
Even if you configure your web method to only require a transaction
and it is called from within the context of an existing transaction,
a new transaction is created for it. Similarly, the value of
TransactionOption.Supported
does not cause a web
service to join an existing transaction (if called from within one).
Consequently, the following statements are equivalent—all four amount to no transaction support for the web service:
[WebMethod] [WebMethod(TransactionOption = TransactionOption.Disabled)] [WebMethod(TransactionOption = TransactionOption.NotSupported)] [WebMethod(TransactionOption = TransactionOption.Supported)]
Moreover, the following statements are also equivalent—creating a new transaction for the web service:
[WebMethod(TransactionOption = TransactionOption.Required)] [WebMethod(TransactionOption = TransactionOption.RequiresNew)]
The various values of TransactionOption
are
confusing. To avoid making them the source of errors and
misunderstandings, use
TransactionOption.RequiresNew
when you want
transaction support for your web method; use
TransactionOption.Disabled
when you want to
explicitly demonstrate to a reader of your code that the web service
does not take part in a transaction. The question is, why did
Microsoft provide four overlapping transaction modes for web
services? I believe that it is not the result of carelessness, but
rather a conscious design decision. Microsoft is probably laying down
the foundation in .NET for a point in the future when it will be
possible to propagate transactions across web sites.
Finally, you do not need to explicitly vote on a transaction from within a
web service. If an exception occurs within a web service method, the
transaction is automatically aborted. Conversely, if no exceptions
occur, the transaction is committed automatically (as if you used the
AutoComplete
attribute). Of course, the web
service can still use
ContextUtil
to vote explicitly to abort
instead of throwing an exception, or when no exception occurred and
you still want to
abort.
An
ASP.NET
web form may access resource managers (such
as databases) directly, and it should do so under the protection of a
transaction. The page may also want to create a few transactional
components and compose their work into a single transaction. The
problem again is that a web form derives from the
System.Web.UI.
Page
base class, not
from ServicedComponent
, and therefore cannot use
the [Transaction]
attribute.
To provide transaction support for a web form, the Page base class
has a write-only property called
TransactionMode
of type
TransactionOption
. You can assign a value of type
TransactionOption
to
TransactionMode
, to configure transaction support
for your web form. You can assign TransactionMode
programmatically in your form contractor, or declaratively by setting
that property in the visual designer. The designer uses the
Transaction page directive to insert a directive
in the aspx form file. For example, if you set
the property using the designer to RequiresNew
,
the designer added this line to the beginning of the
aspx file:
<@% Page Transaction="RequiresNew" %>
Be aware that programmatic setting will override any designer setting. The default is no transaction support (disabled).
The form can even vote on the outcome of the transaction
(based on its interaction with the components it created) by using
the ContextUtil
methods. Finally, the form can
subscribe to events notifying it when a transaction is initiated and
when a transaction is aborted.