Using MSMQ Triggers with Business Scorecard Manager 2005

This is a preliminary document and may be changed substantially prior to final commercial release of the software described herein. The information contained in this document represents the current view of Microsoft Corporation on the issues discussed as of the date of publication. Because Microsoft must respond to changing market conditions, it should not be interpreted to be a commitment on the part of Microsoft, and Microsoft cannot guarantee the accuracy of any information presented after the date of publication.

This white paper is for informational purposes only. MICROSOFT MAKES NO WARRANTIES, EXPRESS OR IMPLIED, IN THIS DOCUMENT.

Complying with all applicable copyright laws is the responsibility of the user. Without limiting the rights under copyright, no part of this document may be reproduced, stored in, or introduced into a retrieval system, or transmitted in any form or by any means (electronic, mechanical, photocopying, recording, or otherwise), or for any purpose, without the express written permission of Microsoft Corporation.

Microsoft may have patents, patent applications, trademarks, copyrights, or other intellectual property rights covering subject matter in this document. Except as expressly provided in any written license agreement from Microsoft, the furnishing of this document does not give you any license to these patents, trademarks, copyrights, or other intellectual property.

© 2005 Microsoft Corporation. All rights reserved.

The example companies, organizations, products, domain names, e-mail addresses, logos, people, places, and events depicted herein are fictitious. No association with any real company, organization, product, domain name, e-mail address, logo, person, place, or event is intended or should be inferred.

Microsoft, MSDN,Win32, Windows, Windows Server and the Office Logo are either registered trademarks or trademarks of Microsoft Corporation in the United States and/or other countries.

Table of Contents

Introduction......

Example of a Business Scorecard Manager MSMQ Message......

Using MSMQ Triggers To Process Messages from a Queue......

MSMQ Triggers......

Conditions......

Actions......

When do you use more than one trigger?......

When do you use more than one rule?......

Sample Message Parser......

Creating MSMQ Queues, Rules, and Triggers......

Introduction

This white paper describes how touse Microsoft® Message Queuing Services (MSMQ) 3.0 with Microsoft Office Business Scorecard Manager 2005.Readers should be familiar with MSMQ.

MSMQ is ideal for use in a variety of systems as a means of transferring data because you can use it tosend messages across machine boundaries to queues where a receiving application can retrieve them in a prioritized manner.

MSMQ enables applications that run at different times to communicate across heterogeneous networks and across systems that might be temporarily offline. The applications send messages to queues and read messages from queues. Therefore, developers can use thistool to perform operations asynchronously, or in other words, to begin a process without waiting for anoperation to complete.

All Microsoft Office Business Scorecard Manager objects, which includekey performance indicators (KPIs), scorecards, datasources, indicators, and annotations,triggeran event for every Create, Update, and Delete operation. If MSMQ is enabled on the Business Scorecard Manager server, these events generate messages that are stored in the MSMQmessage queue that was specified in Microsoft Office Business Scorecard Builder when the scorecard was created. Table 1illustrates the relationship between Business Scorecard Manager objects and events.

Table 1: Relationship Between Business Scorecard Objects and Events

Object / Type attribute / Event
KPI / Microsoft.PerformanceManagement.Scorecards.Client.KPI / Create
Update
Delete
Scorecard / Microsoft.PerformanceManagement.Scorecards.Client.Scorecard / Create
Update
Delete
Data source / Microsoft.PerformanceManagement.Scorecards.Client.DataSource / Create
Update
Delete
Indicator / Microsoft.PerformanceManagement.Scorecards.Client.Indicator / Create
Update
Delete
Annotation / Microsoft.PerformanceManagement.Scorecards.Client.Annotation / Create
Update
Delete

Example of a Business Scorecard Manager MSMQ Message

The following is anexample of a typical MSMQmessage generated by Business Scorecard Manager:

<?xml version="1.0" ?>

<BpmMessage xmlns:xsd=" Schema" xmlns:xsi=" Schema-instance"

Guid="313189e5-2f5c-4fe7-b21f-823f8646b2fb" Version="1"

Type="Microsoft.PerformanceManagement.Scorecards.Client.Indicator"

Action="Create" Login="Domain\UserName" Element="" />.

