VitoPlantamura.com logo - Click here to go to the site home page... Click here to go to the site home page... Click here to go to the Blog page... Click here to go to the archives page... Click here to go to the about page...
"extremely impressive work"
- Mark Russinovich, www.sysinternals.com
(referring to my BugChecker)
my face...
Homepage of Vito Plantamura (@, LinkedIn), Windows Researcher and Developer. [user=Guest] - updated: August 08, 2007
 COM+ .NET CRM
COM+ .NET CRM

INTRODUCTION

In early 2003, I worked on a large COM+/Oracle based application written for the .NET framework (mainly) in the C# language. It was a pretty huge ASP.NET multi-level application designed to be deployed in a web farm scenario using the Network Load Balancing services of Windows 2000 Advanced Server among the various layers of application levels. I worked on a very large portion of this application. The final client of this software was an italian public institution.

Usually my clients call me whenever there is a very hard-to-write piece of software to deliver: in this case one of the main issues that had to be faced was the access to a FileNET Content Services document repository from the business code of this ASP.NET application in a consistent and robust manner (specially in very high-load situations with thousands of clients connected while adding, deleting and modifying documents). Briefly, the problem here is that the FileNET COM data access objects available at that time were not explicitly designed to be consumed in ASP.NET (and .NET in general) COM+-aware implementations.

Another limitation was the threading requirements of those objects calling thread (such a thread had to be STA initialized immediately after its creation -for example setting the ApartmentState property on the Thread object itself- in order to issue successful calls to those legacy components): in the specific case, this is a minor problem when accessing COM STA legacy objects directly from an ASP.NET "code-behind" implementation, where the AspCompat switch was kindly made available by the Microsoft framework developers just for addressing this specific situation. However, as you may guess, a multi-level well designed ASP.NET application does not call data access code directly from the presentation level: these calls tipically happen later, at the business layer of objects (specifically in components that -obviously- have to be accessed through distributed transaction compatible transports and that may be deployed even in different servers than the web ones). As my performance and scalability preliminary tests suggested, I resolved this specific problem handling the requests to the FileNET objects from an high-performance and application managed pool of STA threads, satisfying the compatibility requirements explained earlier (however, who knows FileNET and its working knows that there are other additional restrictions when calling its proprietary API that have influenced my architectural choices: these restrictions may have even system-wide impact as in the case, for example, of the type of logon selected when contacting a FileNET server from the application code and/or from the Panagon tools).

Besides this issue, another problem was the requirement to make the FileNET components calling code partecipate in the current distributed transaction in order to manage the requests for adding and modifying FileNET documents exactly (from the application perspective) as if those calls were issued to any other DTC compatible resource manager (such as Oracle, in the specific case). This means (in the real-world situation) that commit and rollback support had to be added in some way to the FileNET data access code in order to make it comparable to Oracle from the DTC point of view. My specific FileNET solution (with regard to COM+) had to vote for the final distributed transaction completion or abortion and expects to handle only the rollback event in order to compensate any document action that was associated to an aborted transaction (for example, if a document was added during the transaction processing, that document had to be deleted in the case that that specific transaction was aborted in the end).

The standard way to achieve this is to write a Compensating Resource Manager component: I knew about the requirements and complex structure of a "raw" CRM component just before the .NET era: I have written this short article because I am/was amazed at the simplicity of achieving this with the .NET framework and the FCL: I am essentially a kernel and low-level programmer but I just love .NET and the framework services just for their rationality. So I want to provide here a list of steps to follow in order to write a standard CRM component with Microsoft .NET hoping that it could come in handy to someone facing the same problem of mine with FileNET (however I noticed that there is a general lack of information in internet about this topic...).

CREATING A SIMPLE CRM

As in the case of most Enterprise Services objects, the structure of a .NET CRM reflects the structure of the raw (underlaying) non-.NET code that is called underneath. This is the list of steps to follow in order to create a simple commit/abort CRM component:

     Create a Class Library with Visual Studio.
     Add the following code in the AssemblyInfo.cs file - it is required to declare the use of the CRM features in the implemented COM+ application, that this is a server application (as requirement of a CRM implementation) and for specifying its name as registered in the catalog and its security configuration:

[ assembly: ApplicationActivation(ActivationOption.Server) ]
[ assembly: ApplicationCrmEnabled ]
[ assembly: ApplicationAccessControl( Value = false ) ]
[ assembly: ApplicationName( "Compensating Facades" ) ]

Don't forget to include the following namespaces:

using System.EnterpriseServices;
using System.EnterpriseServices.CompensatingResourceManager;
     Create a New Class derived from the System.EnterpriseServices.CompensatingResourceManager.Compensator class.
     Define a STATIC method with a name like "CreateClerk" with the following code - it will write the log record containing the informations about a compensable operation to the disk:

public static void CreateClerk( CommitFnDelegate commitCallback, AbortFnDelegate abortCallback, string opName, params object[] opParams )
{
  // create the clerk.
 
  UserData    userData = new UserData( opName, opParams, commitCallback, abortCallback );
  Clerk       clerk = new Clerk ( typeof( XxxCRMCompensator ), "XxxCRMCompensator", CompensatorOptions.AllPhases );
 
  clerk.WriteLogRecord( userData );
  clerk.ForceLog();
}

