Thursday, November 05, 2009

Use of generic types in BizTalk and XLANG/s

This is just a quick post to note down something that I have been explaining to people lately. The project I am working on at the moment has several teams of developers working together and most of the code is being cut in Visual Studio 2008 / .Net 3.5. As such, we in the BizTalk feature team are taking delivery of components coded in other teams and integrating them. We are using BizTalk 2006 R2 and developing the BizTalk orchestrations in Visual Studio 2005, although there are no compatability issues between the binaries developed in 2008 and 2005, and anyway generic types were introduced in 2005 / .Net 2.0.

However, where I have come across an issue is in the use of generic types, and specifically assigning generic types to orchestration variables. In some cases orchestrations and expression shapes support generics and in some cases they do not.

All I am going to do is to illustrate some cases where generics are OK and some where they are not, with a bit of explanation as to why.

Assigning to variables

Imagine that you have a class:

public class MyClass {}

And you want to use a collection of this class in your code, these days you would usually use a generic collection in our code:

Collection collection = new Collection<MyClass>();

However, if you try to assign this type to a variable in an orchestration you will find that you can't. This is because you can only pick from a type that is compiled, and generic types aren't in there. If you want to use a collection like this in an orchestration you will have to create a type for it. You can define a new class:

public class MyClassCollection : Collection<MyClass> {}

If you do this, you can reference "MyClassCollection" as a variable in your orchestration and everything will be fine. Note that the usual rules about classes being marked as serializable apply.

The same applies if you create a class that has a generic interface such as this:

namespace AndrewGenerics.Components
{
[
Serializable]
public class OrchestrationInstanceHelper<T>
{
public void UpdateInstance(T objectInstance)
{
// Some code in here
}
}
}

If you see this class in the type picker and try to assign it to a variable you will be able to pick it:

However you will not be able to assign the generic type:


As a result if you try to compile you will get a whole heap of errors like these:

Error 1 '` (0x60)': character cannot appear in an identifier C:\Projects\AndrewGenerics\AndrewGenerics.BizTalk\TestOrchestration.odx 62 63
Error 2 '` (0x60)': character cannot appear in an identifier C:\Projects\AndrewGenerics\AndrewGenerics.BizTalk\TestOrchestration.odx 67 80
Error 3 identifier 'OrchestrationInstanceHelper' does not exist in 'AndrewGenerics.Components'; are you missing an assembly reference? C:\Projects\AndrewGenerics\AndrewGenerics.BizTalk\TestOrchestration.odx 62 36
Error 4 cannot find symbol 'AndrewGenerics.Components.OrchestrationInstanceHelper' C:\Projects\AndrewGenerics\AndrewGenerics.BizTalk\TestOrchestration.odx 62 36
Error 5 expected 'identifier' C:\Projects\AndrewGenerics\AndrewGenerics.BizTalk\TestOrchestration.odx 62 64
Error 6 unexpected token: 'numeric-literal' C:\Projects\AndrewGenerics\AndrewGenerics.BizTalk\TestOrchestration.odx 62 64
Error 7 identifier 'helper' does not exist in 'TestOrchestration'; are you missing an assembly reference? C:\Projects\AndrewGenerics\AndrewGenerics.BizTalk\TestOrchestration.odx 67 14
Error 8 cannot find symbol 'helper' C:\Projects\AndrewGenerics\AndrewGenerics.BizTalk\TestOrchestration.odx 67 14
Error 9 identifier 'OrchestrationInstanceHelper' does not exist in 'AndrewGenerics.Components'; are you missing an assembly reference? C:\Projects\AndrewGenerics\AndrewGenerics.BizTalk\TestOrchestration.odx 67 53
Error 10 cannot find symbol 'AndrewGenerics.Components.OrchestrationInstanceHelper' C:\Projects\AndrewGenerics\AndrewGenerics.BizTalk\TestOrchestration.odx 67 53
Error 11 'new OrchestrationInstanceHelper': a new expression requires () after type C:\Projects\AndrewGenerics\AndrewGenerics.BizTalk\TestOrchestration.odx 67 23
Error 12 expected 'identifier' C:\Projects\AndrewGenerics\AndrewGenerics.BizTalk\TestOrchestration.odx 67 82
Error 13 unexpected token: '(' C:\Projects\AndrewGenerics\AndrewGenerics.BizTalk\TestOrchestration.odx 67 82
Error 14 illegal statement '1' C:\Projects\AndrewGenerics\AndrewGenerics.BizTalk\TestOrchestration.odx 67 81

All those errors are because of a couple of things, but mainly because when you add a variable to an orchestration, or add code in an expression shape, the designer writes some C# code for you and then compiles it. Because the way that the designer handles generics is incorrect the C# doesn't compile and you get the build errors.

Again though, if you created a class that inherited from the above class and assigned the generic type you'd be OK:

public class MyClassInstanceHelper : OrchestrationInstanceHelper<MyClass> {}

Passing parameters to methods

OK. Let's now create a helper component that we will call from an orchestration. [Disclaimer: The code below if for illustration purposes only!]

namespace
AndrewGenerics.Components

{
public static class OrchestrationHelper
{

// Note that this uses the collection type
public static void UpdateItemsInCollection(MyClassCollection collection)
{
// Some code in here
}

// Note that this uses the generic type
public static void UpdateItemsInCollection2(IEnumerable<MyClass> collection)
{
// Some code in here }
}
}

The first of these calls uses the inherited type and so will go through no problem:

AndrewGenerics.Components.OrchestrationHelper.UpdateItemsInCollection(myCollection);

But even if we use the second of the method calls with the IEnumerable<MyClass> parameter, that still works OK:

AndrewGenerics.Components.OrchestrationHelper.UpdateItemsInCollection2(myCollection);

Therefore, even though the method in a helper component has a generic type in the code, when the class is compiled the generic type gets "baked" into the interface and so the variable assignment works.

Assigning output from methods to variables

Now let's add another method onto the helper class that we can use to receive a collection of objects, but again through a generic type.

public static IEnumerable<MyClass> GetCollection()
{
Collection<MyClass> coll = new Collection<MyClass>();

coll.Add(
new MyClass());

return coll;
}

In order to call this I might use a line of code like this in an expression shape:

myCollection = (AndrewGenerics.Components.MyClassCollection)AndrewGenerics.Components.OrchestrationHelper.GetCollection();

Again, this is handled OK by the BizTalk compiler when we are assigning to our collection and the we are casting the type of the result. For the same reasons as above, we can't create a variable of IEnumerable<MyClass;> so we can't receive the output of this method without casting it, but we can at least handle it. Obviously, if at runtime we were presented with a different object that conformed to IEnumerable<MyClass;> (such as an array) we would get a runtime error because of an invalid cast.

However, we have been able to declare a method that uses a generic type, and for the same reason we can use it, i.e. when the helper component is compiled the method signature becomes fixed and the orchestration compiler can handle the output.

Conclusions

Here are some key points from this blog post:

  1. You cannot declare generic type in an orchestration because you can only select types in the type picker. If you select a generic interface that does not have the type assigned you get some crazy errors.
  2. In order to use a generic type you can create a class that implements the generic type. This will then be usable by BizTalk.
  3. When a helper component uses generic types in the interface the types get baked into the interface and can be used by BizTalk.

1 comment:

Benjy said...

Nice one Andrew. A little "pattlet" for using Generics with orchestrations. Also nice that the project has got you blogging again.