Saturday, February 12, 2005 8:00:00 AM (GMT Standard Time, UTC+00:00)
One of the things you get used to developing with BizTalk is putting assemblies in the GAC. It becomes almost second nature to sign all the assemblies you create for use with BizTalk and run gacutil -i on them.
But as soon as you start building custom pipeline classes and adapters you must resist the urge to put assemblies in the GAC. Assemblies containing custom pipeline classes need to go into the "Microsoft BizTalk Server 2004\Pipeline Components\" directory (generally C:\Program Files\Microsoft BizTalk Server 2004\Pipeline Components") instead of into the GAC. The only reason I have ascertained for this requirement relates to the Pipeline Designer in Visual Studio .NET. For a type to appear on the Toolbox in VS.NET, the assembly containing that type has to be in that folder. When you right-click on the Toobox and select "Add/Remove Items" - the Pipeline Components tab reads directly from that folder. It loads all the assemblies in that special directory and uses reflection to find all the classes in those assemblies that have a certain custom attribute value associated with that type (the ComponentCategory attribute initialized to the CategoryTypes.CATID_PipelineComponent value. Nothing really wrong with this design.
The problem of course comes during assembly loading time. When .NET needs a type that isn't already in a loaded assembly, it uses a very specific set of rules to find and load the needed assembly. See How the Runtime Locates Assemblies. When an assembly is loaded, that assembly is given an associated binding context (for more info on this concept see here and here. The binding context relates to where the assembly was loaded from, and can be used by the assembly loading infrastructure about where to load dependent assemblies from. Most often this comes into play not with assemblies loaded on demand, but with assemblies which are explicitly loaded via calls to Assembly.LoadFrom. When you call Assembly.LoadFrom specifying the exact CodeBase from which to load the assembly, that CodeBase is used as a hint (stored in the binding context) by the Assembly Resolver when looking for dependant assemblies of the assembly loaded with LoadFrom. As an example, if you had an assembly (let's call it asm.dll) that you loaded with a call like this:
Assembly a;
a = Assembly.LoadFrom(@"C:\bin\asm.dll");
Once asm.dll is loaded, any assemblies that asm.dll references can be loaded from "C:\bin" instead of using the standard assembly loading rules. One issue that can arise when someone starts using Assembly.LoadFrom, is that when an assembly gets loaded with a specific binding context, it isn't considered the "same" assembly as an assembly with the exact same fully-qualified assembly name. In my example if asm.dll also got loaded from the GAC, the CLR would consider those two assemblies as being "different" (even if they are really exactly the same). If asm.dll contained a Type named "Foo",calling Type.Equals on an instances of Foo loaded from the asm.dll loaded via Assembly.LoadFrom, passing in the Type of an instanced loaded from the asm.dll from the GAC dynamically would always return false.
The upshot of this for BizTalk is that if an assembly containing a pipeline component is loaded from the correct directory (the Pipeline Components directory under the BizTalk install directory) and loaded from the GAC, things could easily start to break.
When a pipeline is being configured, the messaging infrastructure in BizTalk calls Assembly.LoadFrom on all the types configured inside of the pipeline configuration (this is the XML document that a type that derives from BasePipeline holds in it's XmlContent property). The calling code is actually unmanaged COM code, and the ComponentLoader type inside of Microsoft.BizTalk.Messaging.dll calls Assembly.LoadFrom on all pipeline assemblies.
The morale of this whole discussion is that you absolutely, positively, under no circumstances should put an assembly containing a pipeline component into the GAC. No matter what odd assembly loading errors you might be getting, you have to solve the problem another way. What bad things could happen? Well, it is a similar problem calling Windows Forms Controls from a thread other than the thread that created them, it will not always be clear immediately what the problems you might have are, but they will come, and they will be difficult to debug.
BizTalk
Tracked by:
"How Pipeline Component Assemblies are Loaded" (Commonality) [Trackback]
http://www.winterdom.com/weblog/2006/10/06/HowPipelineComponentAssembliesAreLoad... [Pingback]