Interesting tech stuff I come across day-to-day. Mainly around Biztalk and .NET 3.0

MIME Message, SOAP Adapter, Web Service with multiple input arguments - BizTalk Messaging-Only (CBR) solution.

Thursday, December 21, 2006

In my previous post we have seen how you can call a web service which expects a single argument using SOAP adapter with a .NET proxy client in a BizTalk messaging only scenario (aka Content based routing) without using any orchestration.

In Step 4 of my previous post I explained how the IBaseMessage from Biztalk is translated into corresponding Web Service arguments in the proxy class. Basically, when we submit the sample message (CustomerInfo) via a Receive Location, BizTalk creates an IBaseMessage as shown in the below figure and submits it into the Message Box, which is then routed to the SOAP Adapter send port (based on ReceivePortName Filter). Inside the SOAP Adapter the body part of the message (CustomerInfo) identified by the content-id is assigned to the web service argument CustomerInfo. Now the .NET client proxy got all the required parameters (only one in this example)to make the call to the web service.

WebService Signature:

ProductInfo GetProductInfo (CustomerInfo customer)

IBaseMessage:

Now, coming to our second web method definition as shown below, how are we going to construct the IBaseMessage.

ProductInfo GetProductInfoByAccountNumber (CustomerInfo customer, int accountNumber)

The IBaseMessage to call the above Web Method should be in the form as shown below:

One easy way to generate the IBaseMessage shown above is by creating an orchestration, Adding a Web Reference to our web service, Assign the web method parameters as shown below inside a message assignment shape

WS_REQUEST.accountNumber = 5;
WS_REQUEST.CustomerInfo = CUSTOMER_INFO;

and link to the web port. The final Orchestration will look like the one shown below.

But, our goal is to call the web service without using any Orchestration. In order to archive this, all we need is a properly constructed IBaseMessage as shown in Figure 2. The method we are going to see here is making use of MIME Decoder pipeline component that comes out of the box and submitting a MIME message (as shown below) with multiple parts.

1. Create MIME Message

MIME-Version: 1.0
Date: Wed 20 Dec 2006 00:12:58 +0000
Content-Type: multipart/related;
boundary="---------MIME-digitaldeposit.net-------------"

-----------MIME-digitaldeposit.net-------------
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
Content-ID: CustomerInfo
Content-Description: CustomerInfo
Content-Type: text/xml;
charset="utf-8"

<CustomerInfo xmlns="http://www.digitaldeposit.net/biztalk/samples/Customer">
<CustomerID>string</CustomerID>
</CustomerInfo>

-----------MIME-digitaldeposit.net-------------
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
Content-ID: accountNumber
Content-Description: accountNumber
Content-Type: text/xml;
charset="utf-8"

<?xml version="1.0"?>
<int>5</int>

-----------MIME-digitaldeposit.net---------------

The MIME message shown above is quite straight forward, it has two parts. The first part contains our first web method argument CustomerInfo  and the second part contains our second web method argument accountNumber. The key aspect here is the Content-ID MIME header, which is used to identify the part and assign in to the web method argument in the SOAP Adapter.

2. Create a custom BizTalk Receive Pipeline with MIME Decoder.

Create a custom receive pipeline, drop the MIME/SMIME decoder component in the Decode stage (there is no settings to change). Build and Deploy it .

When the mime message is passed through the pipeline line eventually through the MIME/SMIME decoded a IBaseMessage (Multipart) will be constructed equivalent to the one shown in Figure 2, which is required for our web service call.

3. Configure the BizTalk Solution:

Now configure the BizTalk solutions as shown in the below figure. Refer to previous post for details on each port configuration.

We need to make two changes here, when compared to the previous post. On the Receive side change the pipeline from PassThru to MimeDecoder (our custom one), and select the appropriate web method on the SOAP Adapter configuration.

Download the sample solution here which got all the required files to support this blog post.

So, How did I construct the MIME Message

Unfortunately there is no support in .NET to create MIME messages, I created the MIME message manually in Notepad, its not hard as you can see.

