Just recently I had a customer ask me if they could use their existing BizTalk infrastructure to manage events that were spawned from their primary application. So, I built a small demonstration that I've outlined here.
Basically, this company's primary application triggers events that should result in email notifications. They wanted something that could listen for these events, send emails, and be flexible and easy to modify. The key is that last part. I wanted to build something that would not require a code change or redeployment if new events were added or email formats had to change.
I started with a basic schema. All that I get from the primary application is the "event source" and "event ID". So I need an orchestration that will fetch the additional data needed to send and format the email alert. From the base schema, I created another schema that BizTalk will actually use to send the alert. It looks like this:
I've now got a "Template" node which will hold the physical location of the XSLT template, and an "OptionalText" node which will hold any additional text we want to include in the email message (HTML markup supported!).
Next I needed a pipeline to do the XSLT conversion. If you recall, the BizTalk SDK ships with a sample for using XSLT to build an HTML formatted email message. I needed to modify that sample because instead of getting the "template" property at design time, I need to use a run-time value. So, I copied the code from the SDK sample, and modified the core Execute method:
public IBaseMessage Execute(IPipelineContext pc, IBaseMessage inmsg)
//variable to grab out template location (promoted value)
string templatePath = inmsg.Context.Read("TemplateLocation", "http://CompanyA.PrimaryApp.EmailNotification.ApplicationAlertPropSchema").ToString();
//call helper method, passing in stream and template path
inmsg.BodyPart.Data = TransformMessage(inmsg.BodyPart.Data, templatePath);
inmsg.BodyPart.ContentType = "text/html";
Ok, now I had to consume this custom pipeline component. My new "send" pipeline in Visual Studio looks like this:
I threw the XML Assembler in there just to be sure that my "TemplateLocation" value would get promoted into context. Probably unnecessary, but what the heck.
Next we needed the Business Rules that will store the key information needed to construct the email message. Based on the source of the event, we need to designate a recipient, subject, extra "body" information, and template to be used. One of the rules I built looks like this:
Note that the (optional) body text field contains HTML markup. You may also see that I perform the Halt command at the bottom. If that rule fires, then I want to halt further rule execution. I wanted to be able to handle unknown events, so each defined alert had a Priority number set in the Business Rules Composer. Finally, I created one final rule with a Priority of 0, which by design, will only fire if all preceding rules have been ignored (that is, it's an event that wasn't recognized by any earlier rules and processing halted). That "catch all" rule looks like this:
Almost there. Now, an orchestration is needed to tie all this together. My process flow consists of receiving a message, instantiating a .NET class (which holds all the email details), calling my Business Rule(s), building the output message, and sending the message out. The flow looks like this:
The main guts of this process exist in the final Construct shape. You'll see that the Construct shape exists in an Atomic Scope. That's because I needed to use a variable of type XmlElement. Why? Here's the code for the Message Assignment:
Create outbound message from inbound message
AlertOutput = AlertInput;
Get the XML element holding the optional text
xmlElem = xpath(AlertOutput, "/*[local-name()='ApplicationAlert' and namespace-uri()='http://CompanyA.PrimaryApp.EmailNotification']/*[local-name()='OptionalText' and namespace-uri()='']");
Set the "innerXML" equal to the HTML markup
xmlElem.InnerXml = EmailData.EmailBody;
Set the template to use
AlertOutput.Template = EmailData.EmailTemplate;
Set all the needed email properties
SendFASTAlertPort(Microsoft.XLANGs.BaseTypes.Address) = "mailto:" + EmailData.EmailTo;
AlertOutput(SMTP.From) = EmailData.EmailFrom;
AlertOutput(SMTP.Subject) = EmailData.EmailSubject;
AlertOutput(SMTP.SMTPHost) = "WS03VSSQL05";
I had to do the whole "XmlElement" thing because just setting the HTML string equal to the OptionalText field caused the HTML markup to get jumbled. By using InnerXML on the XMLElement, the HTML (really just valid XML) gets thrown under the OptionalText field as XML data, tags intact. Pretty handy.
Finally, I built and deployed this whole thing. I then wrote a few XSLT templates to format my HTML email. After feeding a few different alerts into BizTalk, my Outlook mailbox contained the following messages:
You'll see the last sentence in each email "body" represents the HTML markup set in the Business Rule. So, now I have an architecture where someone could change just the XSLT template and modify the entire alert message. If you wanted to add new events to be specifically handled, all that's required is a new business rule, and optionally, a new XSLT template. Pretty simple!
Technorati Tags: BizTalk