Where "XxxCRMCompensator" is the name of the Compensator-derived class. The CommitFnDelegate and AbortFnDelegate types are two delegates that are called whenever a transacted operation must be committed or rollbacked, respectively. Their prototypes could be something like the following definitions:

public delegate bool CommitFnDelegate( UserData userData );
public delegate bool AbortFnDelegate( UserData userData );

The UserData type (that is passed as parameter to each delegate and that is instanced in the CreateClerk function) should be a serializable class that will be used to hold the details of the logged compensable operation, for later reference in the case of commit or rollback needs, for example.

[ Serializable() ]
public class UserData : ISerializable
{
  /// <summary>
  /// Object State.
  /// </summary>
 
  protected string            _name = null;
  protected object[]          _params = null;
  protected CommitFnDelegate  _commitCallback = null;
  protected AbortFnDelegate   _abortCallback = null;
 
  /// <summary>
  /// Class Constructor.
  /// </summary>
 
  public UserData( string name, object[] parameters, CommitFnDelegate commitCallback, AbortFnDelegate abortCallback )
  {
    _name = name;
    _params = parameters;
    _commitCallback = commitCallback;
    _abortCallback = abortCallback;
  }
 
  /// <summary>
  /// Serialization.
  /// </summary>
 
  public UserData( SerializationInfo info, StreamingContext context )
  {
      // omitted for brevity.
  }
 
  void ISerializable.GetObjectData( SerializationInfo info, StreamingContext context)
  {
      // omitted for brevity.
  }
}

One interesting thing is how the delegate instances are deserialized: simply I lookup for the correct delegate type referencing the Method.ToString() name that is returned by the delegate itself. The whole operation is accomplished with Reflection in a cached manner.
     Override two functions in the Compensator specialization in order to handle the commit and rollback events - as you may guess, the Compensator type overrides are tailored to the two-phase prepare/commit logic implemented by the DTC in order to manage the commit and rollback actions in a distributed resource manager environment. We decide not to handle the "prepare" events (BeginPrepare, EndPrepare, PrepareRecord) and to handle only the Abort and Commit events, that are called by the recovery engine passing the LogRecord as parameter when the associated transaction is definitely aborted or committed, respectively.

public override bool CommitRecord ( LogRecord rec )
{
  // call the specified callback.
 
  UserData   userData = (UserData) rec.Record;
 
  if ( userData.CommitCallback == null )
    return true;
  else
    return (bool) InvokeHandlerMethod( userData.CommitCallback, userData );
}
 
public override bool AbortRecord ( LogRecord rec )
{
  // call the specified callback.
 
  UserData   userData = (UserData) rec.Record;
 
  if ( userData.AbortCallback == null )
    return true;
  else
    return (bool) InvokeHandlerMethod( userData.AbortCallback, userData );
}
     Finally you can use the CRM Compensator features inside a Serviced Component implementation:

[ Transaction( TransactionOption.Required ) ]
public class CompensatingType : ServicedComponent
{
      [ AutoComplete() ]
      public void TestMethod ()
      {
            try
            {
                  //
                  // DO THE JOB OF "TestMethod".
                  //
 
                  // then, create the record in the log.
 
                  XxxCRMCompensator.CreateClerk(
                        new CommitFnDelegate( TestMethod_Commit ),
                        new AbortFnDelegate( TestMethod_Abort ),
                        null,
                        ... parameters describing the nature and details of the compensable op ... );
 
                  if ( ContextUtil.IsInTransaction )
                  {
                        // force this, regardless of the presence of AutoComplete,
                        // that in several situations may be ignored.
                        ContextUtil.SetComplete();
                  }
 
                  // return.
 
                  return;
            }
            catch( Exception ex )
            {
                  if ( ContextUtil.IsInTransaction )
                  {
                        // force this, regardless of the presence of AutoComplete,
                        // that in several situations may be ignored.
                        ContextUtil.SetAbort();
                  }
 
                  throw ex;
      }
}

 Quotes
"Among the Windows experts I know personally, no one can beat Vito Plantamura."
- Francesco Balena, Code Architects SRL

"Your NDIS Monitor application, is amongst the most impressive networking code I have seen on the .Net framework."
- Ben Hakim.
 Photos
Various images from italian conferences and events (keep the mouse on a thumbnail for a short description):
Me at the Microsoft/HP/Intel organized Route64 event in Milan in May 2005, explaining how COM+ behaves on 64-bit Microsoft operating systems. I was there with the friends of Code Architects.
Me at the Microsoft Security Roadshow event in Bari in April 2006, explaining how the logon process works in Windows NT. There were 250 attendees.
Microsoft Security Roadshow 2006 in Treviso. This is an image of the huge 700-seats conference room.
Me at the Microsoft Security Roadshow 2006 in Treviso. This is a moment of the 3-hours session.
 Site login
NOTE: Actually the login feature is used only for administrative and content management purposes.
Username

Password

Everything here (code, binaries, text, graphics, design, html) is © 2010 Vito Plantamura and VPC Technologies SRL (VATID: IT06203700965).
If you download something (compilable or not) from the site, you should read the license policy file.
If you want to contact me via email, write at this address.