Conclusion:

The only change we made in comparison to our previous post is changed the receive pipeline and changed the message to MIME message. This sample helps to understand 2 important things.

1. How to call a Web Service with the help of a MIME message and BizTalk

2. Also, how to call a web service without using an orchestration.

Nandri!

Saravana




BizTalk 2006 SOAP Adapter (Web Service call) in Messaging Only Scenario.

Wednesday, December 20, 2006

Web Services support in BizTalk is not a new topic, it's already mentioned heavily in few of the famous blogs like Darren Jefford , Aaron Skonnard, Biztalk Server Team Blog etc

In this entry I'll explain how you can use the out of the box SOAP adapter to call a web service in a messaging only scenario (aka Content based routing),  BizTalk 2004 SOAP adapter didn't have this functionality, we need to use orchestrations along with SOAP adapters to call a Web Service. There is a MSDN sample called "Consuming Web Services (BizTalk Server Sample)" found under Samples, which show cases SOAP adapters messaging only capability, but it fails to explain in detail how its archived.

The below figure shows the biztalk solution we are going to implement in this blog post.

SOAP Adapter Messaing-only scenario

1. Web Service Definition:

For this sample we'll define a simple web service with 2 web methods (sample download contains full implementation)

  1. ProductInfo GetProductInfo (CustomerInfo customer)
  2. ProductInfo GetProductInfoByAccountNumber (CustomerInfo customer, int accountNumber)

The below class diagram explains the custom in and out parameter (CustomerInfo and ProductInfo) data structure.

2. Create .NET Web Service Proxy Class:

Create a new Class Library project within Visual Studio, and Add Web Reference to the web service we created in Step 1. If you are using the sample application the url will be

http://localhost:11258/SOAPMessagingOnly.WebService/CustomerProcessing.asmx

We don't need to add any custom coding to the proxy class, build the solution and deploy the assembly in GAC (you need to add strong name key to solution to do it).

3. Create Ports in Biztalk:

For this demonstration we are going to create 3 ports in BizTalk.

  1. MSG_File_Receive- File Receive
    1. Name : MSG_File_Receive
    2. Adapter: FILE
    3. Direction: One-Way
    4. Receive Pipeline: PassThru
    5. URI: <<Folder location where you are going to drop the file>>
  2. MSG_File_Send - File Send
    1. Name : MSG_File_Send 
    2. Adapter: FILE
    3. Direction: One-Way
    4. Send Pipeline: PassThru
    5. Filter: BTS.SPName = WS_SOAP_Send
    6. URI: <<Folder location where you want the web service response messages>>
  3. WS_SOAP_Send - SOAP Send
    1. Name : WS_SOAP_Send
    2. Adapter: SOAP
    3. Direction: Solicit-Response
    4. Receive Pipeline: PassThru
    5. Send Pipeline: PassThru
    6. Filter: BTS.ReceivePortName=MSG_File_Receive
    7. On the SOAP Adapter Config Screen Set:

Web Service URL:

http://localhost:11258/SOAPMessagingOnly.WebService/CustomerProcessing.asmx

Assembly Name: Pick the .dll of the web service proxy we created in step 2.

Type Name: Fully Qualified name of CustomerProcessing

Method Name: GetProductInfo

(Once after creating all the ports, enlist and start the send ports and enable Receive Location.)

Key things to Note:

  • We didn't deploy any schemas into Biztalk
  • All the pipeline are "PassThru" in our settings.
  • Messages are routed (Filter Conditions) purely based on promoted properties, in our case just port names.

4. Drop the Sample Message in the Receive Location Folder:
This is one of the confusing bit, if you browse to the asmx page in Internet explorer and see the request message for GetProductInfo Web method, the SOAP:Body element will have the XML content as shown below.


<GetProductInfo xmlns="http://www.digitaldeposit.net/biztalk/samples">
<CustomerInfo xmlns="http://www.digitaldeposit.net/biztalk/samples/Customer">
<CustomerID>string</CustomerID>
</CustomerInfo>
</GetProductInfo>


