8. Pseudo-Walkthrough to Perform Edits and Resubmits
Exercise 1 describes in detail the steps to edit and resubmit a suspended message. The walkthrough in Exercise 1 follows the steps in Figure 2, shown earlier in this section.
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 2008.
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 the
location 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 want 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
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 =
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 BizTalk resources. 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 2007 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.