The message has the following attributes:

  • Guid, which is the identifier for the scorecard element.
  • Version, which is the version number of the scorecard element.
  • Type, which is the type of the scorecard element. (In the preceding example, the element type is Indicator.)
  • Action, which is the action that is performed on the scorecard element.
  • Login, which is the login name of the user who performed the action.
  • Element, which is a serialized format of the actual element.Business Scorecard Manager doesnot store the entireelement in the message queue because a Unicode message in an MSMQ queue cannot be greater than 2 megabytes (MB)in size.

For more information about managing resources in MSMQ, see the article "Resource Management in MSMQ Applications" on MSDN® at:

Using MSMQ Triggers To Process Messages from a Queue

A typical architecture for pulling messages from a message queue uses a Microsoft Windows®service application on the receiving end of a queue,which is constantly polling for new messages. When a new message arrives, the service application reads it from the queue and performs some action(s). There are several issues associated with using this type of architecture that are addressed by using MSMQ Triggers Application, which is installed with Microsoft Windows Server™ 2003,to ensure successful polling of Business Scorecard Manager message queues and retrieval of messages.

Issue:If the Windows service application is not running, you might lose the messages that are in the queue.

Solution: Because the MSMQ Triggers Application is also a Windows service, the messages in the queue are processed when the service is restarted.

Issue:If there is more than one service application polling the queue, you must decide what service is to receive messages first.

Solution: The components attached to ruleactions will receivethe message in sequential order when you use the MSMQ Triggers Application.

Issue:If the first service to receive a message deletes the message, none of the other service applications will receive the message.

Solution: With the recommended architecture, a message is deleted only after the last action is finished.

Issue:If a service application reads but does not delete a message, it is not clear what service will delete the message when all service applications have finished polling the queue.

Solution:The last action deletes the message from the message queue when you use the MSMQ Triggers Application.

MSMQ Triggers

MSMQ Triggers Application makes it possible for MSMQ to perform a specific action upon receipt of an incoming message in a queue. The application monitors all the queues that have triggers associated with them. When a message is received on a monitored queue, MSMQ Triggers Application uses the rules and conditions to determine what action to take. The action can take the form of the invocation of a method within a COM component or of running a standard executable.

Triggers can take an existing COM component and invoke a specific method on the basis of the attributes of the message and the queue in which the message arrives.

A trigger consists of a set of rules that are made up of conditions and actions.A single trigger can have many rules, and one rule can be associated with multiple triggers. Therefore, a rule is created independent of an actual trigger. The first stage of using a trigger involves creating a rule, providing the rule with a unique name and a description, and then defining the conditions and actions.

Conditions

Condition checks are performed against incoming messages to determine whether the action is appropriate, according to whether the condition has been met. All condition checks must be passed in order before an action is taken. Conditions are case sensitive.

Actions

Actions are taken when all of the specified conditions are met. An action can be one of the following:

  • Invocation of a COM methodin a COM DLL (or in a .NET assembly through COM Interop). You provide the ProgID and method name for the component. Additional parameters can be passed to the method call.
  • Running a WIN32®executable.

When do you use more than one trigger?

If you have multiple triggers, they will be processed independently and possibly simultaneously. This works well when the sequence in which the triggers execute is not critical, or when one of the rules involves a long running process and other rules do not depend on that rule completing first.

When do you use more than one rule?

When you attach multiple rules to one trigger, MSMQ Triggers Applicationruns the rules according to the priority that you set when you attached the rules to the trigger. The application waits until the execution of one rule is complete before executing the next rule. This is helpful because all of the rules associated with a trigger are run in a strict sequence. You might want to set the last rule that runs to consume the MSMQ message or to send a notification that the trigger has run and the message has been processed.

Sample Message Parser

This section describes how to create a message parser using the Microsoft .NET Framework.This component can later be called by an action in order to process a message in a queue.

The following code sample is a .NET component that writes the contents of a message in a queue to a text file. This component is contained within a class library called MSMQXMLLog. This class librarycontainsa ParseMessage method that parses the MessageBody of the message.

Note the following when you are viewing the code sample:

  • You must add theGUID, ProgId, and ComVisible attributes to make the .NET component aware of COM.
  • The MSMQ message contains all of the attributes listed in the earlier section "Example of a Business Scorecard Manager Message."The .NET component makes a Web service call to Business Scorecard Manager server to retrieve additional information about the element, such asName, Description, and so forth.
  • Triggers can only call a COM component.This examplemakesCOM-callable wrappers (CCWs) for the.NET component and storesthese wrappersin the Global Assembly Cache (GAC). To generate CCWs automatically, inyour project's Configuration Propertiesin Visual Studio .NET, select Register for COM Interop.

