Tuesday, April 19, 2011

Processing Empty Files in Biztalk

In my current integration project, there is a need to process empty files (0 byte) in Biztalk. Default behavior of FILE adapter is to delete an empty file and raise an event. This empty file is then deleted even before it reaches pipeline on way to Biztalk MsgBox. So there is no way to do a routing based on filename either in a pure message scenario or using an orchestration.

One possible solution is to extend the File Adapter that is part of SDK (Image 1.1). Add the 3 projects in a Visual Studio Solution as shown in Image 1.2

Image1.1



Image 1.2



Key steps:

1. Build the solution, GAC Microsoft.Samples.BizTalk.Adapter.Common.dll.

2. Registry Key.

Use the StaticAdapterManagement.reg registry key file given in the sample and update the locations of the dlls mentioned. Microsoft recommends adding additional string for 64 bit machines. I didn’t do and it worked.

3. Go to Biztalk admin console and try to add the new EMPTY FILE adapter. It is possible that you may get an error similar too :
The system cannot find the file specified.

/C:\Program Files (x86)\Microsoft BizTalk Server 2009\SDK\Samples\CustomAdaptersDevelopment\File Adapter\Runtime\Microsoft.BizTalk.SDKSamples.Adapters.DotNetFile.Runtime.dll

This error threw me off as my registry key file did not contain this path at all and it left me wondering why is Biztalk even looking for this path. I did a search for this file in the entire registry and I found couple of references. I had no idea what were they used for but I knew that Biztalk was somehow reading them from registry key. I renamed the key for these entries without deleting . I was able to add the adapter to Biztalk successfully.

4. Once adapter was added, I assigned it to appropriate Biztalk host so I can use it in send/receive port. While setting the receive handler for this newly added adapter, I got the following error:
The Messaging Engine encountered an error when intializing the receive adapter "EmptyFile", HRESULT:"Property /Config/pollingInterval not found on adapter configuration XML.".

I decided to debug the code to find the point of error. I found the error in method : ExtractPollingInterval in file ConfigProperties.cs under project Microsoft.Samples.BizTalk.Adapter.Common.csproj

Update the following code


long pollingInterval = ExtractInt(document, "/Config/pollingInterval");
string pollingUnitOfMeasureStr = Extract(document,"/Config/pollingUnitOfMeasure", "Seconds");


To


long pollingInterval = Convert.ToInt32(IfExistsExtract(document, "/Config/pollingInterval","5"));
string pollingUnitOfMeasureStr = IfExistsExtract(document, "/Config/pollingUnitOfMeasure", "Seconds");

Or update the root name of ReceiveHandler schema to Config. I did the code change and not the schema change


I was in a position to configure this new custom FILE adapter and receive an empty file. In order to get a handle on the empty file, I need to go to method PickUpFilesAndSubmit which is part of DotNetFileReceiverEndpoint.cs under DotNetFile.csproj The line of code that got me the handle to the incoming file is if (item.Length == 0).
Once I got the handle, I was able to add my logic to deal with the incoming empty file. In my case, I decide to add a string “EmptyFile” if the incoming file was empty. I would then detect this file in the send adapter using the same custom adapter and assign 0 bytes to it. In between, I will have my orchestration reading the file name of this empty file (added string “EmptyFile”) and route it to appropriate directory folder.

This solution requires tampering with contents of an emptry file. It may be to acceptable in some cases. In my case, it was acceptable.

Send Side Changes:


To intercept the EmptyFile (file with “EmptyFile” string) on the send side, I used the method: ProcessMessage in file DotNetFileTransmitterEndpoint.cs under project DotNetFile.csproj.
I inserted the following snippet after cloning the stream. This writes an empty file.
StreamReader sr = new StreamReader(Stream);
string fileContents = sr.ReadLine();
if (fileContents.Contains("EmptyFile"))
{
fileStream.Write(new byte[0],0,0);
return null;
}

Dynamic Send Port:

In order to get this adapter to work with Dynamic Send port following changes will be needed:

1. Use DotNetFILE:// (same as the entry in registry) instead of FILE:// in orchestration for setting dynamic address




2. In the adapter use the following code to extract destination string from the context properties:

filePath = message.Context.Read("OutboundTransportLocation", "http://schemas.microsoft.com/BizTalk/2003/system-properties").ToString();

Use this destination string to parse the final destination of the file and add code to write the file to that location.