The Context Accessor functoid, part II

by eliasen 15. April 2009 22:11

Hi all

I had a post about one of the context accessor functoids which can be seen here: http://blog.eliasen.dk/2009/04/01/TheContextAccessorFunctoidPartI.aspx

This post is about the other one – the one that can only be used in a map that is used in a receive port.

Basically, the functoid takes in three inputs:

GetReceivedFilename

The first is the name of the property and the second parameter is the namespace of the property schema this property belongs to. The third parameter is an optional string that is returned in case the promoted property could not be read.

This functoid only works in a map that is called in a receive port and only if the receive location uses a pipeline that uses the ContextAccessorProvider pipeline component that is included in he same DLL as the functoids.

What the pipeline component does is, that it takes the context of the incoming message and saves it in a public static member. This way, the functoid can access this static member of the pipeline component and read the promoted properties this way.

Good luck using it.

--
eliasen

Tags:

Promoting reoccurring elements – Part II

by eliasen 13. April 2009 23:22

Hi all

I had a post about promoting reoccurring elements in BizTalk. As we all know, this isn’t possible…. BUT… actually… well… :-)

Basically, you promote properties for three reasons:

  1. You need it to route based on the value
  2. You need it for correlation (which is basically just a specialization of routing)
  3. You need to either read or set the value inside an orchestration (Don’t do that! Use distinguished fields instead)

So, dealing with number 1, routing, I started thinking back at BizTalk 2002, which was my first BizTalk experience (I’l bet ALL BizTalk developers/architects think of BizTalk 2000/2002 every now and then… no? :-) ). I seemed to recall that you could do something fancy with routing back then, so I fired up an old BizTalk 2002 on windows 2000 Professional to test it. It turns out, that on a channel you can enter a filter which is an XPath expression. The text field is editable, so you can change the XPath expression all you want – it will complain if you try to leave it with an invalid XPath expression (syntactically – not semantically).

Given this XML:

<MyRoot>
  <myReoccuringRecord Myfield="42" /> 
  <myReoccuringRecord Myfield="2" />
  <MySecondRecord MySecondField="jan" /> 
  <MyThirdRecord>
    <MyThirdField>1</MyThirdField> 
  </MyThirdRecord>
</MyRoot>