People often try to post the whole message to the SOAP adapter, thinking it will be transmitted as it's. But the reality is Biztalk will submit the IBaseMessage (Multipart message) to the SOAP Adapter, then the SOAP adapter will pick up the part inside the IBaseMessage and assign it to the web service argument based on the content-id of the part. If we try to pass the whole message, then there will be a type mismatch. So all we need to pass is

<CustomerInfo xmlns="http://www.digitaldeposit.net/biztalk/samples/Customer">
<CustomerID>string</CustomerID>
</CustomerInfo>

Interesting Exception message:

Ok, just a quick quiz, can you suggest me some solution based on the following exception.

The messaging engine was unable to resubmit a message for send port "WS_SOAP_Send" with URL

http://localhost:11258/SOAPMessagingOnly.WebService/CustomerProcessing.asmx.

Details:"Cannot access a disposed object. Object name: 'CEventingReadStream'.".

I tried googling it, couldn't find any search results. After a bit of experiment found out, its the problem with the namespace I specified in the sample message. Its one of the bizarre error messages I came across.

You can replicate this exception by pasting the sample file "Namespace Error Customer.xml" found under MSG_SAMLES (from the download) folder in the receive location folder.

 So what happened to the second Web Method with 2 arguments?

As explained in step 4. I need to construct a Multipart message with two parts, first part will contain "customer" argument and the second part will have "accountNumber" argument. It's still achievable by the traditional orchestration approach. In part 2 of the article I'll explain in detail one of the approaches I took to solve this problem in a messaging only scenario without Orchestration.

Download the sample here and follow the steps below.

  1. Create a folder called BTSSample under C: and extract all the files.
  2. Open the solution in Visual Studio, build it, copy the proxy client dll to GAC, and view CustomerProcessing.asmx in the browser, this will kick off ASP .NET web server with port 11258.
  3. Create a Biztalk 2006 Application called SOAPMessagingOnly and import the binding file binding.xml.
  4. Drop the sample file (placed inside MSG_SAMPLES folder) into the folder MSG_IN.

Nandri!

Saravana

Labels:




"Schema Detector" tool for Biztalk Development

Tuesday, December 19, 2006

How often have you seen error messages like this

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:\BTSSamples\SOAPMessagingOnly\FileDrops\MSG_IN\ *.xml" Reason: Cannot locate document specification because multiple schemas matched the message type "http://Microsoft.Samples.BizTalk.WebServices.Consuming.SampleWebService/ Customers.xsd#CustomerInfo".

If you are working with multiple applications in Biztalk at the same time, its quite often you'll end up in situation like this in your development machine. In situations like this I used to open Biztalk Applications one by one inside the Biztalk Administration console (2006), navigate to Schema node and look for any matching schemas with same RootName and TargetNamespace, after a while it became so annoying, so I build this small application called "Schema Detector", its a .NET winforms application which utilizes Biztalk ExplorerOM to loop through each deployed schemas in the management database, if a specified schema with RootName and TargetNamespaces matches, it then list the schema as shown in the below figure.

You can now easily detect the duplicate deployment of the schemas in the Applications, as highlighed in the above picture.

Download the source code for the tool here. Open it in visual studio and compile it. Didn't provide the exe for security reasons.

Labels:




GAC your assemblies with one click!!

We BizTalk developers often get frustrated with this assembly GACking stuff. Here is a cool registry setting which will make our life little bit easier.

Copy and paste the code into a notepad file and save it with extension .reg. Double click on the file and its all done.

-------------------------------------------------------

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\dllfile\shell\GAC-It\command]
@="c:\\windows\\Microsoft.NET\\Framework\\v1.1.4322\\gacutil.exe /i \"%1\""

-------------------------------------------------------

Then open explorer and navigate to your dll, right click on it, for your surprise you'll see a shortcut called GAC-It as shown below. You can put whatever name you want as highlighted in RED in the above script.

