(Update: The original download files were missing the
PDB-to-GAC functionality I've discussed before. Please download
again if you have already...)
The
BizTalk Deployment Framework has been updated to work with BizTalk
2006...It is hard to believe that this project has been going on since May of
2004!
The Deployment Framework for BizTalk 2006 still has the same goals as
the 2004 version:
-
Streamline the deployment process for developers, ensuring repeatability
-
Make it easier for a team of BizTalk developers to stay in sync - not just with
BizTalk artifacts, but with all the other infrastructure that surrounds the
solution (virtual directories, queues, file folders, etc.)
- Extremely close
parity between server-side deployments and developer deployments - so you are
always testing your server deployments as you go through a typical developer
work day.
BizTalk 2006 itself introduced quite a few features to make deployment easier,
and can work fine for small (or solo) projects. Here are a few
limitations I've encountered:
-
Though much improved, it is still possible to get into "dependency chaos" -
where you spend time manually undeploying/deploying individual artifacts.
-
Binding changes have to be communicated "manually" between developers on a
team, since the import process is done through the Admin MMC or
command-line. Just "getting latest" from version control isn't
sufficient.
-
Binding files for your various environments have to be maintained as separate
files (manually), rather than "merging in" environment-specific settings
-
Artifacts such as rules, virtual directories, queues, folders, additional
dependent assemblies, etc. can be represented as "Resources" within the Admin
MMC (and in exported MSIs), but not in a fashion that easily moves between developers
on a team.
So! To get started with this version, download the
Deployment Framework (Tools) zip and run the
MakeBizTalkExternalTools_VS2005.vbs script. This will add entries to the
Visual Studio tools menu for deploying and undeploying using the
framework. You can download the
full sample to see the framework in action (first build it, then
do Tools-BizTalk Deploy. You'll see something like
this.)
The high-level approach is the same as the 2004 framework - you supply a small
project-specific NAnt script that indicates via properties what elements of a
deployment your solution requires. You include
BizTalkDeploymentInclude.nant to get all the core
deployment functionality, and make sure the DeployTools directory is
copied to your project. (Unzipping the
Deployment Framework Core into your project is a good way to do
this.) See the
documentation for a more complete discussion.
The primary difference in the upgraded framework for BizTalk 2006 is
that we now create a BizTalk Application definition, and use
BTSTask to add all BizTalk artifacts as resources within that
application. Starting and stopping the application is done at an
application level rather than per port/per orchestration.
Packaging up your solution as an MSI can be done with the WiX-based scripts
that have been in the framework for awhile, or by using the MSI
export mechanism in BizTalk 2006, if you prefer. (The latter solution
will require a few additional steps, and doesn't give you the parity described
earlier. But it works if you need to go that route.)
Other Notes:
-
NAnt
.85 RC4 (and
NAntContrib ) is required. Be sure to copy the new
BizTalk.NAnt.Tasks.dll to NAnt's bin directory. We need to call BizTalk's
.net 2.0 assemblies, and NAnt wasn't built against 2.0 -- so change
nant.exe.config to have only<supportedRuntime
version="v2.0.50727" /> in the<startup> element.
-
Log4net usage in the sample (which isn't required of course for the
framework) has been updated to
log4net 1.2.10 , as has my
serializable wrapper. You can find the new
log4net.Ext.Serializable in the
Tools
download (which is useful all by itself, apart from the framework.)
-
You can actually use the BizTalkDeploymentInclude.nant file in this release
with BizTalk 2004, if you like, to aid in your migration. There is NAnt
property to indicate 2004 vs. 2006.
-
Scan all the
previous release notes...
Leave comments with any questions/issues/etc. Enjoy!
Download:
Full Sample,
Framework Core,
Tools Source ,
Docs
Update: Andy pointed out that
Tom Abraham has given this a thorough treatment
here.
There appear to be cases where having BizTalk 2004 and
the .NET 2.0 framework installed on the same machine may cause you some
difficulty. I've seen a few instances where the BizTalk service will not
start...or where the BizTalk 2004 process is loading (gulp) the 2.0 runtime (so
says Process
Explorer.) This latter behavior will result in the VS2003 debugger
not attaching correctly (among other things...) See this
kb.
To fix this, you can modify the BTSNTSvc.exe.config file to include the
following (after
'configSections'):
<startup>
<supportedRuntime version="v1.1.4322"/>
</startup>
In my
last post I indicated there was a better story in BizTalk 2006 for
working with the C# code that is generated as an intermediate when
compiling ODX (orchestration) files - that is, better than having to
deal with temporary files and the BizTalk 2004
File Dump utility. I
If you've worked with BizTalk 2006, you might have already stumbled across
this. The xlang compiler for BizTalk 2006 is kind enough to place the
intermediate C# file for your ODX directly in the project directory. If
you have multiple orchestrations in an assembly, you will find a C# file for
each orchestration - but only one of those files will actually have
content (all the generated C# code is consolidated into one file.)
What this means is that you can do symblic debugging of orchestrations in
a fashion far easier than what
I described for BizTalk 2004. The Edit/Debug cycle might look like
this:
-
Build your orchestration project.
-
Open the associated (generated) C# file - perhaps make it a solution
item for convenience.
-
Search for something in one of your expression shapes (say "MyClass.Execute"),
and set a breakpoint.
-
From the Debug menu, choose Processes and attach to BTSNTSvc.exe. (Have
more than one?) Choose CLR debugging only - not native. The symbols
should be loaded automatically - no need to copy PDBs to the GAC for this case.
-
Trigger your orchestration however you normally would.
-
Find your problem, Debug-Detach All, and fix the problem in the
orchestration.
-
Decide that you really want
Gilles' MessageContext Debugger Visualizer
There is another benefit to having the generated C# files for orchestrations be
more accessible...If you deploy your
PDBs to the GAC, either using either my upcoming deployment work for
BizTalk 2006 or just the GetGacPath2 utility that I made available
here, you will find that stack traces that are recorded in the event log
actually reference line numbers within the generated C# code.
As I said in an earlier post, if you are responsible for troubleshooting
BizTalk applications in production, you are likely hungry for all the
information you can get about why something is failing. Since
all BizTalk project assemblies are in the GAC, the stack traces you
get (either from your own logging, or the event logs BizTalk generates for
unhandled exceptions) do not contain file and line number information by
default. This makes post-mortem analysis a lot harder...
When a BizTalk 2006 application is deployed to production, you might consider
archiving the generated C# code for production support. If you use
GetGacPath2 to put PDBs in the GAC, you will have file and line
number information within captured stack traces to aid in troubleshooting.
(Note that GetGacPath2 supports MSIL, 32 bit, and 64 bit assembly
caches. MSIL is the right choice for BizTalk 2006 projects.)
In the last release of the BizTalk Deployment Framework, an additional feature
was added that I thought deserved its own post for explanation...
If you are responsible for troubleshooting BizTalk applications in production,
you are likely hungry for all the information you can get about why something
is failing. Since all BizTalk project assemblies are in the
GAC, the stack traces you get (either from your own logging, or the event logs
BizTalk generates for unhandled exceptions) do not contain file and line number
information. This makes post-mortem analysis a lot harder...
A new custom NAnt task (getgacpath) and corresponding support within
BizTalkDeploymentInclude.nant was added for placing PDB (program database) files for all BizTalk
and component assemblies into the GAC. (You can use just this NAnt task
without the rest of the Deployment Framework, of course. In addition, a
standalone command line utility called GetGacPath has been
included in the "Tools" download.)
Why is this interesting? Well, because it allows you to get stack traces
that include file and line number information even when assemblies reside
in the GAC. (This is obviously useful and useable outside the domain of
BizTalk...) Without extra work, file and line number information is
generally missing from a stack trace if a PDB file is not co-located physically
with the corresponding assembly. Putting the PDB file directly into the
GAC is an easy way of solving this problem.
Set the "deployPDBsToGac" NAnt property to "true" in your project-specific NAnt
file to get this functionality.
A couple of points are worth mentioning:
There are some updates to the Deployment Framework and log4net story that I've
been meaning to release for quite some time. After this, I'll focus my
deployment efforts on BizTalk 2006 (didn't I say that last release?)
Below are a list of revisions. On this blog's home page under
"Downloads", you will find an updated full download (with sample), as well
as updated "Core" and "Tools" downloads. "Core" contains only the
deployment framework itself and can be used for upgrades of existing
projects. "Tools" contains source for all utilities used by the
framework. Be sure to put a fresh copy of BizTalk.NAnt.Tasks.dll in
your nant\bin directory.
-
Update to the log4net.Ext.Serializable library. Note that the "Tools"
download now has variants of this library for both log4net 1.2.0 and 1.2.9,
though the sample is built against 1.2.9. The important change here is
that the original version of the library had a threading bug...as a result, the
usage pattern for the library has changed somewhat. Now, an instance of
log4net.helpers.PropertiesCollectionEx is declared (as an orchestration
variable), and an expression shape at the top of your orchestration will do
something like below (notice logProps is passed to all logging calls so as
to preserve context despite thread changes across dehydrations.) See the
sample for additional detail.
logger = log4net.Ext.Serializable.SLogManager.
GetLogger(@"BizTalkSample",log4net.helpers.CallersTypeName.Name);
...
logProps.Set("InstanceId",TopLevelOrch(Microsoft.XLANGs.BaseTypes.InstanceId));
logger.Debug(logProps,"Received top level request...");
-
Update to the SSOSettingsFileReader class to include an "Update"
method. Likewise, an additional custom NAnt task called
"updatessoconfigitem" that allows you to add or update config items within an
SSO affiliate application. What is the use case for this? Suppose
you have a piece of information that you a) don't want to store in
SettingsFileGenerator.xls (perhaps because you can't manage that
file securely) and b) you need access to at run time. (For instance,
it might be an Oracle username/password.) You could ask an operator/admin
for this information at deployment time using
SetEnvUI.exe/InstallWizard.xml (from the deployment framework) -- and then in
your project-specific NAnt file do this:
<target name="customSSO" if="${property::exists('serverDeploy') and serverDeploy}">
<updatessoconfigitem ssoitemname="databaseUserName" ssoitemvalue="${sys.env.databaseUserName}"/>
<updatessoconfigitem ssoitemname="databasePassword" ssoitemvalue="${sys.env.databasePassword}"/>
</target>
At runtime, you can simply use SSOSettingsFileReader to retrieve this
information. Note that this example checks for whether we are doing a
server deployment - so in this case, we would have the development
environment values in SettingsFileGenerator.xls.
-
Added a small utility called SSONameValueDump to the tools download to view
current name/value pairs of an SSO affiliate application (if you have
appropriate permissions.)
-
Update to the <controlbiztalkports> custom NAnt task to allow Send Port
Groups to be shared across projects. That is, Send Port Groups will only
be removed if they no longer contain Send Ports (and putting new Send
Ports into an already-existing Send Port Group will happen automatically.)
-
XmlPreprocess will be run against log4net configuration files, if you are using
log4net. This allows you to use SettingsFileGenerator.xls to control logging
levels for different physical environments.
-
SetEnvUI.exe no longer defaults to "last directory" when doing a file browse,
to avoid confusion between applications when managing multiple BizTalk
application installs.
-
If you use
ElementTunnel, you will find that the xpath file will be reversed
automatically for unescape operations (thanks to Frank de Groot for that
update!)
-
Fix: When deploying HTTP/SOAP infrastructure, file system permissions granted
to the specified application pool account.
-
Fix: The "quick update" target (updateOrchestrations) now respects true/false
properties for includeSchemas, includeTransforms, etc. (A menu option in Visual
Studio is created for this target when you use the
MakeBizTalkExternalTools script. The target updates orchestrations, components,
schemas, and transforms (see Flanders'
post on this) Using this as part of your normal edit/run/debug cycle is
a must
for productivity…)
-
Fix: Handle multiple assemblies (of one artifact type) correctly in more cases,
including where we are not deploying to the management database (i.e. gac
deployment only.)
Download:
Full Sample,
Framework Core,
Tools Source
Late notice, I know, but if you can...be sure to join us at 6:00 pm at the local Microsoft office in Bloomington!
Jim Gaudette will be discussing reusability in BizTalk, and hey, food and beverages provided.
I've noticed that - despite a fair bit of discussion on the topic - many folks
wind up using atomic scopes simply to avoid serialization requirements for .Net
types within an orchestration. It isn't surprising this is the past of
least resistance - after all, the relevant compiler error sort of suggests this
as a fix:
error X2141: a non-serializable object type 'SomeAssembly.SomeComponent
yourComponent' can only be declared within an atomic scope or service
Recall that persistence will occur when messages are sent, when another
orchestration is started asynchronously (like via the Start Orchestration
shape), when an orchestration instance is suspended, when a host instance does
a controlled shutdown, when a "wait" state occurs (like a blocking receive or
delay shape that makes the schedule a candidate for dehydration), or when the
orchestration completes.
It will also occur at the end of a transactional scope to assist with resuming
in an unambiguous state (and executing compensation logic.)
So...although the introduction of an atomic scope prevents
serialization within the scope, it introduces serialization at the end
of the scope. This is often a persistence point (read: database hit) you
wouldn't have had to live with -- if an atomic scope was introduced purely
to get around the compiler error above.
Now...sometimes the .Net component you are using indeed requires state - and
you should go ahead and implement a serialization strategy for that case.
If the component can be stateless, then you might consider making the methods
you access "static" to avoid the compiler-enforced requirement
altogether.
There is a third case, however - where you know that serialization is not
required (given how your orchestration is structured) - but you don't own the
code for it, either. For instance, you might want to use an XmlNode to
hold the result of a SelectSingleNode call on an XmlDocument. If
your use of the XmlNode instance is confined to a single
expression shape...no state actually needs to be preserved. So, in this
case, consider wrapping the class as shown below. Here, we honor the
serialization requirement - but we don't, in fact, do any serialization
work. Now, no atomic scope is needed (avoiding the associated performance
hit). Use this technique with care - the state management facilities in
BizTalk are usually a great asset.
/// <summary>
/// It can be helpful when dealing with xpath expressions within orchestrations to
/// make use of a System.Xml.XmlNode (or derivations) without having to deal
/// with the fact that XmlNode is not serializable. If usage is confined to a single
/// expression shape, we actually won't have any serialization requirements, and so they
/// have empty implementation here.
/// </summary>
[Serializable]
public class BizTalkXmlNode : ISerializable
{
private XmlNode _xmlNode;
public BizTalkXmlNode()
{
}
public XmlNode XmlNode
{
get{return _xmlNode;}
set{_xmlNode = value;}
}
public string InnerText
{
get{return _xmlNode.InnerText;}
set{_xmlNode.InnerText = value;}
}
// This is helpful to use as a static helper method because the xpath expression in
// biztalk returns a XmlNode - so direct comparisons to string literals won't work.
// (And the expression editor won't let you get the InnerText property...)
public static string GetInnerText(XmlNode node)
{
return node.InnerText;
}
private BizTalkXmlNode(SerializationInfo info, StreamingContext context)
{
// no state intended...
}
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
// no state intended...
}
}
I had a chance to speak at the Twin Cities BizTalk User Group last Thursday. It was a great turnout, entirely too much pizza, and a lot of fun to talk to folks throughout the area about how they are using BizTalk. The deployment talk had 2004 content and a bit on 2006 as well – slides are here.
This great
post from Tomas on
receiving ack/nack notifications from the MSMQ[C] adapter prompted me to write
up something I had researched a few weeks back...
But first, a bit of background...
When doing work with message queueing systems, a very common (and well
supported) convention exists when two systems are exchanging messages: System
'A' sends a message to System 'B'. When System 'B' wishes to reply, it
uses the message identifier of the 'request' message as the correlation
identifier of the 'reply' message. The reply queue used is
either well known by both parties, or is communicated as a property on
the request message. For purposes of discussion, let's call this the
"queueing contract".
Both MSMQ and MQSeries have MessageId and CorrelationId properties on
messages. When looking at messages in a reply queue, System 'A' can peek
at the messages, looking for a particular CorrelationId prior to issuing a
receive. Alternatively, it might use the CorrelationId as a look-up
into working state of some sort. To facilitate request-response
interactions, MQSeries has had a ReceiveByCorrelationId API for a long while,
whereas MSMQ added this in 3.0 (that is, on Windows XP and Windows
2003.) (Actual property and method names may vary in real life...)
There are, of course, many deployed systems in the wild that implement the
queueing contract - using both MSMQ and MQSeries. You may find yourself
needing to integrate with these. At first blush, it seems that consuming
a service such as this should be a breeze from within
BizTalk orchestrations - after all, accomplishing correlation is a
first-class-citizen feature for BizTalk, so how tough can this be?
Correlation in BizTalk is indeed a first-class citizen...for data within
your documents (or promoted message context.) The classic example is
to define a property schema that embodies a business identifier like
"PONumber", and then visit all schemas that will be used across a set
of message interactions - promoting the appropriate field in each as
a "PONumber". Then, a correlation type and correlation set
are defined which reference that property, and Send and Receive
shapes are set to either initialize or follow the correlation as
appropriate.
The problem with an orchestration consuming the queuing contract we
described earlier is that this contract occurs at the transport layer.
To be a consumer of the queueing contract, an orchestration would like to send
a message, and initialize a correlation based on the MessageId...but the
MessageId is produced further downstream, at the point an actual MSMQ or
MQSeries message is generated by an adapter. Moreover, an orchestration
would subsequently like to receive a message, following an initialized
correlation - but the CorrelationId isn't promoted by the MSMQ
adapter (though it is for the MQSeries adapter...)
Solving for the MSMQ Adapter
The MSMQ Adapter gives us a small amount of help to get past this...We
can in fact do our initial send operation through a solicit-response port
(rather than a one-way port) and get back the MessageId that was generated by
the adapter -- it actually comes back in a single-element document with a
tag named "MSMQMsgId". Starting here, the approach I took to solving the
whole of the problem looked like this:
-
Send a message through a MSMQ solicit-response port. The response
operation in the orchestration designer can be of type "String".
-
On the response half of the solicit-response port, execute a pipeline &
pipeline-component that extracts the returned MessageId and promotes
it as the CorrelationId
(using the standard MSMQ adapter namespace for that property.) (For
tracing purposes & useability, the MessageId is also returned in a
<string> wrapper by the pipeline component rather than the native
<MSMQMsgId> wrapper.)
-
When the initial message (containing the correlation id in both content and
context!) is received back into the orchestration, it is sent back out
to initialize a correlation set (typed as MSMQ.CorrelationId.) This Send
shape is just a "dummy send"...it exists just to initialize the correlation,
because that is the only means given to us in the orchestration world. I
used
Patrick Wellink's "NOPE" Adapter for this purpose, but
there would be other options as well (including Tomas' Null Send Adapter, which I just recently rediscovered and which apparently performs better under load.)
-
Next, we receive the "real" response message - following the correlation that
was just initialized. Because MSMQ.CorrelationId is not promoted
by default, the response is brought through a second pipeline component that
performs that service for us.
See the diagram below (and the
downloadable sample) for more detail.

(click)
What can go wrong with this approach? Well...there is race condition that
can occur if the "real" response message (step 4 above) arrives before steps 2
and 3 can execute and establish the correlation (and associated instance
subscription.) In other words, if the "real" response message comes back
before the orchestration has had a chance to express interest in that
particular message, a routing failure will occur. This might be highly
unlikely for your scenario (given response times of the service you are
interacting with) but it is something to test for, nonetheless. (With
BizTalk 2006's ability to route failed messages, you could potentially catch
and retry in this scenario...)
(Note - if you are going to rely on the technique in the sample, consider
asking Microsoft support for BizTalk 2004 QFE 1647, which addresses an issue
with truncated correlation identifiers being returned to BizTalk...)
Solving for the MQSeries Adapter
Consuming the queueing contract with the MQSeries adapter for BizTalk can be
done with a technique quite close to the one just discussed - but with a little
less work. The response to the initial solicit-response interaction
returns with MQMD_MsgID already copied to a
MQSeries.BizTalk_CorrelationId property. Likewise, the when
receiving the "real" response message, MQMD_CorrelationId is copied to
the MQSeries.BizTalk_CorrelationId property. This means all correlation
can be done with this one property - no pipelines/pipeline-components are
needed!
From the documentation:

(click)
(The "dummy send" - though not pictured - would be required here as well to
initialize the correlation set.)
In addition, there is a much cleaner solution that the BizTalk
2004 MQSeries adapter provides. It turns out that MQSeries allows
the "caller" to assign the message ID (unlike MSMQ), so within BizTalk,
you can in fact set MQMD_MsgID and MQMD_CorrelationId to the same
value prior to doing your initial send. Now, you can initialize a
correlation set based on MQMD_CorrelationId with the first outgoing message,
and follow the correlation for the "real" response message. Slick!
From the documentation:

(click)
Solving for the MQSeries Adapter in BizTalk 2006
Ahhhh, here we have what looks like real simplicity...There is a new feature in
the BizTalk 2006 MQSeries adapter termed "Dynamic Receive" (referenced in the
BizTalk 2006 Adapter Enhancements document.) For a
solicit-response port, Dynamic Receive allows context properties on the
outbound message to determine which server, queue manager, and queue
should be "watched" for the response message. This is used
in conjunction with a "match" option - which allows you to wait for a
particular message based on CorrelationID (or any of several other properties,
such as MessageID, GroupID, SequenceNumber, Offset, or MsgToken...)
Not only does this give us quite a bit of flexiblity for where response
messages should be found (we don't need a fixed receive location anymore) but
it also elminates the whole of the problem of implementing the "queueing"
contract within orchestrations. With this feature in place, an
orchestration simply uses one solicit-response port, and the work is done.
Needless to say, it would be fantastic to get similar support from the MSMQ
Adapter (at least the ability to have a single solicit-response port with
a similar "match" criteria option, even without the dynamic receive
location.) But I don't think that is in the cards for BizTalk 2006...
Notes on the download:
-
See the BTMSMQCorrelation.PortBindings.xml file for the names of the two queues
and two file directories that must be created for this sample.
-
Install
Patrick Wellink's "NOPE" Adapter or choose a different
strategy for dummy sends (like Tomas' Null Send Adapter.)
-
Use the included
Deployment Framework-based deployment, or deploy manually.
-
To run, launch the "TestQueueServer" console app that will implement the
queueing contract (request/response)
-
The sample will import and run just fine in BizTalk 2006, though you will have
to deploy manually. (Still working on the 2006 version of the deployment
framework - more later...)
Hopefully this is helpful to those doing queueing work with BizTalk - enjoy!
|