I can have a channel with this filter:

  • /*[local-name()='MyRoot' and namespace-uri()='']/*[local-name()='myReoccuringRecord' and namespace-uri()=''][position()=1 and @Myfield = 42]

Basically, documents will only go through this channel, if the value of the “Myfield” attribute of the first “myReoccuringRecord” element has the value 42.

So, it isn’t promoting as such – BizTalk 2002 doesn’t have this concept, but it allows us to route based on the value of a specific occurrence  of a reoccurring element.

On a side note; If you leave the filter like this:

  • /*[local-name()='MyRoot' and namespace-uri()='']/*[local-name()='myReoccuringRecord' and namespace-uri()=''][@Myfield = 42]

it will accept the incoming document no matter what the position of the “myReoccuringReord” is. Can’t make up my mind if this is a good thing or not :-)

Now, as we all know (?), manually editing the XSD in BizTalk 2006 solution to make sure the XPath evaluation will only return a single XmlNode doesn’t work. You either get a compile time error:

  • The promoted property field or one of its parents has Max Occurs greater than 1. Only nodes that are guaranteed to be unique can be promoted as property fields.

or you get a runtime error:

  • There was a failure executing the receive pipeline: "Microsoft.BizTalk.DefaultPipelines.XMLReceive, Microsoft.BizTalk.DefaultPipelines, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" Source: "XML disassembler" Receive Port: "ReceivePort3" URI: "C:\Projects\Blog Entries\PrommotingReoccurringElementsEditXSD\Instances\In\*.xml" Reason: Unexpected XPath format:

Anyway… not that many BizTalk 2000/2002 installations till in production, I assume, so this post is merely for informational purposes. It’s just funny discovering functionality that is doable in earlier versions of BizTalk and not in the latest versions.

--
eliasen

Tags:

Wishes for the next version of BizTalk

by eliasen 13. April 2009 20:58

Hi all

Well, the time has once again come for a “If I had all the money in the world to pay to the BizTalk development team and I could decide what to put into the next version of BizTalk, this would be it”-list :-) There are a couple of items that I have stolen from http://blog.eliasen.dk/2007/10/06/BizTalkVNextWishlist.aspx.

  1. Orchestration debugging should be easier. I really hate that I have to deploy my orchestration, throw a message through it just to get an instance in HAT that I an set breakpoints on and then have to throw another message trough the orchestration to debug it.
  2. No more pipelines. Why no let the developer have an inline pipeline designer in a receive location and a send port? this way I don’t have to build custom pipelines – I can just chose pipeline components right on the receive locations and send ports.
  3. For development purposes, it would be really nice to be able to right click a receive location that is disabled and choose "Execute". If for instance I have a SQL adapter receive location that is supposed to poll every minute, then I don't want to have to quickly disable the receive location once it has been fired. I want to keep it disabled, so data wont go through my system when I am not ready, and then just execute it whenever I am ready.
  4. Restart Host Instances only once. Right now, if I deploy my solution from VS.NET, and this solution has 10 projects that are all set to "Restart Host Instances" on deployment, then the host instances will get restarted 10 times. Would be nice if VS.NET could figure this out and only do it once.
  5. Specify the node that is body, when using enveloping and not just the parent. It makes great sense, that I can specify a node and all child elements are then submitted as separate messages from the receive pipeline. This is how we can receive orders, invoices, etc. in the same XML. BUT, if I receive XML where I only need the Orders, then I would like to point at the Orders element so that is all I get. Right now I have to use standard enveloping, and implement logic to just delete the invoices, etc. Not really nice, I think.
  6. Better modeling tools for business processes. The last Gartners quadrant I saw on BMPS didn’t have Microsoft mentioned at all. Would be great with some formal cooperation with IDScheers Aris product or the like. Something like simulating your business process with different values in rules would be just great
  7. Promoting via XPath. Please let me specify the XPath for some element that I want promoted in the disassembler components. If I know of an XPath that guarantees that only one value is in the result set og evaluating that XPath, then let me use it instead of forcing me to not promote anything that can occur multiple times. I am aware that with the current limitation, BizTalk is helping developers to not make mistakes, but hy not give us the opportunity and then suspend the message if the developer was wrong and multiple elements are in the resulting node list after evaluating the XPath expression?
  8. It would be nice if we could expose a web service without having to require a parameter. If I want to expose an orchestration that builds an inventory XML hat customers can request, I really don’t need a parameter to do that. Some workaround would be nice.

--
eliasen

Tags:

BizTalk 2009 available

by eliasen 7. April 2009 20:50

Hi all

Some may have noticed it – others will know it now :-)

BizTalk 2009 is available for download from MSDN.

the official BizTalk site doesn’t reflect the new version yet, though, so please have patience if you need the trial version or the likes.

--
eliasen

Tags:

The Context Accessor functoid, part I

by eliasen 1. April 2009 22:54

Hi all

At http://social.msdn.microsoft.com/Forums/en-US/biztalkgeneral/thread/ecaa2059-d78e-449f-8eb0-37696847b4b0 there is a guy who is struggling to get the MessageID into a message inside a map.

I have been trying to help him and have come to a stand still, because I missed a very important point. The MessageID is “written” and not promoted to the context base. Therefore, it cannot be read using the functoid found at http://www.codeplex.com/ContextAccessor (The one used inside an orchestration).

So basically, you can use the functoid to get hold of all the properties that were promoted into the context, but the ones that were written, you need to get to some other way.

I have created a small sample that illustrates how to do this inside an orchestration.

It can be downloaded here.

Basically, I have a schema for the input:

Inputschema

It has two fields, and I have promoted the first field.

The output schema looks like this:

OutputSchema

It has four fields.

What I want in the output is this:

  • Field1: The value from Field1 in the source schema. It isn’t to be mapped directly, though. I want to demonstrate that the functoid can be used to get values that were promoted from a schema.
  • Field2: The value from Field2 in the source schema. Mapped directly
  • MessageID: The Message ID of he input message. Since this isn’t accessible by the functoid, I will use a Message Assignment shape to do that
  • ReceivedFileName: Will contain the content of the BTS.ReceivedFileName poperty – by using the functoid.

The map looks like this:

Map

I am using the ContextAccessor (for orchestations) to fill a value into Field1 and ReceivedFileName. I am mapping Field2 directly. The MessageID field I am not mapping, since I need a message assignment shape for that. The element “MessageID” must be present in the output, though. Otherwise i cannot fill in value. So, as you can see in the screenshot, I have set the “Value” property to “<empty>”. This will create an empty element. This feature is very handy for initializing elements that will later on get values from message assignment shapes but also for creating the needed empty fields for demotion.

Anyway, the parameters for the first functoid look like this:

FirstFunctoid

The parameters are:

  1. The name of the message to get the value from.
  2. The name of the property
  3. The name space of the property schema that property exists in.

The second functoid looks like this:

SecondFunctoid

Now, the final touch is the orchestration:

Orchestration

Quite simple… A receive, a construct and a send port for the output. The construct shape has two shapes inside it. The first is a transformation shape hat will execute the map. The second is a message assignment shape hat will insert the messageid into the destination schema.

The message assignment shape looks like this:

MesageAssignment

I made the MessageID field of he output a distinguished field and can therefore insert values into it. This is quite clever . the way you can do several things to a message as long as you are still inside the same Construct Message shape. After the map, where I let the MessageID field be empty, I can insert a value into this field using a distinguished field. I could also just as easily have used XPath to do that, but that is not as readable, so I didn’t do it.

So… This was the first post of two – next time I will look at the receive port version of the Context Accessor functoid.

And one last point: If the idea i just to get an Id into a message and it doesn’t have to be the message ID of a message, you can also just use the “New GUID” functoid found at http://eebiztalkfunctoids.codeplex.com/

--
eliasen

Tags:

Preserving white space in BizTalk map

by eliasen 31. March 2009 21:05

Hi all

Alister Whitford has a question today on the online forums about preserving white space in a map. He thought that the functionality has changed between BizTalk 2006 and BizTalk 2006 R2. He has done a great job looking into stuff and it appears he is right. You can check out he thread here: http://social.msdn.microsoft.com/Forums/en-US/biztalkgeneral/thread/7dd28a9b-16b5-4c0e-90db-843caf4689ee where he also shows how not to preserve white space in R2 and thus have the same functionality as in 2006 (non-R2).

Hope this helps someone

--
eliasen

Tags:

“Weird” subscription when dealing with no subscribers found

by eliasen 31. March 2009 20:48

Hi all

Disclaimer: I do NOT encourage the usage of he information in this blog post. The post is merely about some silly experiment, he results thereof and a conclusion on it.

So, this friend of mine (and former colleague) mentions every now and then that he isn’t all that sure that the “No subscribers found” should be an error but maybe more a warning. His man argument is, that if I have two subscribers for something, say all incoming orders are put into an archive file drop and also sent to the ERP system, and the send port that sends to the ERP system is unenlisted, then no errors will occur in BizTalk, but from a business point of view, the system is definitely not working. So the fact that you don’t get the error that indicates something is wrong with routing is not actually very useful, because parts of the system may be down after all.

Anyway, we were discussing what to do about this in case you just don’t want that error to occur if no subscribers were found. We came up with two options:

  1. Add a send port that uses Tomas Restrepos /dev/null adapter. you can find it at http://winterdom.com/dev/bts/index.html – look for “BizTalk 2006 R2 Null Send Adapter”. Using this adapter in the send port will cause everything going through the port to magically disappear.
  2. Mostly for fun we came up with the idea to have an orchestration that only as one receive shape. This receive shape should receive a message of type System.Xml.XmlDocument – since this will let the orchestration receive any message types. Also, it would have to be a direct bound port, so the orhestration would get ALL messages that are published to the MessageBox, so we would never get the “No subscribers found” error. Now, naturally, this solution is extremely silly, since we would fire up an orchestration for all published messages. But we started thinking what the subscription would look like.

The rest of this post is to explore item 2 above to find out how the subscription would look like.

To do this, I created four scenarios – just to explain it to you.

The four scenarios are:

  1. An orchestration that receives a message of type ReceiveSchema.xsd and is linked to a “Specify Later” port. This is the normal and widely used scenario.
  2. An orchestration that receives a message of type System.Xml.XmlDocument from a “Specify Later” port. The common way of receiving binary files or any file without caring about what files they are.
  3. An orchestration that receives a message of type ReceiveSchema.xsd and is linked to a direct bound port. This is the common way to receive ALL published orders, no matter what receive port or orchestration they were published from.
  4. An orchestration that received a message of type System.Xml.XmlDocument and is linked to a direct bound port. This is not something I have ever seen used, but this is what I want to find out about :-)

So, to summon up the subscriptions:

Scenario Subsription Description
1

http://schemas.microsoft.com/BizTalk/2003/system-properties.ReceivePortID == {C464C9C6-F4BB-4ADF-9322-B2E89E6C8885}  And
http://schemas.microsoft.com/BizTalk/2003/system-properties.MessageType == http://ReceiveEverything.ReceiveSchema#ReceiveSchemaRoot

This is the most common subscription. It consists of both a ReceivePortID (Because the logical port is bound to a physical port) and the message type (Because I am using a strongly typed message).
2 http://schemas.microsoft.com/BizTalk/2003/system-properties.ReceivePortID == {C464C9C6-F4BB-4ADF-9322-B2E89E6C8885} This subscription is partly like the first one. The ReceivePortID part is the same, but no message type is specified. This is because I am using System.Xml.XmlDocument as message type, and this is just a “catch all” message type.
3 http://schemas.microsoft.com/BizTalk/2003/system-properties.MessageType == http://ReceiveEverything.ReceiveSchema#ReceiveSchemaRoot This subscription has the part of the first subscription that was missing from the second one and it doesn’t have the part that was actually in the second subscription. This is because I am now using a direct bound port, and therefore the port ID becomes irrelevant in the subscription. I am using a strongly typed message, though, so the message type is relevant.
4   Surprised? A completely empty subscription. Kind of makes sense, when you think about it, since we are using a direct port, so the port ID is irrelevant and we are using a untyped message, making the message type irrelevant.

Now then… As I wrote, it makes sense, but it wasn’t what I was expecting, actually. With this empty subscription, I STILL get the “No subscribers found” error when I pick up a message and publish it into the Message Box.

So instead of doing this, I started thinking about what else to do. So I created a receive port with a receive location and let a message go through it and get suspended. Looking at the details of the context of the message that was suspended I gor this:

ReceiveLocationSuspended

So… I need a subscription that is not empty, but that will make sure my orchestration gets EVERYTING that is published into the MessageBox. This is done by setting the filter on he receive shape of my orchestration in scenario 4. The filter will have to include one of he above properties that is promoted. But looking at them I really don’t expect a message created inside an orchestration and then sent to the messagebox to have any of those properties set. So I decided to create a small orchestration that will simply just send a message to the messagebox. The context of the message published to the MesageBox looks like this:

OrchesrationSuspended

As you can see, no overlap at all.

So, as I see it, a filter like “BTS.ReceivePortID exists OR BTS.Operation exists” should do the trick. Now, this subscription works in my small example, but I cannot guarantee it will work for all scenarios. I can’t think of an example right now where either the ReceivePortID or the Operation doesn’t exist, but there might be examples.

So… Basically, the whole idea about having an orchestration taking in ALL published messages to avoid errors about no subscribers is REALLY silly and should not be implemented. And if you choose to do it anyway, please remember that the above filter isn’t guaranteed to work in all scenarios… I was just playing around :-)

Not sure this will ever help anyone… but there goes :-)

--
eliasen

Tags:

Combining two messages in one map

by eliasen 25. March 2009 22:26

Hi all

Someone at the online general BizTalk forum asked a question about combining two messages in a map. Now, he all ready knew about creating the map from inside an orchestration, but let me just quickly summon up for those not knowing this. If you have two messages inside an orchestration that you need to merge into one message in a map, what you do is that you drag a transformation shape into your orchestration like this:

DualInputOrchestration

In my example, I have a parallel convoy to get the two input messages into my orchestration. I then have two different ways of combining the two input messages into one output, and each is then output.

Anyway, after the transform shape is dragged onto the orchestration designer, you double-click on it to choose input and output messages like this:

DualInputCreateMap

You can add as many source messages as you want – I have chosen two messages. Make sure the checkbox at the bottom is selected. Then click “OK” and the mapper will open up. It will have created an input schema for you, which is basically a root node that wraps the selected source messages. At runtime, the orchestration engine will take your messages and wrap them to match this schema and use that as input for the map.

In my case, I have these two schemas:

DualInputSchemaHouseBill 

and

DualInputSchemaWayBill

My output schema looks like this:

DualInputSchemaOutput

The automatically generated map looks like this:

DualInputGeneratedMap

As you can see, the destination schema is just like my output schema, but the input schema wraps my two input schemas into one schema.

So… I have just briefly explained how two create the map that can combine two messages into one. Now for the functionality inside the map.

Most maps like this can be mapped like any other complex input schema. But sometimes you need to somehow merge elements inside the source messages into one element/record in the destination. This automatically becomes different, because the values will appear in different parts of the input tree.

The requirement that was expressed by the person asking the question in the online forum was that these two inputs:

DualInputSchemaHouseBillExample

and

DualInputSchemaWayBillExample

and combine them into this:

DualInputSchemaOutputExample

So basically, there is a key that is needed to combine records in the two inputs. My schemas above are my own schemas that roughly look like the schema that was in use in the forum.

My first map that will solve the given problem looks like this:

DualInputMapFunctoids

Quite simple, actually. I use the looping functoid to create the right number of output elements, and I use the iteration and index functoids to get the corresponding values from the WayBill part of the source schema. The index funtoid can take a lot of inputs. In my case the path to the element is always the first until the vey last step, where I need to use the output of the iteration functoid. So I have only two inputs: The element that loops and the index of the parent of this element because that is the only place where I need to go to a specific element.

This works very nicely, but it has one serious drawback (and a minor one, which I will get back to later): It requires that the elements appear in the exact same order in both inputs. If this restriction can be proven valid, then this is my favorite solution, since I am a fan of using the built-in functoids over scripting functoids and custom XSLT if at all possible. I didn’t ask the person who had the issue if this restriction is valid, but thought I’d try another approach that will work around this just in case. This requires some XSLT, unfortunately, and the map looks like this:

DualInputMapXSLT

Quite simple, really :-) The scripting functoid takes care of the job for me. It is an “Inline XSLT Call Template” functoid and the script goes like this:

<xsl:template name="BuildOutput">
<xsl:param name="ID" />
<xsl:element name="Output">
<xsl:element name="Number"><xsl:value-of select="$ID" /></xsl:element>
<xsl:element name="OriginPortId"><xsl:value-of select="/*[local-name()='Root' and namespace-uri()='http://schemas.microsoft.com/BizTalk/2003/aggschema']/*[local-name()='InputMessagePart_0' and namespace-uri()='']/*[local-name()='HousebillRoot' and namespace-uri()='http://DualInput.DualSchemaHouseBillInput']/*[local-name()='HouseBillsNode' and namespace-uri()=''][HouseBillNo = $ID]/*[local-name()='OriginPortId' and namespace-uri()='']" /></xsl:element>
<xsl:element name="ShippingAddress"><xsl:value-of select="/*[local-name()='Root' and namespace-uri()='http://schemas.microsoft.com/BizTalk/2003/aggschema']/*[local-name()='InputMessagePart_1' and namespace-uri()='']/*[local-name()='WayBillRoot' and namespace-uri()='http://DualInput.DualSchemaWayBillInput']/*[local-name()='WayBillInfo' and namespace-uri()=''][WayBillNo = $ID]/*[local-name()='ShippingAddress' and namespace-uri()='']" /></xsl:element>
<xsl:element name="ContainerAddress"><xsl:value-of select="/*[local-name()='Root' and namespace-uri()='http://schemas.microsoft.com/BizTalk/2003/aggschema']/*[local-name()='InputMessagePart_1' and namespace-uri()='']/*[local-name()='WayBillRoot' and namespace-uri()='http://DualInput.DualSchemaWayBillInput']/*[local-name()='WayBillInfo' and namespace-uri()=''][WayBillNo = $ID]/*[local-name()='ContainerAddress' and namespace-uri()='']" /></xsl:element>
</xsl:element>
</xsl:template>

Now this looks complex, but really it isn’t. Let me try to shorten it for you to be more readable:

<xsl:template name="BuildOutput">
<xsl:param name="ID" />
<xsl:element name="Output">
<xsl:element name="Number"><xsl:value-of select="$ID" /></xsl:element>
<xsl:element name="OriginPortId"><xsl:value-of select="XXX/*[local-name()='HouseBillsNode' and namespace-uri()=''][HouseBillNo = $ID]/*[local-name()='OriginPortId' and namespace-uri()='']" /></xsl:element>
<xsl:element name="ShippingAddress"><xsl:value-of select="YYY/*[local-name()='WayBillInfo' and namespace-uri()=''][WayBillNo = $ID]/*[local-name()='ShippingAddress' and namespace-uri()='']" /></xsl:element>
<xsl:element name="ContainerAddress"><xsl:value-of select="YYY/*[local-name()='WayBillInfo' and namespace-uri()=''][WayBillNo = $ID]/*[local-name()='ContainerAddress' and namespace-uri()='']" /></xsl:element>
</xsl:element>
</xsl:template>

Here XXX is the XPath from the root node down to the HouseBillsNode node and YYY is the XPath from the root node down to the WayBillInfo node.

Basically, the script is fired by the map for each HouseBillNo element that appears (3 in my example) and the script will create an Output element with the HousebillNo value and i will then use the number to look up the values that correspond to the key in the other parts of the input.

There are some drawbacks to this solution as well, and I will just try to summon up the drawbacks here:

Drawbacks for first maps

  1. If the elements do not appear in the exact same order in both inputs, the map will fail.

Drawbacks for the second map

  1. The script has not been adjusted to handle optional fields. So it will create the output fields no matter if the input fields exist in the source.

Drawbacks for both maps

  1. If the HouseBill input has more elements than the other, then the output will be missing values for the elements that would get there values form the second input.
  2. If the HouseBill input has fewer elements than the other, then the output will simply not have records corresponding to these extra elements in the WayBill input.
  3. Both scenarios can be handled in the XSLT, naturally, if needed.

There are probably other drawbacks – most of them related to the fact that I was too lazy to handle all exceptions that might occur. But you should get the idea anyway :-)

The solution can be found here

.

Hope this helps some one…

--
eliasen

Tags:

My functoid library has moved

by eliasen 10. March 2009 20:36

Hi all

I decided that it was time to put my functoid library which was earlier hosted at http://www.eliasen.eu/DownloadSoftware.aspx to Codeplex.

You can find them along with the documentation at http://eebiztalkfunctoids.codeplex.com/ from now on.

Cheers

--
eliasen

Tags:

Several custom scripting functoid with the same method

by eliasen 9. March 2009 20:51

Hi all

A couple of months ago, while trying to solve the If-Then-Else issue within a map in a nice way, a fellow BizTalk MVP suggested to me that I could just have the script inside a custom scripting functoid and then copy the script from custom scripting functoid to custom scripting functoid.

Naturally (as I assumed it was, back then) I told him that this was impossible, because BizTalk wouldn't let me have two custom scripting functoids which contains the same method, ie. the two methods have the same signature.

"Sure you can", he replied.... So I had to check it out... I fired up my BizTalk 2006 R2 virtual machine, and sure enough: He was right. I was in chock, because I was totally sure i was right. so I fired up a BizTalk 2006 virtual machine and again; He was right and I was wrong.

Now... those who know me will know that when I am convinced I am right, I will usually go to great lengths to prove it :-) So I stepped down a version and tried BizTalk 2004. Again no luck.

But FINALLY, when trying it on BizTalk 2002, it turned out that I was right. Back in BizTalk 2002, scripts were in VBScript and here you can not have multiple custom scripting functoids with methods with the same signature. In versions after BizTalk 2002, the compiler will collapse those methods into one, but in BizTalk 2000 and 2002, this is not done, and therefore an error occurs.

So all in all, I discovered this limitation back in 2003 and never questioned it again since. So let this be a lesson to all (especially me, because I always think I am right :-) ) - that sometimes we need to reevaluate what we think we know.

Hopefully this post will:

  1. encourage people to reevaluate opinions and knowledge
  2. inform others like me, who thinks that you cannot have the same method signature twice in a map, that this is wrong :-)

--
eliasen

Tags:

About the author

Jan Eliasen is 37 years old, divorced and has 2 sons, Andreas (July 2004) and Emil (July 2006).

Jan has a masters degree in computer science and is currently employed at Logica Denmark as an IT architect.

Jan is a 6 times Microsoft MVP in BizTalk Server (not currently an MVP) and proud co-author of the BizTalk 2010 Unleashed book.

BizTalk Server 2010 Unleashed


Buy from Amazon

Microsoft MVP


6 times: July 2004, July 2008, July 2009, July 2010, July 2011, and July 2012. Not currently an MVP.

MCTS

Image to show

Month List

Page List