using System;

using System.Messaging;

using System.EnterpriseServices;

using System.Runtime.InteropServices;

using System.IO;

using System.Text;

using System.Xml;

using Microsoft.PerformanceManagement.Scorecards.Client;

namespace Microsoft.PerformanceManagement.Scorecards.Samples

{

// Contract service

publicinterface ITrigger

{

void ParseMessage(object message);

}

// CLSID

[Guid("CED4CDF9-B839-48B5-9F97-1D3FB1318719")]

// ProgId

[ProgId("BSM.MSMQXMLLog")]

[ComVisible(true)]

publicclass MSMQXMLLog : ServicedComponent, ITrigger

{

// COM Activated method

[ComVisible(true)]

publicvoid ParseMessage(object message)

{

IBpm manager;

stringbsmServerName = "

Guid guid = Guid.Empty;

string name = String.Empty;

string description = String.Empty;

string login = String.Empty;

BpmMessageAction action;

string type = String.Empty;

Kpi kpi = null;

Scorecard scorecard = null;

DataSource dataSource = null;

Indicator indicator = null;

Annotation annotation = null;

try

{

Message myMessage = new Message();

//Creating an instance of the WebService

manager = PmService.CreateInstance(bsmServerName + "/PmService.asmx");

//Get the MessageBody into a Memorystream

myMessage.BodyStream = new System.IO.MemoryStream((byte[])message);

//Specify the XmlMessageFormatter to convert the stream into a BmpMessage object later

myMessage.Formatter = new XmlMessageFormatter(new Type[] { typeof(BpmMessage)});

//Generate the BpmMessage object out of the message body

BpmMessage bpmMessage = (BpmMessage)myMessage.Body;

//Get the Guid of the message

guid = bpmMessage.Guid;

//Get the Login(Who changed?)of the message

login = bpmMessage.Login;

//Get the Action (Create/Update/Delete) on the message

action = bpmMessage.Action;

//Get the Type (Kpi/Scorecard/Annotation/Datasource/Indicator) of the message

type = bpmMessage.Type.ToString();

switch (type)

{

case "Microsoft.PerformanceManagement.Scorecards.Client.Kpi":

{

// If the action is not delete then get the KPI by

// calling the webservice through Client.dll

if(action != BpmMessageAction.Deleted)

{

kpi = manager.GetKpi(guid);

name = kpi.Name.Text;

description = kpi.Description.Text;

}

// If the action is Delete as the element is already deleted from

// the database just display 'N/A'

else

{

name = "N/A";

description = "N/A";;

}

type = "Kpi";

break;

}

case "Microsoft.PerformanceManagement.Scorecards.Client.Scorecard":

{

// If the action is not delete then get the KPI by

// calling the webservice through Client.dll

if(action != BpmMessageAction.Deleted)

{

scorecard = manager.GetScorecard(guid);

name = scorecard.Name.Text;

description = scorecard.Description.Text;

}

// If the action is Delete as the element is already deleted from

// the database just display 'N/A'

else

{

name = "N/A";

description = "N/A";;

}

type = "Scorecard";

break;

}

case "Microsoft.PerformanceManagement.Scorecards.Client.DataSource":

{

// If the action is not delete then get the KPI by

// calling the webservice through Client.dll

if(action != BpmMessageAction.Deleted)

{

dataSource = manager.GetDataSource(guid);

name = dataSource.Name.Text;

description = dataSource.Description.Text;

}

// If the action is Delete as the element is already deleted from

// the database just display 'N/A'

else

{

name = "N/A";

description = "N/A";;

}

type = "DataSource";

break;

}

case "Microsoft.PerformanceManagement.Scorecards.Client.Indicator":

{

// If the action is not delete then get the KPI by

// calling the webservice through Client.dll

if(action != BpmMessageAction.Deleted)

{

indicator = manager.GetIndicator(guid);

name = indicator.Name.Text;

description = indicator.Description.Text;

}

// If the action is Delete as the element is already deleted from

// the database just display 'N/A'

else

{

name = "N/A";

description = "N/A";;

}

type = "Indicator";

break;

}

case "Microsoft.PerformanceManagement.Scorecards.Client.Annotation":

{

// If the action is not delete then get the KPI by

// calling the webservice through Client.dll

// but Annotations doesn't have any name and description

// and the Annotation Element contains additional info about the

// Scorecard and the data slices to which it belongs to

if(action != BpmMessageAction.Deleted)

{

annotation = manager.GetAnnotation(guid);

name = "N/A";

description = "N/A";

}

// If the action is Delete as the element is already deleted from

// the database just display 'N/A'

else

{

name = "N/A";

description = "N/A";

}

type = "Annotation";

break;

}

}

//Call the AddReport to log into a XML File

AddToReport(type, name, description ,guid.ToString(),action.ToString() ,login);

}

catch(Exception ex)

{

LogException(ex);

}

}

[ComVisible(false)]

void LogException(Exception ex)

{

TextWriter errorOutput = null;

try

{

//If an error occurs try to log into a error log

string errorfile = @"C:\MSMQXMLLog_Error.log";

if (!File.Exists(errorfile))

errorOutput = File.CreateText(errorfile);

else

errorOutput = File.AppendText(errorfile);

errorOutput.WriteLine(ex.Message);

errorOutput.WriteLine(ex.StackTrace);

errorOutput.Close();

}

catch

{

}

finally

{

if(errorOutput != null)

errorOutput.Close();

}

}

///<summary>

/// Add the Message to an XML file.

///</summary>

///<param name="Type"</param>

///<param name="Name"</param>

///<param name="Description"</param>

///<param name="Guid"</param>

///<param name="Action"</param>

///<param name="Login"</param>

[ComVisible(false)]

void AddToReport(string Type, string Name, string Description, string Guid, string Action, string Login )

{

try

{

string filename = @"C:\MSMQXMLLog.xml";

XmlDocument xmlDoc = new XmlDocument();

try

{

xmlDoc.Load(filename);

}

catch(System.IO.FileNotFoundException)

{

//if file is not found, create a new xml file

XmlTextWriter xmlWriter = new XmlTextWriter(filename, System.Text.Encoding.UTF8);

xmlWriter.Formatting = Formatting.Indented;

xmlWriter.WriteProcessingInstruction("xml", "version='1.0' encoding='UTF-8'");

xmlWriter.WriteStartElement("Elements");

xmlWriter.Close();

xmlDoc.Load(filename);

}

XmlNode root = xmlDoc.DocumentElement;

//Creating a new "Element" as child node

//Create "Type & Name & Description & Guid & Action & Login" nodes

XmlElement childNode = xmlDoc.CreateElement("Element");

XmlElement nodeType = xmlDoc.CreateElement("Type");

XmlElement nodeName = xmlDoc.CreateElement("Name");

XmlElement nodeDescription = xmlDoc.CreateElement("Description");

XmlElement nodeGuid = xmlDoc.CreateElement("Guid");

XmlElement nodeAction = xmlDoc.CreateElement("Action");

XmlElement nodeLogin = xmlDoc.CreateElement("Login");

//Assigning data to the above added nodes

nodeType.InnerText = Type;

nodeName.InnerText = Name;

nodeDescription.InnerText= Description;

nodeGuid.InnerText = Guid;

nodeAction.InnerText = Action;

nodeLogin.InnerText = Login;

//Appending the "Type & Name & Description & Guid & Action & Login" nodes to the "Element" node

childNode.AppendChild(nodeType);

childNode.AppendChild(nodeName);

childNode.AppendChild(nodeDescription);

childNode.AppendChild(nodeGuid);

childNode.AppendChild(nodeAction);

childNode.AppendChild(nodeLogin);

//Insert the new Element node at the end of the xml file

xmlDoc.DocumentElement.InsertAfter(childNode, xmlDoc.DocumentElement.LastChild);

// Save the file

xmlDoc.Save(filename);

}

catch(Exception ex)

{

LogException(ex);

}

}

}

}

To add the .NETcomponent to the GAC

  1. Generate a strong name and use the gacutil tool to insert the assembly in the GAC.
  2. Use the following .NET command prompt to generate a strong name key file in your project directory.

sn –k msmqKey.snk