- The Mapper
- Functoids
- Advanced Maps
- Building Custom Functoids
- Testing of Maps
- Summary
Building Custom Functoids
You can use the built-in functoids as building blocks to provide for most mapping needs you will encounter. Sometimes, however, you will need functionality that the built-in functoids cannot provide. Other times, you might find yourself building the same combination of functoids to solve a specific problem in your map over and over again, which is tiresome and which creates a mess in your map. To overcome this, you can develop your own functoids. Developing custom functoids is not nearly as scary as it sounds, and in fact you will probably find out that the most difficult part is to create an icon that is nice and descriptive of your functoid. This section describes how to develop a custom functoid.
Functoids are divided into two categories:
- The noncumulative, or “normal,” ones, which expose one method that takes in some parameters or possibly no parameters and returns a string.
- The cumulative ones, which expose three methods, where the first is called to initialize the functoid; the second is then called for all inputs, and the third method is called to get the final value.
Also, functoids can be divided into two other types:
- The type that is compiled into an assembly and put into the Global Assembly Cache (GAC). It exposes one or more methods that are called at runtime to perform your functionality. This type is also known as a referenced functoid.
- The type that doesn’t expose a method that is called at runtime, but instead outputs a script that is included in the map. This type is also known as an inline functoid.
You should consider developing an inline functoid when
- You have no worries about your code getting into the map as clear text (which allows others to read it and possibly modify it).
- Your functoid depends only on .NET namespaces that are available to maps. For a full list of these, refer to http://msdn.microsoft.com/en-us/library/aa561456(BTS.70).aspx.
- You do not want to have to maintain another assembly, remembering to add it to installation scripts, deploying it to all servers in your BizTalk group, and so on.
- You want to provide the developer that uses the functoid with the ability to debug the maps that use your functoid.
- You are developing more than one functoid, and they need to share variables.
You should consider developing a referenced functoid when
- You want to be able to put a new assembly in the GAC and restart host instances for it to work in all maps that use the functoid without any need to maps to be recompiled.
- You do not want your business logic code exposed in clear text for all to read and possibly modify.
- Your functoid depends on .NET namespaces that are not available to maps.
You do not have to choose either an inline functoid or a referenced functoid. As a matter of fact, you can develop your functoid to be both and let the developer of the map choose which implementation to use.
Initial Setup
No matter what type of functoid you want to create, you want to create a Visual Studio 2010 project for it. You do this by either right-clicking your solution and choosing Add, New Project or by creating a new project in a new solution if you do not have an existing solution you want to add the project to. You can have the project with your functoids in the same solution as your BizTalk projects and any other projects, if you like. The project should be a Class Library project, as shown in Figure 3.35.
Figure 3.35. Adding a new Class Library project to your Visual Studio 2010 solution.
After adding the project to your solution, you should either rename the automatically added class to a better name or delete it and add a new one. Also, you need to add a reference to the Microsoft.BizTalk.BaseFunctoids.dll, which you can find under <InstallationFolder>\Developer Tools. Finally, you need to add a string name to the assembly so that it can be GAC’ed after the functoid has been developed.
The Microsoft.BizTalk.BaseFunctoids namespace contains a BaseFunctoid class that must be the base class for all functoids. You must therefore let your class inherit from this and call the constructor of the base class. Listing 3.7 shows an example.
Listing 3.7. Extending the Needed Base Class Required to Create a Custom Functoid
using Microsoft.BizTalk.BaseFunctoids; namespace FineFoods.Map.Functoids { public class StringReplace : BaseFunctoid { public StringReplace() : base() { } } }
Inside the constructor, you need to set the value of some properties and call some methods on the base class. The steps you must go through for all functoids are described in this section, and the ones that are specific for either normal or cumulative functoids are described in the next sections:
- Add a resources file to your project. To do so, right-click your project and choose Add, New Item. In the next screen, choose Resources File. Provide a descriptive filename that reflects whether the resources file is for one functoid only or for a collection of functoids that are in the same assembly. Figure 3.36 shows an example.
- Add three string resources: one for the name of the functoid, one for the tooltip of the functoid, and one for the description of the functoid. Provide descriptive names of the resources. Figure 3.37 shows an example.
Figure 3.36. Adding a resources file to your project.
Figure 3.37. Adding resources for name, tooltip, and description..
- Add an image resource of type Bitmap for the icon of the functoid. Provide a descriptive name of the resource. After adding it, you can edit the bitmap. Change it to be 16x16 pixels in the properties of the bitmap, and then release your inner artist. Figure 3.38 shows how to add the bitmap resource.
- Assign a value to the ID property. The value must be an int, and it must be greater than 6000 because the first 6000 are reserved for internal BizTalk usage. Always keep track of all IDs you use in your organization to make sure you do not get an overlap. If you use third-party functoids, there is no way of knowing what other IDs are in use by these other than using reflector on the assemblies and looking at the source code.
Figure 3.38. Adding a bitmap resource to serve as the icon for the functoid.
- Call the SetupResourceAssembly method to let the base class know where to find the resources file that contains the resources for name, tooltip, description, and icon. The method takes two parameters. The first is the fully qualified .NET type name of the resources file. This is normally the name of the project concatenated with a period and the name of the resources file without extension. So if your project is called FineFoods.Maps.Functoids and your resources file is called FunctoidResources.resx, the fully qualified .NET type name of the resources file will be FineFoods.Map.Functoids.Resources. If in doubt, open <ResourceFile>.Designer.cs file, where <ResourceFile> is the name of your resources file without the .resx extension. In the designer file, you can see namespace and class name. Concatenate these with a period in between and you have the fully qualified name. The second is the executing assembly. Listing 3.8 shows an example.
- Call the SetName method, which takes in one string parameter that defines the name of the resource that the base class should use to find the name of the functoid.
- Call the SetTooltip method, which takes in one string parameter that defines the name of the resource that the base class should use to find the tooltip of the functoid.
- Call the SetDescription method, which takes in one string parameter that defines the name of the resource that the base class should use to find the description of the functoid.
- Call the SetBitmap method, which takes in one string parameter that defines the name of the resource that the base class should use to find the icon of the functoid.
- Set the value of the Category property. This property is of the type FunctoidCategory, which is an enumeration, and it must be set to match the category this functoid should belong to. There are 25 different categories, of which only 7 are supposed to be used in custom functoids. These are Conversion, Cumulative, DateTime, Logical, Math, Scientific, and String.
- As you can probably imagine, these are used to let the Mapper Toolbox know in which group of functoids to show your custom functoid. Some of the categories are also used to let the Mapper know how to create the XSLT; that is, functoids in the category Logical are useful for determining when to create destination nodes. This is explained in more detail later.
- Determine how many parameters your functoid should take in as a minimum and as a maximum. Call the SetMinParams and SetMaxParams with the correct values.
- For each parameter, determine what the source of the input link for the parameter can be. For custom functoids, the parameters can often be links coming from anything that has a value. After determining what possible inputs the parameters can have, you must call AddInputConnectionType for each parameter in the order of the inputs to specify what possible inputs the parameters can have. The possible parameter values for the AddInputConnectionType method call are the values in the ConnectionType enumeration, and there is a value for each functoid category and also some other possible values like All, AllExceptRecord, Element, and so on. The AllExceptRecord is often used because this will allow all inputs that are not a record, and this can be useful because a record does not have a value, whereas others have a value.
- Determine what the outgoing link from the functoid can be connected to. After determining this, set the appropriate value to the OutputConnectionType property, which is of type ConnectionType enumeration.
For setting either the input connection type or the output connection type, you can set it to a combination of values, which gives you control of what possibilities you want to allow.
Listing 3.8 shows a functoid that does a string replacement on an incoming string. The functoid code is not complete, but contains the methods and properties that have been discussed up to now.
Listing 3.8. Functoid That Does String Replacement
public class StringReplace : BaseFunctoid { public StringReplace() : base() { ID = 8936; SetupResourceAssembly(GetType().Namespace + ".FunctoidResources", Assembly.GetExecutingAssembly()); SetName("StringReplace_Name"); SetTooltip("StringReplace_ToolTip"); SetDescription("StringReplace_Description"); SetBitmap("StringReplace_Icon"); Category = FunctoidCategory.String; SetMinParams(3); SetMaxParams(3); AddInputConnectionType(ConnectionType.AllExceptRecord); AddInputConnectionType(ConnectionType.AllExceptRecord); AddInputConnectionType(ConnectionType.AllExceptRecord); OutputConnectionType = ConnectionType.AllExceptRecord; } }
Normal Functoid
The functoid code in Listing 3.8 is not finished yet. It still has no implementation of the functionality it is supposed to do. As explained earlier, this can be achieved either as a referenced functoid or as an inline functoid and either as a normal functoid or as a cumulative functoid. Implementing it as both a referenced functoid and as an inline functoid is explored in this section.
Referenced Functoid
When implementing a functoid as a referenced functoid, you must provide the method that is to be called at runtime. Listing 3.9 shows a method that provides the string replacement functionality.
Listing 3.9. A Method Performing String Replacement
public string Replace(string str, string search, string replace) { if (String.IsNullOrEmpty(str)) return String.Empty; if (String.IsNullOrEmpty(search)) return str; return str.Replace(search, replace); }
To instruct BizTalk what method to call at runtime, you must call the SetExternalFunctionName method. This method has two overloads.
- The first takes in three parameters, where the first is the full name of the assembly, the second is the class that contains the method, and the third is the name of the method to call.
- The second takes in four parameters but is not intended to be used in custom code.
Listing 3.10 shows the added functionality to the functoid from Listing 3.8 that is needed to make the functoid work.
Listing 3.10. A Referenced Functoid That Does String Replacement
public class StringReplace : BaseFunctoid { public StringReplace() : base() { // Functionality from Listing 3.8 SetExternalFunctionName(GetType().Assembly.FullName, GetType().FullName, "Replace"); } public string Replace(string str, string search, string replace) { if (String.IsNullOrEmpty(str)) return String.Empty; if (String.IsNullOrEmpty(search)) return str; return str.Replace(search, replace); } }
The call the SetExternalFunctionName in Listing 3.10 is coded to have the method to call inside the same class as the functoid itself. If this is not the case, you must change the parameters to point to the correct assembly, class, and method.
Inline Functoid
If you should choose to implement an inline functoid rather than a referenced functoid, you should not call the SetExternalFunctionName method, but instead some other methods and properties must be used.
The first is a method called AddScriptTypeSupport. It takes in one parameter, which is the ScriptType enumeration. You must send in the value that matches the script type you will be creating. For instance, you can send in a value of ScriptType.CSharp to tell the Mapper that the script is a C# script.
The second is a method called SetScriptBuffer, which is called to set the script that will be included in the map. It takes in two parameters and one optional parameter. The first parameter is the ScriptType for this script. The second parameter is the string that is the actual script. Most often, this parameter is a method call to a method that returns the string that contains the script and not a constant string itself, because that would be too big and confusing. The third and optional parameter is used for cumulative functoids, which are described in the next section.
The third method used for inline functoids is called SetScriptGlobalBuffer. This method is used to add some script that must be global for all your scripts. This can initialize a variable, for instance, which is needed for cumulative functoids or functoids that just need to know values from other scripts. Just as the SetScriptBuffer method, this method takes in the ScriptType of the script and a string that contains the script.
The fourth is a property called RequiredGlobalHelperFunctions. This is used to let the Mapper know whether some built-in helper functions are needed for the script to execute. This is to allow for the use of the built-in IsNumeric and IsDate methods that are easily accessible in a referenced functoid. Also, for inline functoids, you can make use of the ValToBool method, which tests whether your string is a Boolean value. This method is not accessible for referenced functoids.
Listing 3.11 shows a method that generates the same method as shown in Listing 3.9, only for an inline functoid.
Listing 3.11. An Inline Functoid That Does String Replacement
private string GetCSharpBuffer() { StringBuilder sb = new StringBuilder(); sb.Append("\n"); ‑sb.Append("public string Replace(string str, string search, string replace)\n"); sb.Append("{\n"); sb.Append("\tif (String.IsNullOrEmpty(str))\n"); sb.Append("\t\treturn String.Empty;\n"); sb.Append("\tif (String.IsNullOrEmpty(search))\n"); sb.Append("\t\treturn str;\n"); sb.Append("\treturn str.Replace(search, replace);\n"); sb.Append("}"); return sb.ToString(); }
So, as you can see, for inline functoids, you must create a string that contains the exact same C# method you would have written if it were to be called in a referenced functoid. The new lines and tabulator characters are not needed, but are there to make the method look readable when viewing it inside the map after you have compiled it.
For inline functoids, you have the option of generating an inline script that takes in a variable number of inputs. This requires some other method calls and is described in the “Advanced Functoids” section.
The code for the inline functoid that does string replacement can be seen in Listing 3.12.
Listing 3.12. An Inline Functoid That Does String Replacement
public class StringReplace : BaseFunctoid { public StringReplace() : base() { // Functionality from Listing 3.8 AddScriptTypeSupport(ScriptType.CSharp); SetScriptBuffer(ScriptType.CSharp, GetCSharpBuffer()); } private string GetCSharpBuffer() { StringBuilder sb = new StringBuilder(); // Code to build the method, as shown in Listing 3.11. return sb.ToString(); } }
Creating an inline C# functoid is most of the times the most appropriate over XSLT for three reasons:
- You get the .NET framework, which enables you to write your functionality with a minimum number of lines.
- XSLT has lots of quotation marks, which can get heavy to track when building a string. For information purposes, the code that is needed in an XSLT Call-Template functoid for string replacement is shown in Listing 3.13. The reason for this quite long code in XSLT is that the version of XSLT that BizTalk supports does not include a native Replace function, so you have to do it yourself. Imagining the code to build this as a string for an inline functoid is left to the reader.
- The real strength of XSLT functoids is that XSLT can access the entire structure of the source schema and it has the responsibility of creating the output structure. This means that the functoid will be hard wired to those two structures. Because the purpose of a custom functoid is to take some functionality that is often needed and wrap it in a common generic component, custom XSLT functoids actually go against this purpose.
Listing 3.13. XSLT Example of Doing String Replacement
<xsl:template name="MyXsltReplaceTemplate"> <xsl:param name="str" /> <xsl:param name="search" /> <xsl:param name="replace" /> <xsl:element name="Field6"> <xsl:call-template name="DoReplace"> <xsl:with-param name="str" select="$str" /> <xsl:with-param name="search" select="$search" /> <xsl:with-param name="replace" select="$replace" /> </xsl:call-template> </xsl:element> </xsl:template> <xsl:template name="DoReplace"> <xsl:param name="str" /> <xsl:param name="search" /> <xsl:param name="replace" /> <xsl:choose> <xsl:when test="contains($str, $search)"> <xsl:value-of select="substring-before($str, $search)" /> <xsl:value-of select="$replace" /> <xsl:call-template name="DoReplace"> <xsl:with-param name="str" select="substring-after($str, $search)" /> <xsl:with-param name="search" select="$search" /> <xsl:with-param name="replace" select="$replace" /> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:value-of select="$str" /> </xsl:otherwise> </xsl:choose> </xsl:template>
Cumulative Functoid
Cumulative functoids are useful for performing functionality on recurring elements. The built-in cumulative functoids provide functionality for finding the smallest number, greatest number, and so on of a reoccurring element.
If you want to develop a cumulative functoid yourself, you need to specify three methods rather than one. The first method initializes the functoid at runtime. The second is called for each occurrence of the recurring element, and the last is called at the end to retrieve the aggregated value that should be output in the end.
Thread Safety
For cumulative functoids, an issue of thread safety arises that is usually not present for normal functoids.
Normal functoids have just one method that is called, and unless you use some variable inside your method that is globally defined, you are usually safe. Cumulative functoids are different, though, because you need a global variable in your functoid to hold the state of your calculations across the initialization, multiple calls to the add method, and the final call to the get method.
You get some help in making your functoid thread safe, though. To all three methods, an index variable is sent as a parameter, which is unique for each instance of the functoid inside the map, which means that you can use it as an identifier into some data structure you must maintain with the calculations you perform. Listing 3.14 shows an example of how the XSLT looks when a cumulative functoid is used multiple times in a map. You can see that the first occurrence of the cumulative functoid uses an index of 0 and the second occurrence uses an index of 1.
Listing 3.14. XSLT Generated When a Cumulative Functoid Is Used Two Times in One Map
<xsl:template match="/s0:InputRoot"> <ns0:OutputRoot> <xsl:variable name="v1" select="userCSharp:Init(0)" /> <xsl:for-each select="/s0:InputRoot/Field1"> <xsl:variable name="v2" select="userCSharp:Add(0,string(./text()),"1000")" /> </xsl:for-each> <xsl:variable name="v3" select="userCSharp:Get(0)" /> <Feld1> <xsl:value-of select="$var:v3" /> </Field1> <xsl:variable name="v4" select="userCSharp:Init(1)" /> <xsl:for-each select="/s0:InputRoot/Field2"> <xsl:variable name="v5" select="userCSharp:Add(1,string(./text()),"1000")" /> </xsl:for-each> <xsl:variable name="v6" select="userCSharp:GetCumulativeMax(1)" /> <Field2> <xsl:value-of select="$var:v6" /> </Field2> </ns0:OutputRoot> </xsl:template>
This way of using an index is the same both for a cumulative referenced functoid and a cumulative inline functoid. The scope parameter to the second method is not used and can therefore be ignored in your code.
Cumulative Referenced Functoids
For referenced functoids, the runtime engine doesn’t necessarily instantiate an object of your functoid class for each map it executes, but rather reuses the existing object if present. Unfortunately, this means that your functoid can get an index of 0 as parameter to any one of the methods from multiple instances of the map at the same time without your code being able to distinguish them from each other. This, in turn, means that it is impossible to develop a custom referenced cumulative functoid that is thread-safe, and this should therefore be avoided.
If you want to develop a custom referenced cumulative functoid, you need to set the three methods that are to be used at runtime by the mapper. This is done via the SetExternalFunctionName, SetExternalFunctionName2, and SetExternalFunctionName3 methods. They set the initialization method, the accumulation method, and the get method, respectively. Listing 3.15 shows an example of the code needed. The code is given in full except for the code already listed in Listing 3.8 because it will make it easier to understand the code in Listing 3.16, which shows how to build he same functionality for an inline functoid.
Listing 3.15. Sample Code of a Custom Referenced Cumulative Functoid
private Dictionary<int, string> myCumulativeArray = new Dictionary<int,string>(); public CummulativeComma() : base() { // All the functoid setup code seen in Listing 3.8 SetExternalFunctionName(GetType().Assembly.FullName, GetType().FullName, "InitializeValue"); SetExternalFunctionName2("AddValue"); SetExternalFunctionName3("RetrieveFinalValue"); } public string InitializeValue(int index) { myCumulativeArray[index] = ""; return ""; } public string AddValue(int index, string value, string scope) { string str = myCumulativeArray[index].ToString(); str += value + ","; myCumulativeArray[index] = str; return ""; } public string RetrieveFinalValue(int index) { string str = myCumulativeArray[index].ToString(); if (str.Length > 0) return str.Substring(0, str.Length - 1); else return ""; }
Cumulative Inline Functoids
Contrary to referenced cumulative functoids, you can develop a thread-safe inline cumulative functoid. This is because whereas the Mapper reuses the same object for referenced functoids, there is no object to reuse for an inline functoid because all the code is inline in the XSLT. Therefore, the data structure is not shared among multiple instances of the map, effectively making the index parameter, which is unique across multiple instances of the functoid in one map, enough to guarantee thread safety. This requires, naturally, that you develop the functoid using the index parameter to access a specific entry in the data structure.
Building a custom inline cumulative functoid basically requires the same three methods as for a referenced cumulative functoid. As with the referenced version, you need to initialize the needed data structure.
For setting the needed three methods that are used at runtime, you must call the SetScriptBuffer method three times, with a parameter indicating whether you are setting the initialization, adding, or retrieval method. For initializing the data structure, you must call the SetScriptGlobalBuffer method. Listing 3.16 shows sample code for a custom inline cumulative functoid, with the code from Listing 3.8 omitted.
Listing 3.16. Inline Version of the Referenced Functoid from Listing 3.15
public CummulativeComma() : base() { // All the functoid setup code seen in Listing 3.8 SetScriptGlobalBuffer(ScriptType.CSharp, GetGlobalScript()); SetScriptBuffer(ScriptType.CSharp, GetInitScript(), 0); SetScriptBuffer(ScriptType.CSharp, GetAggScript(), 1); SetScriptBuffer(ScriptType.CSharp, GetFinalValueScript(), 2); } private string GetFinalValueScript() { StringBuilder sb = new StringBuilder(); sb.Append("\npublic string RetrieveFinalValue(int index)\n"); sb.Append("{\n"); sb.Append("\tstring str = myCumulativeArray[index].ToString();"); sb.Append("\tif (str.Length > 0)\n"); sb.Append("\t\treturn str.Substring(0, str.Length - 1);\n"); sb.Append("\telse\n"); sb.Append("\t\treturn \"\";\n"); sb.Append("}\n"); return sb.ToString(); } private string GetAggScript() { StringBuilder sb = new StringBuilder(); sb.Append("\npublic string AddValue(int index, string value, string scope)\n"); sb.Append("{\n"); sb.Append("\tstring str = myCumulativeArray[index].ToString();"); sb.Append("\tstr += value + \",\";\n"); sb.Append("\tmyCumulativeArray[index] = str;\n"); sb.Append("\treturn \"\";\n"); sb.Append("}\n"); return sb.ToString(); } private string GetInitScript() { StringBuilder sb = new StringBuilder(); sb.Append("\npublic string InitializeValue(int index)\n"); sb.Append("{\n"); sb.Append("\tmyCumulativeArray[index] = \"\";\n"); sb.Append("\treturn \"\";\n"); sb.Append("}\n"); return sb.ToString(); } private string GetGlobalScript() { return "private Dictionary<int, string> myCumulativeArray = new Dictionary<int,string>();"; }
Developing Advanced Functoids
This section covers some advanced topics related to developing custom functoids.
Functoids with a Variable Number of Inputs
Sometimes you need to develop a functoid that should take in a variable number of parameters. For instance, the Addition functoid in the Math category takes in a variable number of parameters. Doing this is only supported for creating inline functoids and can therefore not be done with a custom referenced functoid.
To develop a custom inline functoid that takes a variable number of parameters, you must do this:
- Set the property HasVariableInputs to true.
- In the constructor, call AddScriptTypeSupport for each script type you support.
- Override the GetInlineScriptBuffer method. Listing 3.17 shows an example. This method takes in three parameters:
- A script type determining the type of script to return.
- An integer determining the number of parameters your functoid will be getting.
- A function number for use with cumulative functoids. Values can be 0, 1 and 2, for initializing, accumulating, and retrieving functions, respectively.
- Set the RequiredGlobalHelperFunctions to reflect any global helper methods you may need, such as the IsDate, IsNumeric, and so on.
- Use SetScriptGlobalBuffer to declare any global variables you may need. For cumulative functoids, you need to initialize some data structure that is used across the calls to the three functions.
Listing 3.17. Generating a Functoid That Takes in a Variable Number of Parameters
protected override string GetInlineScriptBuffer(ScriptType sT, int numPar, int func) { if(ScriptType.CSharp == scriptType) { StringBuilder builder = new StringBuilder(); builder.Append("public string MyFunction("); for(int i=0; i<numParams; i++) { if(i > 0) builder.Append(", "); builder.Append("string param" + i.ToString()); } builder.Append(")\n"); // Method body; Do what you need with the parameters. builder.Append("{\n"); builder.Append("}\n"); return builder.ToString(); } return string.Empty; }
The code in Listing 3.17 assumes this is not a cumulative functoid and therefore ignores the func parameter. Had this been for a cumulative functoid, the method would have to return one of three functions, given the value of the func parameter. Also, the method shown in Listing 3.17 works only for C#. If the developer of the map requires something else, you must extend the method to also support that ScriptType and return valid methods for that.
Functoid Categories
When assigning a functoid category to your functoid, you get to choose between 25 different categories, of which only 7 are supposed to be used in custom functoids. These are Conversion, Cumulative, DateTime, Logical, Math, Scientific, and String.
Assigning one of these categories to your functoid has some effects:
- The category maps to one of the categories in the Mapper Toolbox in Visual Studio 2010, so choose a category that matches where you want the functoid to be placed.
- Some functoids have restrictions as to what types of input they can have. For instance, a Value Extractor must have a Database Lookup functoid as its first input. The Database Lookup is actually a functoid category in itself; it just belongs to the Database group in the Toolbox. This means that how your functoid will be used in a map may therefore also influence what category you want to choose for it.
- Some categories imply some semantics other than just the two preceding bullets. For instance, a Logical functoid, as explained earlier, when connected to an output record determines whether the record should be created. You can therefore not create a functoid that is in the Logical category and use it to map a value of true or false to a field.
You can see all the possible values for the functoid category when developing custom functoids on the MSDN site at http://msdn.microsoft.com/en-us/library/microsoft.biztalk.basefunctoids.functoidcategory(BTS.70).aspx. Most of them are for internal use only, because they impose some semantics that you cannot control in your code.
Deployment of Custom Functoids
After developing a functoid, it must be deployed to be used. Deployment of a functoid can be divided into deployment on a development machine where a developer can then use the functoid in any maps created and deployment on a server that needs to be able to run the functoid at runtime.
For a server that needs to execute the functoid at runtime, the assembly containing the functoid must be put into the Global Assembly Cache (GAC) if the functoid is a referenced functoid. If the functoid is an inline functoid, no deployment is necessary.
For easy deployment, you can add the assembly with the functoid to your BizTalk application, which will deploy it along with the rest of the assemblies when the exported MSI package is installed on the server. To do this, follow these steps:
- Open the BizTalk Server 2010 Administration Console.
- Right-click your application and choose Add, Resources.
- Click Add.
- Browse your way to the assembly that contains the pipeline component and double-click it (or click it once and click Open).
- In the File Type drop-down, choose System.BizTalk:Assembly.
- Make sure the Add to the Global Assembly Cache on MSI File Install (gacutil) check box is checked.
- Click OK.
The final screen should look like Figure 3.39
For deployment to a developer machine, the assembly must be copied into the <InstallationFolder>\Developer Tools\Mapper Extensions folder. After copying the assembly to this folder, you can add it to the Toolbox in Visual Studio 2010.
Figure 3.39. Adding an assembly to your BizTalk application to have it automatically deployed with the MSI file.
To deploy the assembly on a development machine for it to be used when developing maps, you should copy the assembly to the <InstallationFolder>\Developer Tools\Mapper Extensions folder. This is not strictly required, but it is the easiest way, because your functoid will then always be in the Toolbox, no matter how many times the Toolbox is reset. To add the component to the functoid Toolbox that is available at design time, follow these steps:
- Open Visual Studio 2010
- Go to Tools, Choose Toolbox Items, or right-click the Toolbox and choose Choose Items.
- Go to the Functoids pane
- If the functoid is present in the list, make sure it is checked.
- If the functoid is not present in the list, click Browse, and browse your way to the assembly that contains the pipeline component.
If the functoid is placed in the <InstallationFolder>\Developer Tools\Mapper Extensions folder you can also right-click inside the Toolbox and choose Reset Toolbox. Be aware, though, that this completely resets the Toolbox, which might not be what you want.
Note that the Toolbox sometimes fails to update itself when new versions of a functoid are deployed. In this case, you can delete the following four files, which contain Visual Studio 2010s Toolbox setup:
- toolbox.tbd
- toolbox_reset.tbd
- toolboxIndex.tbd
- toolboxIndex_reset.tbd
On Windows Server 2008, using Visual Studio 2010, you can find these under C:\Users\<User>\AppData\Local\Microsoft\VisualStudio\10.0. For different operating systems or different installation options, you can search for them. They are hidden files, so you need to search for those. If you want to script the deletion of the files in a batch file, you can use the script shown in Listing 3.18 in a batch file.
Listing 3.18. Script for Deleting Visual Studio 2010 Toolbox Items
@echo off C: cd "\Users\<User>\AppData\Local\Microsoft\Visual Studio\10.0" del toolbox* /AH pause
If you need to update a functoid that is also in the GAC, you must both update it in the GAC and in the <InstallationFolder>\Developer Tools\Mapper Extensions folder. This is because Visual Studio 2010 uses the version in the GAC if present and any updates in the file system are therefore not used.
Note that overriding the assembly that has been copied to the <InstallationFolder>\Developer Tools\Mapper Extensions folder is not always possible because it might be in use by Visual Studio 2010, BizTalk runtime, or some isolated host like Internet Information Services (IIS). In these cases, you need to restart whatever programs are locking the assembly before you can copy the new version.
If you are trying to add your functoid to the Toolbox and get an error doing this, the most common causes are as follows:
- Your resources are not set up correctly, so the name, tooltip, description, and icon cannot be correctly fetched and used by the Toolbox.
- Your class is not marked public.
- There is an older version of your component in the GAC that is not valid.
If you get a sharing violation when copying your newly compiled assembly to the <InstallationFolder>\Developer Tools\Mapper Extensions folder, you might consider writing a script that deploys the new component for you. The script should
- Restart IIS using iisreset. Only needed in your script if you have any receive functionality running in IIS like hosted WCF services, HTTP, SOAP, or others.
- Restart BizTalks Host instance. Only needed if the functoid has been used by BizTalks runtime. This can be done in a couple of ways:
- By using net stop BTSSvc$BizTalkServerApplication and net start BTSSvc$BizTalkServerApplication.
- By using PowerShell and doing get-service BTS* | foreach-object -process {restart-service $_.Name}
- By using WMI. For details about this, refer to http://msdn.microsoft.com/en-us/library/aa578621(BTS.10).aspx.
- Copy the DLL to the <InstallationFolder>\Developer Tools\Mapper Extensions folder.
- Use gacutil to add the new version to the GAC, like this: "C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin\gacutil.exe" /if NewlyCompiled.DLL
- Delete the Toolbox items, as described earlier, if you are having issues updating the toolbox.
Visual Studio 2010 should probably be closed first, as well. You can do this a bit faster if you test your functoid in a Visual Studio 2010 BizTalk project that is the only project in a test solution. This Visual Studio 2010 will be the only instance of Visual Studio 2010 that has a lock on the file, and it can be closed and reopened faster than your entire solution.
Debugging
When developing a custom functoid, at some point you will probably want to debug it to make sure not only that it provides you with the expected output under normal circumstances but also to test borderline cases where input might be missing or be in an unexpected format. For a functoid that is to be used throughout your company, it is essential that all other developers can trust on your functoid to behave as expected and give the expected output under all circumstances.
How to debug a custom functoid depends on whether you have developed a referenced or an inline functoid.
Common Debugging Options for Inline and Referenced Functoids
The first and obvious way of debugging your custom functoid is to use it in a map and then validate and test your map from within Visual Studio 2010, checking that no errors occur and manually inspecting the output to make sure it provides the expected result.
Whether you need to debug a custom referenced or a custom inline functoid, you can and should leverage the unit testing capabilities of BizTalk maps. This basically enables you to check all cases the functoid should be able to handle. Because unit testing of maps should be enabled for all maps even if no custom functoids are present, it is an obvious testing solution for your custom functoids, as well. Unit testing of maps are explained in the “ch02lev1sec8” section.
Debugging Referenced Functoids
Debugging a custom referenced functoid can be achieved in different ways. No matter which way you choose, the functoid must be compiled in Debug mode.
Separate Assembly
If the functionality the functoid is to perform is complex, you can write a separate assembly with the functionality and then a small console application that uses your assembly or do some standard unit tests on this to make sure the functionality is as expected. When satisfied with the results, you can wrap the functionality in a functoid either by copying the code or by referencing the unit-tested assembly.
Runtime Debugging
Another way of debugging your functoid is to deploy a map that uses the functoid and debug it when an instance is sent through BizTalk. Sometimes your functoid actually depends on the context it is executed in, and in this case, this is your only option of debugging it.
When debugging the functoid inside BizTalks runtime, make sure the latest compiled version is in the GAC and make sure your host instance has been restarted, so it hasn’t cached a previous version. Inside Visual Studio 2010, set breakpoints in your code wherever you want the code to break and allow you to inspect values and step into code. When this it done, a few steps are needed to start debugging:
- Inside Visual Studio 2010, go to Debug, Attach to Process.
- Choose the BTSNTSvc.exe process.
- If the BTSNTSvc.exe process is not available, check
- That the host instance is started
- That you have checked Show Processes from All Users and Show Processes in All Sessions
If multiple instances of the BTSNTSvc.exe process are running, you can stop the host instance that will be executing the map and notice what PIDs are active and then start the host instance. The new PID will be the one to attach to. Another option is to attach to all the BTSNTSvc.exe processes.
- Click Attach.
- Send a message through BizTalk
This causes BizTalk to load the functoid at runtime, and your debugging session breaks the runtime when one of your breakpoints is hit, allowing you to step through the code to debug your component.
DebugView
A third way of debugging your custom referenced functoid is to leverage the System.Diagnostics namespace, which contains classes called Debug and Trace. In your code, you can insert statements that leverage these classes to write out either trace or debug statements, which are then viewable by tools like DebugView, which you can download from Microsoft’s home page.
Listing 3.19 shows statements for leveraging the Debug and Trace.
Listing 3.19. Leveraging the Debug and Trace Classes for Debugging
public string Replace(string str, string search, string replace) { Trace.WriteLine("Replace method of \"String Replace\" functoid was called."); Debug.WriteLine("Parameter str: " + str); Debug.WriteLine("Parameter search: " + search); Debug.WriteLine("Parameter replace: " + replace); if (String.IsNullOrEmpty(str)) { Debug.WriteLine("First input was null or empty. Returning empty string."); return String.Empty; } if (search == null) { Debug.WriteLine("Second parameter was null. Returning first parameter."); return str; } Trace.WriteLine("Replace method of \"String Replace\" functoid has ended."); str = str.Replace(search, replace); Trace.WriteLine("Replace method will return " + str); return str; }
Debugging Inline Functoids
For inline functoids, you do not have the option of attaching to a process and setting breakpoints allowing you to step through your code because the code is inline in the map and doesn’t use the assembly you compile.
Also, the option of using the System.Diagnostics namespace and leveraging the Debug and Trace classes will not work because the System.Diagnostics namespace is not one of the namespaces you can access from inline scripts.
When developing an inline functoid, it is impossible to know whether the script that is encoded in the string you output is actually a valid method that can compile. Therefore, it is often easiest to either develop your functoid as a referenced functoid or to develop the functionality needed in a separate assembly. Either way, you can debug that as mentioned earlier in the section about debugging custom referenced functoids, and once the functionality is as you want it to be, you can create a method that wraps the entire method in a string.