Create the Suspended Message Handler Service:
The following steps are used to create the Windows service that will poll for suspended messages:
Start Microsoft Visual Studio 2005. On the File menu, point to New, and then click Project. Click Visual Basic Projects under Project Types, and then click Windows Service under Templates. Type BizTalkSuspendedMessageHandlerService in the Name text box. Change location if necessary. Click OK. In the Code Editor window, right-click Design View, and then click Properties. In the Properties pane, click the Add Installer link. Change the display name to BizTalk Suspended Message Handler Service. In the Properties pane for serviceInstaller1, change the ServiceName property to Service1. NOTE
The ServiceName property needs to match the name of the service class.
Change StartType to Automatic. In the Code Editor window in Design view, click serviceProcessInstaller1.
Note that the account
is set to User. When the service is installed, it will need to be set to
an account that has access to the BizTalk resources.
Add a Configuration File:
Right-click the project and choose Add New Item. Double-click Application Configuration File in the right-hand pane. Between the configuration tags paste the following XML: <appSettings> <add key="SuspendedMessagesTempFileLocation" value="MyDrive:\MyFolder" /> <add key="SuspendedMessagesFileLocation" value="MyDrive:\MyFolder" /> <add key="ProcessingInstruction" value="MyInfoPathProcessingInstruction"/> </appSettings>
Replace "MyDrive:\MyFolder"
with appropriate paths. SuspendedMessagesTempFileLocation is
thelocation where the message parts and context will get saved.
SuspendedMessagesFileLocation is the location for the SuspendedMessage instance. Replace
processing instructions if desired within the XML file to point to the
proper InfoPath form you wish to open. An example of one is
solutionVersion='1.0.0.1' productVersion='11.0.5531' PIVersion='1.0.0.0' href='file:///C:\My%20Documents\EditAndResubmit\InfoPathForms\ SuspendedMessage.xsn' language='en-us'
Add References and Class Variables:
Add a reference within the project to System.Management.dll and System.Configuration.dll. In Solution Explorer, right-click Service1.vb, and then click View Code. At the top of the page add the following Imports statements: Imports System.Management Imports System.Xml Imports System.IOt Imports System.Configuration
Within the class declaration, just under private components as System.ComponentModel.Container = nothing
add the following: private watcher as ManagementEventWatcher
Add Code to OnStart:
In Solution Explorer, right-click Service1.cs, and then click View Code. In the OnStart event handler, replace the comments with the following: 'Listen for messages Dim scope as string = "root\\MicrosoftBizTalkServer" Dim wqlQuery as string = "Select * from MSBTS_ServiceInstanceSuspendedEvent" watcher = new ManagementEventWatcher(scope, wqlQuery) AddHandler watcher.EventArrived, AddressOf MyEventHandler watcher.Start
This will start listening for the ServiceInstanceSuspended event.
Add Custom Event Handler:
Add the following two procedures to the Service 1 class: Public Shared Sub MyEventHandler(sender As Object, e As EventArrivedEventArgs) Try ' Read the TempDirectoryName from config file Dim TempDirectoryName As String = _
ConfigurationManager.AppSettings("SuspendedMessagesTempFileLocation") ' Read WaitingDirectoryName ' This folder is the location for the new XML document that this service ' creates based on context and message parts. Dim WaitingDirectoryName As String = _ ConfigurationSettings.AppSettings("SuspendedMessagesFileLocation") ' If you want to add processing instructions for InfoPath ' this will get it. Dim pi As String = _ ConfigurationSettings.AppSettings("ProcessingInstruction")
Dim waitingMessageFileName As String ' xwriter for suspended message Dim xwriter As XmlTextWriter
'Look up MSBTS_ServiceInstanceSuspendedEvent 'in the BTS04/06 documentation for additional properties Dim ErrorID As String = e.NewEvent("ErrorID").ToString() Dim ErrorCategory As String = e.NewEvent("ErrorCategory").ToString() Dim ErrorDescription As String = e.NewEvent("ErrorDescription").ToString() Dim ServiceStatus As String = e.NewEvent("ServiceStatus").ToString() Dim ServiceInstanceID As String = e.NewEvent("InstanceID").ToString() Dim enumOptions As New EnumerationOptions()
enumOptions.ReturnImmediately = False
Dim MessageInstancesInServiceInstance As New _ ManagementObjectSearcher("root\MicrosoftBizTalkServer", _ "Select * from MSBTS_MessageInstance where ServiceInstanceID='" + _ ServiceInstanceID + "'", enumOptions) 'Enumerate through the result set Dim MessageInstance As ManagementObject For Each MessageInstance In MessageInstancesInServiceInstance.Get() ' The only way to get at the message body is to utilize the ![](http://mscerts.programming4.us/image/201108/arrow2.jpg) SaveToFile ' method on the BTS_MessageInstance WMI Class. ' This saves all of the message information to files. ' Each MessagePart making up a message is saved in separate files, ' typically you only get a Body, but you must cater to multipart ' messages to cover all scenarios. ' As well as the MessageParts, a context file is created; you need to ' use this to extract the MessagePartIDs and MessagePartNames so you ' can then work out the file names to open! ' The context file name format is ' <MessageInstanceID>_context.xml. ' And then the actual message information file name format is
' <MessageInstanceID>_<MessagePartID>[_<MessagePartName>].out ' MessagePartName is only required if the MessagePart has a name! ' You need to build this file name up so you can load it up - ' no hacking here! ' Save the files MessageInstance.InvokeMethod("SaveToFile", New Object() _ {TempDirectoryName})
' Get the MessageInstanceID Dim MessageInstanceID As String = _ MessageInstance("MessageInstanceID").ToString()
' You now need to load the context file up to get the MessagePart ' information Dim ContextFileName As String
' Load the context file up Dim doc As New XmlDocument() doc.Load(ContextFileName)
' Pull out context properties that you are interested in Dim ReceivedFileName As String = GetContextProperty(doc, _ "ReceivedFileName") Dim InboundTransportLocation As String = GetContextProperty(doc, _ "InboundTransportLocation") Dim InterchangeID As String = GetContextProperty(doc, _ "InterchangeID") Dim ReceivePortID As String = GetContextProperty(doc, _ "ReceivePortID") Dim ReceivePortName As String = GetContextProperty(doc, _ "ReceivePortName")
' Create an XmlWriter to store the data. ' This will get written to a file when complete. waitingMessageFileName = [String].Format("") xwriter = New XmlTextWriter(waitingMessageFileName, _ System.Text.Encoding.UTF8) xwriter.Formatting = Formatting.Indented xwriter.WriteStartDocument() 'Write the ProcessingInstruction node. xwriter.WriteProcessingInstruction("mso-infoPathSolution", pi) xwriter.WriteProcessingInstruction("mso-application", _ "progid=""InfoPath.Document""") xwriter.WriteComment(String.Format("Created on {0}", _ DateTime.Now.ToString())) ' Write the context information xwriter.WriteStartElement("ns0", "SuspendedMessage", _
"http://Microsoft.BizTalk.SuspendQueue.SuspendedMessage") xwriter.WriteStartElement("Context") xwriter.WriteElementString("ReceivedFileName", ReceivedFileName) xwriter.WriteElementString("InboundTransportLocation", _ InboundTransportLocation) xwriter.WriteElementString("InterchangeID", InterchangeID) xwriter.WriteElementString("ReceivePortID", ReceivePortID) xwriter.WriteElementString("ReceivePortName", ReceivePortName) xwriter.WriteEndElement() ' Context ' Start the Message Element xwriter.WriteStartElement("Message")
' Use XPath to return all of the MessagePart(s) referenced in the ' context ' You can then load the file up to get the message information Dim MessageParts As XmlNodeList = _ doc.SelectNodes("/MessageInfo/PartInfo/MessagePart") Dim MessagePart As XmlNode For Each MessagePart In MessageParts ' Pull the MessagePart info out that you need Dim MessagePartID As String = MessagePart.Attributes("ID").Value Dim MessagePartName As String = ![](http://mscerts.programming4.us/image/201108/arrow2.jpg) MessagePart.Attributes("Name").Value Dim Contents As String Dim FileName As String ' If you have a MessagePartName, append this to the end of ' the file name. It's optional so if you don't have it, don't ' worry about it. If MessagePartName.Length > 0 Then FileName = [String].Format("") End If
' Load the message, place it in canonical schema, and submit it. ' Create an instance of StreamReader to read from a file. ' The using statement also closes the StreamReader. Dim sr As New StreamReader(FileName) Try ' Read to end of file Contents = sr.ReadToEnd() Finally sr.Dispose() End Try
' Write out MessagePart data xwriter.WriteStartElement("MessagePart") xwriter.WriteElementString("MessagePartId", MessagePartID)
xwriter.WriteElementString("Name", MessagePartName) xwriter.WriteStartElement("Contents") ' Write out contents as CDATA. xwriter.WriteCData(Contents) xwriter.WriteEndElement() ' Contents xwriter.WriteEndElement() ' MessagePart Next MessagePart xwriter.WriteEndElement() ' Message xwriter.WriteEndElement() ' SuspendedMessage xwriter.Close() Next MessageInstance End Try End Sub 'MyEventHandler
' Helper function to pull out context properties given a property name Private Shared Function GetContextProperty(doc As XmlDocument, propertyName As _ String) As String Dim MessageContext As XmlNode = _ doc.SelectSingleNode(("/MessageInfo/ContextInfo/Property[@Name='" + _ propertyName"']")) If Not (MessageContext Is Nothing) Then If Not (MessageContext.Attributes("Value") Is Nothing) Then Return MessageContext.Attributes("Value").Value Else Return "Value no found" End If Else Return "Property not found" End If End Function 'GetContextProperty
Compile Project and Install Windows Service:
Under the Build menu, select Build Solution. Open a command prompt and change to the project root directory of this project. From the command line type the following: - "<Drive>:\WINDOWS\Microsoft.NET\Framework\
v2.0.50727\installutil.exe"
"bin\Debug\BizTalkSuspendedMessageHandlerService.exe"
- "<Drive>"is the drive letter where Windows is installed. This will install the EXE as a Windows service.
A
prompt will come up asking you for credentials. Enter credentials that
have access to the BizTalkresources. After entering credentials, a
message should be returned indicating success and that the install was
completed. From Administrative Tools, open Services. Find
the new service, Service1. Right-click and select Start. The service
will now start, and the system will write out suspended messages from
both the SaveToFile procedure and the canonical SuspendedMessage message
that the service creates itself.
Create Client to Edit XML in Canonical Format:
The Windows service that you've created generates a new XML document according to the following XSD:
<?xml version="1.0" encoding="utf-16"?> <xs:schema xmlns="http://Microsoft.BizTalk.SuspendQueue.SuspendedMessage" xmlns:b=http://schemas.microsoft.com/BizTalk/2003 targetNamespace=http://Microsoft.BizTalk.SuspendQueue.SuspendedMessage xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="SuspendedMessage"> <xs:complexType> <xs:sequence> <xs:element name="Context"?> <xs:complexType> <xs:sequence> <xs:element name="ReceivedFileName" type="xs:string" /> <xs:element name="InboundTransportLocation" type="xs:string" /> <xs:element name="InterchangeID" type="xs:string" /> <xs:element name="ReceivePortID" type="xs:string" /> <xs:element name="ReceivePortName" type="xs:string" /> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="Message"> <xs:complexType?> <xs:sequence> <xs:element minOccurs="1" maxOccurs="unbounded" name="MessagePart"> <xs:complexType> <xs:sequence> <xs:element name="MessagePartId" type="xs:string" /> <xs:element name="Name" type="xs:string" /> <xs:element name="Contents" type="xs:string" /> </xs:sequence> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> </xs:element> </xs:schema>
In order to consume
the XML document generated in accordance with this schema, you need to
load the document into some type of editor and modify the contents of
the original message contained in the <Contents>
element. One option for this is InfoPath. Specifically InfoPath with
SP1 may be more useful because it has a Paragraph Breaks option for text
boxes that allows for easier viewing of data. You may also experience
problems when trying to perform changes on flat files, since most flat
files use CR and LF. Flat files will need to be tested to see whether
they work with this scenario.
If you are going to use
InfoPath, you can easily create a new form based on the preceding
schema, modify the app.config file of the Windows service to point to
the processing instruction for the InfoPath form, and then be able to
open the SuspendedMessage XML files that get generated. Once modified,
you can just save the app.config file.
Send Document to BizTalk File Service Receive Location:
The data within the <Contents>
element represents the actual data for each suspended message part.
Once this data has been repaired, there are two easy options for
resubmitting the file:
Copy and paste the value within the <Contents>
element into a new file within Notepad. Place this new file within the
receive location drop directory to be processed by BizTalk. Create a simple submit button on your InfoPath form that reads the <Contents> element and submits the file to BizTalk based upon the ReceivedFileName and InboundTransportLocation context property values.
|