Found it from here

Labels:




Message Debatching inside Biztalk Orchestration, with TargetNamespace.

Monday, December 18, 2006

Recently I end up in a scenario where I need to debatch a message inside the orchestration to produce multiple messages based on XPATH. There are already quite few articles about it, one good one is from Stephen Thomas hands on lab.

Only problem with this lab is, the examples are based on schemas without any TargetNamespace. In a real world situation its hard to imagine a schema without a TargetNamespace, so I spend some time to enhance the sample with TargetNamespace. I'll highlight some of the things you need to take care when using schemas with TargetNamespace.

Download: The attached zip file contains the complete BizTalk Solution. follow the steps below to configure it:

1. Extract the sample BizTalkDebatching to C:\BTSSamples

2. Open the project in visual studio, build and deploy it. (Don't need to create the ports, since early binding is used)

3. Open Biztalk Administration console and set the appropriate host instance for Orchestration

4. Drop the sample file (found under BizTalkDebatching\FileDrops\Sample File) into the folder FileDrops\MD_In

5. You should see 10 messages under MD_Out\After Mapping and 10 messages under  MD_Out\Before Mapping

Its quite straight forward to understand, if you have any doubts, read this article for explanation.

1. Set TargetNamespace

Make sure all your schemas has appropriate TargetNamespace, in general its very important to have a TargetNamespace, because BizTalk identifies the message based on the combination of TargetNamespace#RootElement combination.

2. We need to include namespaces in the XPath.

Any XPATH we use inside the orchestration must be fully qualified with the local name and namespace uri (TargetNamespace we set in the schemas) as shown below.

nRecordCount = System.Convert.ToInt32(xpath(Input, "count(/*[local-name()='EnvelopeData' and namespace-uri()='http://www.digitaldeposit.net/samples/schemas']/*[local-name()='Data' and namespace-uri()='http://www.digitaldeposit.net/samples/schemas'])"));

3. When looping through the nodes, we need to use xpaths node-set function position() .

When we are extracting the sub message from the envelope message we'll be extracting it based on the position inside a loop. We can make use of XPATH Node-set function position() to get to the sub message as shown below

sXPath = System.String.Format("/*[local-name()='EnvelopeData' and namespace-uri()='http://www.digitaldeposit.net/samples/schemas']/*[local-name()='Data' and namespace-uri()='http://www.digitaldeposit.net/samples/schemas' and position()={0}]", nLoopCount);

4. Set Schemas "ElementFormDefault" property to Qualified (default is unqualified)

When elementFormDefault is set to qualified, it implies that all the elements must be explicitly qualified, either by using a prefix or setting a {default namespace}. An unqualified setting means that only the globally declared elements must be explicitly qualified, and the locally declared elements must not be qualified. Unfortunately the default setting for elementFormDefault is unqualified.

If you don't set the elementFormDefault value to Qualified the output will be as shown below without any values.

<?xml version="1.0" encoding="utf-8"?>
<ns0:MappedData xmlns:ns0="
http://www.digitaldeposit.net/samples/schemas">
<Id></Id>
<Company></Company>
<FName></FName>
<LName></LName>
<OrderId></OrderId>
<RecDate></RecDate>
<ShipDate></ShipDate>
</ns0:MappedData>

Once after setting the elementFormDefault to Qualifed, you'll get the required output.

<?xml version="1.0" encoding="utf-8"?>
<ns0:MappedData xmlns:ns0="
http://www.digitaldeposit.net/samples/schemas">
<ns0:Id>3</ns0:Id>
<ns0:Company>City Power And Light</ns0:Company>
<ns0:FName>Greg</ns0:FName>
<ns0:LName>Chapman</ns0:LName>
<ns0:OrderId>12031-ABC-0001</ns0:OrderId>
<ns0:RecDate>5/15/2005</ns0:RecDate>
<ns0:ShipDate>5/17/2005</ns0:ShipDate>
</ns0:MappedData>

Labels: