COM+ Transactions

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)]

Voting on the Transaction

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.

The AutoComplete Attribute

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.

The TransactionContext Object

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.

COM+ Transactions and Nonserviced Components

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 and transactions

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.

ASP.NET and transactions

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset