This post is about how you can take advantage of the underlying transaction created by the Endpoint Manager on receive ports. This is pretty useful when you only want to commit certain changes in your receive custom pipeline component if the entire pipeline is successful. One obvious application for this is if you want to track the message details to a custom tracking database but only if the entire pipeline executes successfully and does suspend the message.
An example of this is in the AS2 Decoder pipeline component shipped as part of BizTalk’s AS2 and EDI offering. The Party configuration allows you to store the encoded or decoded messages in the ‘non-repudiation database’. For example, to store the encoded and decoded versions of inbound AS2 messages for a specific party you can set the following properties:
The AS2 Decoder uses the underlying transaction created by the Endpoint Manager while it writes the message details to the BizTalk tracking database. If other pipeline components in the pipeline fail, like an EDI Disassembler, it will rollback the entry to the database.The non-repudiation database in question are some purposely created new tables in the BizTalk tracking database (EdiMessageContent and EdiMessagePartContent).
The trick is to get to the transaction already created by BizTalk by calling the the IPipelineContext.GetTransaction() method of your pipeline context. This will give you the MSDTC transaction for the instance of the receive port. Send ports do not expose a transaction so if you try getting the pipeline context’s transaction on a send port pipeline component you will get an exception that says ‘Transaction is not supported in a send pipeline or when a pipeline is invoked from an orchestration’.
So let’s have a look at this first example scenario. For some crazy reason you decide to write to a SQL Server table the message id and the message context of messages going through a receive pipeline at the Decode stage, but only if the entire receive pipeline works. If anywhere else in the pipeline fails you need to ensure the row on the table gets rolled back. Here’s the code that gets the transaction scope out of the pipeline context:
{
if (!Enabled)
return pInMsg;
var transactionNative = (IDtcTransaction)((IPipelineContextEx)pContext).GetTransaction();
using (_mTransactionScope = new TransactionScope(TransactionInterop.GetTransactionFromDtcTransaction(transactionNative)))
{
if (((pContext == null) || (pInMsg == null)) || (pInMsg.BodyPart == null))
throw new ArgumentNullException("pInMsg");
try
{
InsertMessageContext(pInMsg.MessageID, pInMsg.Context);
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine("[InboundTransaction.Blog.PipelineComponents.StoreMessageInfo.Execute] Exception thrown. Full exception: " + ex.ToString());
throw;
}
this._mTransactionScope.Complete();
}
return pInMsg;
}
The code inside the ‘InsertMessageContext’ only calls a stored procedure:
{
var connection = new SqlConnection(_connString);
var storeMessageInfoCmd = new SqlCommand("sp_StoreMessageInfo", connection)
{CommandType = CommandType.StoredProcedure};
SqlParameter param = storeMessageInfoCmd.Parameters.Add("@MessageId", SqlDbType.UniqueIdentifier);
param.Value = msgId;
param = storeMessageInfoCmd.Parameters.Add("@MessageContext", SqlDbType.NVarChar);
param.Value = GetMessageContextString(msgContext);
connection.Open();
try
{
storeMessageInfoCmd.ExecuteScalar();
}
finally
{
if (connection.State != ConnectionState.Open)
connection.Close();
}
connection.Close();
}
All the stored procedure does is insert into a ‘MessageInfo’ table:
@MessageId uniqueidentifier,
@MessageContext nvarchar(max)
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO [dbo].[MessageInfo]
([MessageId]
,[MessageInfo]
,[Created])
VALUES
(@MessageId
,@MessageContext
,GETDATE())
END
I created a receive pipeline with my custom component in the decode stage and a flat file disassembler in the disassemble stage:
What happens if I put two files that get parsed correctly by the flat file disassembler? We get two entries in our table:
If I put a file that fails flat file assembly I get the usual exception in the event log saying “There was a failure executing the receive pipeline …”, and no new row in the table.
If I put another file that passes flat file assembly I get a new row in the table, and this time the auto identity field was skipped by one (id = 4) further validating that the transaction was rolled back:
On a send pipeline component you can ensure the code in your pipeline component is run in a new scope by using a “using (TransactionScoe scope = new TransactionScope()) { you code })” around your code but this will not follow any underlying transaction scopes.
Another good post on this subject is this by Frederic on using the new File System Transaction (TxF) on receive pipeline components.
For some other interesting recent pipeline component posts Yossi has released some great guidance on streaming pipeline components and Richard Hallgren explains how to properly use the ResourceTracker to avoid the infamous “Cannot access a disposed object” exception.
Download the source code
Regards,
Thiago Almeida
cool, don’t know if i am going to use it right away, but it could come in handy some day
Your blog is an amazing source of knowledge !
Thank you for sharing and referring me.
Kind Regards
Great blog.
Just curious why this is not supported in the send pipeline?
If this is not supported, how could the MessagingEventStream work in a send pipeline?
Hi Roger, thanks. For some reason they haven’t exposed the underlying transaction from a public method so even though it might use one underneath we don’t seem to be able to access it.