Sunday, January 23, 2011
Saturday, January 8, 2011
Calling the BRE from .NET Applications
One common misconception regarding the Business Rules Engine is that you need BizTalk to use it. Actually, you can call the Rules Engine from any .NET code. Below is some sample code that takes in a message and runs it against a specified policy.
using System.Xml;
using System.IO;
using System.Collections;
using Microsoft.XLANGs.BaseTypes;
using Microsoft.RuleEngine;
public static XmlNode CallRules(string policyName,
XLANGMessage msg, string messageDocType)
{
//Convert to XmlDocuments
Stream msgStream = (Stream)msg[0].RetrieveAs(typeof(Stream));
XmlDocument doc = new XmlDocument();
doc.PreserveWhitespace = false;
doc.Load(msgStream);
//Add the facts
ArrayList facts = new ArrayList();
facts.Add(new TypedXmlDocument(messageDocType, doc));
try
{
using (Policy policy = new Policy(policyName))
{
policy.Execute(facts.ToArray());
}
}
catch (Exception ex)
{
throw ex;
}
//retrieve the return document from the output array
return ((TypedXmlDocument)facts[0]).Document;
}
Our incoming message in this example is an XLANGMessage as one would find while working with BizTalk, but it needn’t be. Note that before calling the rules engine, we had to go through some machinations to convert the message into an XmlDocument. If you start off with an XmlDocument in the first place you can remove the conversion code and the reference to the System.IO and the Microsoft.XLANGs.BaseTypes namespaces.
The code here is geared toward working with Xml. However, rules may also be executed against .NET components–you just have to make sure that you initialize them and send them to the BRE as facts by adding them to the “fact array”. After executing the policy, whatever changes the rules engine made to the “fact” would be present when retrieved from the array.
SAMPLE Microsoft.RuleEngine.TypedXmlDocument
public class BREResultHelper
{
public bool DebugTrace(string value)
{
System.Diagnostics.Debug.WriteLine("<" + value + ">");
return true;
}
public void AddValidationError(TypedXmlDocument resultMessage, string rule, string message)
{
var node = GetNode(resultMessage.Document.OwnerDocument, "//ns0:BREResult/BrokenRules");
AddNode(node, "BrokenRule");
AddNode(node.LastChild, "Rule", rule);
AddNode(node.LastChild, "Message", message);
}
public void AddSetting(TypedXmlDocument resultMessage, string key, string value)
{
var node = GetNode(resultMessage.Document.OwnerDocument, "//ns0:BREResult/Settings");
AddNode(node, "Setting");
AddNode(node.LastChild, "Key", key);
AddNode(node.LastChild, "Value", value);
}
private void AddNode(XmlNode node, string elementName)
{
XmlElement newElement = node.OwnerDocument.CreateElement(elementName);
node.AppendChild(newElement);
}
private void AddNode(XmlNode node, string elementName, string value)
{
XmlElement newElement = node.OwnerDocument.CreateElement(elementName);
XmlText elementText = node.OwnerDocument.CreateTextNode(value);
node.AppendChild(newElement);
node.LastChild.AppendChild(elementText);
}
private XmlNode GetNode(XmlDocument doc, string xpath)
{
XmlNamespaceManager xmlnsManager = new XmlNamespaceManager(doc.NameTable);
xmlnsManager.AddNamespace("ns0", "http://xyz.BizTalk.ESB.Schemas.BREResult");
return doc.SelectSingleNode(xpath, xmlnsManager);
}
}
public void AddValidationError(TypedXmlDocument resultMessage, string rule, string message)
{
XNamespace ns = "http://xyz.BizTalk.ESB.Schemas.BREResult";
XDocument doc = XDocument.Parse(resultMessage.Document.InnerXml);
XElement root = doc.Element(ns + "BREResult").Element("BrokenRules");
root.Add(new XElement("BrokenRule",
new XElement("Rule", rule),
new XElement("Message", message)));
resultMessage.Document.InnerXml = doc.ToXmlDocument().InnerXml;
}
Trace BRE Call Rules
MyPolicy = new Microsoft.RuleEngine.Policy(YourPolicyName);
MyTracer = new Microsoft.RuleEngine.DebugTrackingInterceptor();
MyArrayList.Add(MyFact2);
MyPolicy.Execute(MyArrayList.ToArray(),MyTracer);
MyPolicy.Dispose();
System.Diagnostics.Trace.WriteLine(MyTracer.GetTraceOutput());
Passing an Array of Facts into the Rules Engine (BizTalk )
I finally figured out how to pass an array of facts into the Rules Engine that comes with BizTalk 2004. (Since the XLangs syntax does not directly support arrays, see http://datacogs.com/datablogs/archive/2004/12/04/183.aspx )The trick is to use the ToArray method of the ArrayList class. Simple when you know how, but guaranteed to give hours of head-scratching if you don't. I will post a more “step-by-step“ article when I get some time.
Here is the code from the Expression shape that does all the work. (The variables are declared within an atomic scope shape in the orchestration)
//System.Collections.ArrayList List;
sCon = "Initial Catalog=Northwind;Data Source=(local);Integrated Security=SSPI;";
con = new System.Data.SqlClient.SqlConnection(sCon);
dcNorthwind = new Microsoft.RuleEngine.DataConnection("Northwind", "ShipperCountry", con);
List.Add(dcNorthwind);
xmlDocument = msgShippingRequest;
typedXmlDocument = new Microsoft.RuleEngine.TypedXmlDocument("RoleLinkSample.ShippingRequest",xmlDocument);
policy = new Microsoft.RuleEngine.Policy("ShippingPolicy");
List.Add(typedXmlDocument);
policy.Execute(List.ToArray());
msgOutgoingShippingRequest = typedXmlDocument.Document;
policy.Dispose();
typedXmlDocument = null;
dcNorthwind = null;
Helper class for Adding XML to documents using the Rules Engine
With the rules engine you can't directly add a node to an XML document in a rules action. On the other time you often would like to do that. No worries, you can call a helper class to do the work for you. Here is an example helper class from John Rummell <welcome back> that does the trick. Nothing particularly revolutionary in the code but it will save you some time I'm sure.
using System;
using System.Xml;
using Microsoft.RuleEngine;
namespace XMLHelpers
{
/// <summary>
/// Class used to add nodes and/or attributes to a TypedXmlDocument
///
/// Using the example:
/// <root>
/// <a>
/// </a>
/// </root>
///
/// AddNode(doc, "/root/a", "b") or AddNodeIfNotThere(doc, "/root/a", "b") will result in:
/// <root>
/// <a>
/// <b />
/// </a>
/// </root>
///
/// AddAttribute(doc, "//a", "name", "value") will result in:
/// <root>
/// <a name="value">
/// </a>
/// </root>
///
/// The code does not create intermediate nodes (e.g. can't create a "c" inside "b"
/// if "b" doesn't exist. As a result, you need to sequence the calls:
/// AddNode(doc, "/root/a", "b");
/// AddNode(doc, "/root/a/b", "c");
/// Note that if "b" already exists, a second "b" node will be created. Use AddNodeIfNotThere
/// to create the node "b" if it doesn't already exist, but leave it alone if it is.
/// </summary>
public class XmlCreateNodes
{
/// <summary>
/// Add an attribute with a specified name and value into the node selected by the XPath
/// </summary>
/// <param name="txd">RuleEngine wrapper for an XmlDocument (or some part of it)</param>
/// <param name="xPath">XPath that must select a node (null or "" to use current node)</param>
/// <param name="attributeName">Attribute name</param>
/// <param name="attributeValue">Attribute value</param>
public void AddAttribute(TypedXmlDocument txd, string xPath, string attributeName, string attributeValue)
{
// can we find the XPath indicated?
// we need an XmlElement in order to set the attribute, not the usual XmlNode
// if the result is not an XmlElement, we don't work
XmlElement node = LocateXPath(txd, xPath) as XmlElement;
// if we found a matching node, add the attribute
if (null != node)
{
node.SetAttribute(attributeName, attributeValue);
}
}
/// <summary>
/// Add an new node with a specified name and namespace into the node selected by the XPath
/// </summary>
/// <param name="txd">RuleEngine wrapper for an XmlDocument (or some part of it)</param>
/// <param name="xPath">XPath that must select a node (null or "" to use current node)</param>
/// <param name="nodeName">Name for the new node</param>
/// <param name="nodeNamespace">Namespace for the new node</param>
public void AddNode(TypedXmlDocument txd, string xPath, string nodeName, string nodeNamespace)
{
// can we find the XPath indicated?
XmlNode node = LocateXPath(txd, xPath);
if (null == node) return;
// determine the root node for the document
// if the XPath selects the TXD, it will have no root if it is an XmlDocument
// in that case, simply use the document from the TXD
XmlDocument root = node.OwnerDocument;
if (null == root)
{
// if the XPath selects the TXD, it may
// so fix accordingly
root = txd.Document as XmlDocument;
if (null == root) return;
}
// create a new node and add it in
XmlElement newNode = root.CreateElement(nodeName, nodeNamespace);
node.AppendChild(newNode);
}
/// <summary>
/// Add an new node with a specified name into the node selected by the XPath
/// </summary>
/// <param name="txd">RuleEngine wrapper for an XmlDocument (or some part of it)</param>
/// <param name="xPath">XPath that must select a node (null or "" to use current node)</param>
/// <param name="nodeName">Name for the new node</param>
public void AddNode(TypedXmlDocument txd, string xPath, string nodeName)
{
// can we find the XPath indicated?
XmlNode node = LocateXPath(txd, xPath);
if (null == node) return;
// determine the root node for the document
// if the XPath selects the TXD, it will have no root if it is an XmlDocument
// in that case, simply use the document from the TXD
XmlDocument root = node.OwnerDocument;
if (null == root)
{
// if the XPath selects the TXD, it may
// so fix accordingly
root = txd.Document as XmlDocument;
if (null == root) return;
}
// create a new node and add it in
XmlElement newNode = root.CreateElement(nodeName);
node.AppendChild(newNode);
}
/// <summary>
/// Add an new node with a specified name and namespace into the node selected by the XPath
/// provided that it doesn't already exist
/// </summary>
/// <param name="txd">RuleEngine wrapper for an XmlDocument (or some part of it)</param>
/// <param name="xPath">XPath that must select a node (null or "" to use current node)</param>
/// <param name="nodeName">Name for the new node</param>
/// <param name="nodeNamespace">Namespace for the new node</param>
public void AddNodeIfNotThere(TypedXmlDocument txd, string xPath, string nodeName, string nodeNamespace)
{
// can we find the XPath indicated?
XmlNode node = LocateXPath(txd, xPath);
if (null == node) return;
// does the new element already exist?
foreach (XmlNode child in node.ChildNodes)
{
if ((child.LocalName == nodeName) && (child.NamespaceURI == nodeNamespace)) return;
}
// determine the root node for the document
// if the XPath selects the TXD, it will have no root if it is an XmlDocument
// in that case, simply use the document from the TXD
XmlDocument root = node.OwnerDocument;
if (null == root)
{
// if the XPath selects the TXD, it may
// so fix accordingly
root = txd.Document as XmlDocument;
if (null == root) return;
}
// create a new node and add it in
XmlElement newNode = root.CreateElement(nodeName, nodeNamespace);
node.AppendChild(newNode);
}
/// <summary>
/// Add an new node with a specified name into the node selected by the XPath
/// provided that it doesn't already exist
/// </summary>
/// <param name="txd">RuleEngine wrapper for an XmlDocument (or some part of it)</param>
/// <param name="xPath">XPath that must select a node (null or "" to use current node)</param>
/// <param name="nodeName">Name for the new node</param>
public void AddNodeIfNotThere(TypedXmlDocument txd, string xPath, string nodeName)
{
// can we find the XPath indicated?
XmlNode node = LocateXPath(txd, xPath);
if (null == node) return;
// does the new element already exist?
foreach (XmlNode child in node.ChildNodes)
{
if (child.LocalName == nodeName) return;
}
// determine the root node for the document
// if the XPath selects the TXD, it will have no root if it is an XmlDocument
// in that case, simply use the document from the TXD
XmlDocument root = node.OwnerDocument;
if (null == root)
{
// if the XPath selects the TXD, it may
// so fix accordingly
root = txd.Document as XmlDocument;
if (null == root) return;
}
// create a new node and add it in
XmlElement newNode = root.CreateElement(nodeName);
node.AppendChild(newNode);
}
/// <summary>
/// Select the first node that the specified XPath points to inside the XmlDocument
/// </summary>
/// <param name="txd">RuleEngine wrapper for an XmlDocument (or some part of it)</param>
/// <param name="xPath">XPath that must select a node (null or "" to use current node)</param>
/// <returns></returns>
internal XmlNode LocateXPath(TypedXmlDocument txd, string xPath)
{
// does the TXD contain a node?
XmlNode parent = txd.Document;
if (null == parent) return null;
// is there an XPath specified? if not, return the parent
if ((xPath == null) || (xPath == string.Empty)) return parent;
// return the first node that the XPath points to
return parent.SelectSingleNode(xPath, txd.NamespaceManager);
}
}
}
SAMPLE Microsoft.RuleEngine.TypedXmlDocument
using System;
namespace CallBusinessRules
{
class CallBusinessRules
{
const string defaultPolicyName = \"Policy1\";
const string defaultConnectionString =
\"Server=(local);Integrated Security=SSPI;database=Northwind\";
const string defaultDataConnectionName = \"Northwind\";
const string defaultDataTableName = \"ItemThreshold\";
static int Main(string[] args)
{
if (args.Length < 1)
return usage(-1);
string xmlFilePath = null,
policyName = defaultPolicyName,
sqlConnectionString = defaultConnectionString,
dataConnectionName = defaultDataConnectionName,
dataTableName = defaultDataTableName;
for (int i = 0; i < args.Length; i++)
{
if (args[i][0] == '-' || args[i][0] == '/')
{
if (i == args.Length - 1)
return usage(-2);
if (string.Compare(args[i].Substring(1),
\"policyName\", true) == 0)
{
policyName = args[++i];
}
else if (string.Compare(args[i].Substring(1),
\"sqlConnection\", true) == 0)
{
sqlConnectionString = args[++i];
}
else if (string.Compare(args[i].Substring(1),
\"datasetName\", true) == 0)
{
dataConnectionName = args[++i];
}
else if (string.Compare(args[i].Substring(1),
\"dataTableName\", true) == 0)
{
dataTableName = args[++i];
}}
else
{
if (xmlFilePath != null)
return usage(-3);
xmlFilePath = args[i];
}
}
Microsoft.RuleEngine.TypedXmlDocument document = null;
try
{
xmlReader = new System.Xml.XmlTextReader(xmlFilePath);
document = new Microsoft.RuleEngine.TypedXmlDocument(
\"ReplenishmentSchemas.RequestStatus\", xmlReader);
}
catch (Exception ex)
{ Console.WriteLine(ex.Message);
}
finally
{
if (xmlReader != null)
xmlReader.Close();
}
try
{
using (Microsoft.RuleEngine.Policy policy = new
Microsoft.RuleEngine.Policy(policyName))
{
object[] facts = new object[2];
facts[0] = document;
using (System.Data.SqlClient.SqlConnection
sqlConnection = new System.Data.SqlClient.SqlConnection(sqlConnectionString))
{
Microsoft.RuleEngine.DataConnection dataConnection = new Microsoft.RuleEngine.DataConnection(dataConnectionName, dataTableName, sqlConnection);
facts[1] = dataConnection;
policy.Execute(facts);
System.Xml.XmlDocument doc =
document.Document as System.Xml.XmlDocument;
doc.Save(System.Console.Out);
sqlConnection.Close();
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return 0;
}
static int usage(int retval)
{
Console.WriteLine(@\"
Use:
{0}
xmlDocumentPath
[-policyName \"\"
[-sqlConnection \"\"
[-datasetName \"\"
[-dataTable \"\"
xmlDocumentPath must point to RefillmentSchemas.RequestStatus XML document instance
defaults:
policyName - \"\"{1}\"\"
connectionString - \"\"{2}\"\"
dataConnectionName - \"\"{3}\"\"
leName - \"\"{4}\"\"
dataTab\", Environment.GetCommandLineArgs()[0], defaultPolicyName, defaultConnectionString, defaultDataConnectionName, defaultDataTableName);
return retval;
}
}
}
I build the above code and i get the following error.
C:\\Labs\\CallBusinessRules\\Class1.cs(58): The name 'xmlReader' does not exist in the class or namespace 'CallBusinessRules.CallBusinessRules'
C:\\Labs\\CallBusinessRules\\Class1.cs(67): The type or namespace name 'xmlReader' could not be found (are you missing a using directive or an assembly reference?)
Any help greatly appreciated!!!!!!!!!!!
Deploying Business Rules Programmatically
{
if (args.Length < 1)
Console.WriteLine("Format: DeployPolicies.exe
else if (args[0] == "/u")
{
Microsoft.BizTalk.RuleEngineExtensions.RuleSetDeploymentDriver rdd = new Microsoft.BizTalk.RuleEngineExtensions.RuleSetDeploymentDriver();
//Undeploy specified policies
for (int i = 1; i < args.Length; i++)
{
string policyName = args[i];
RuleSetInfo rsi = new RuleSetInfo(policyName, 1, 0);
rdd.Undeploy(rsi);
RuleStore rs = rdd.GetRuleStore();
rs.Remove(rsi);
}
}
else
{
//import the BRL file and publish policies in the XML file
Microsoft.BizTalk.RuleEngineExtensions.RuleSetDeploymentDriver rdd = new Microsoft.BizTalk.RuleEngineExtensions.RuleSetDeploymentDriver();
rdd.ImportAndPublishFileRuleStore(args[0]);
//deploy specified policies
for (int i = 1; i < args.Length; i++)
{
string policyName = args[i];
RuleSetInfo rsi = new RuleSetInfo(policyName, 1, 0);
rdd.Deploy(rsi);
}
System.Threading.Thread.Sleep(60000);
}
}
Ref:http://blogs.msdn.com/b/biztalkbre/archive/2007/02/16/sample-deploying-business-rules-programmatically.aspx
Friday, January 7, 2011
Typed XML Facts
http://msdn.microsoft.com/en-us/library/aa561096%28v=BTS.70%29.aspx
Typed Facts
Typed facts are classes that implement the ITypedFact interface: TypedXmlDocument, DataConnection, TypedDataTable, and TypedDataRow.
The TypedXmlDocument class represents the XML document type in the Business Rules Framework. When you use a node of an XML document as an argument in a rule, two XPath expressions are created: the Selector and Field bindings.
If the node has no child nodes, a Selector binding (also known as an XmlDocument binding) is created to the node's parent node and a Field binding (also known as an XmlDocumentMember binding) is created to the node itself. This Field binding is relative to the Selector binding. If the node has child nodes, a Selector binding is created to the node and no Field binding is created.
Suppose that you have the following schema.

If the Income node is selected, only a Selector binding is created, because the node has child nodes. The default XPath expression in the XPath Selector property of the Property pane contains:
/*[local-name()='Root' and namespace-uri()='http://LoansProcessor.Case']/*[local-name()='Income' and namespace-uri()='']
However, if the Name node is selected, both a Selector binding and a Field binding are created. The binding information looks like.
Property | Value |
---|---|
XPath Field | *[local-name()='Name' and namespace-uri()=''] |
XPath Selector | /*[local-name()='Root' and namespace-uri()='http://LoansProcessor.Case'] |
You can change the default XPath expressions for the XML nodes before you drag the node into a rule argument, and the new binding information is placed in the policy. Note, however, that any edits that are made to the XPath expressions must be re-entered in the Business Rule Composer when the schema is reloaded.
When vocabulary definitions are created for XML nodes, the XPath expressions for the bindings have similar defaults based on the rules described earlier, but can be edited in the Vocabulary Definition Wizard. Changes to the expressions are placed in the vocabulary definition and are reflected in any rule arguments built from the definitions.
DataConnection is a .NET class provided in the RuleEngine library. It contains a .NET SqlConnection instance and a DataSet name. The DataSet name enables you to create a unique identifier for the SqlConnection and is used in defining the resulting type.
The DataConnection class provides a performance optimization to the Business Rule engine. Rather than asserting into the engine very large database tables (TypedDataTable class) that may contain many database rows (TypedDataRow class) that are not relevant to the policy, you can assert a lightweight DataConnection. When the engine evaluates a policy, it dynamically builds a SELECT query based on the rule predicates/actions and queries the DataConnection at execution. For example, suppose you have the following rule:
The following SQL query is generated by from the rule:
The results of the query are asserted back into the engine as data rows.
![]() |
---|
The use of an OleDbConnection in a DataConnection is not currently supported. |
When you select a database table/column to use in a rule condition or action, you can choose to bind to the object using either DataConnection or TypedDataTable by selecting "Data connection" or "Database table/row" from the Database binding type drop-down box in the Property Window for the Databases tab of Fact Explorer.
![]() |
---|
The DataConnection binding is used by default. |
You can assert an ADO.NET DataTable object into the engine, but it will be treated like any other .NET object. In most cases you will instead want to assert the rule engine class TypedDataTable.
TypedDataTable is a wrapper class that contains an ADO.NET DataTable. The constructor simply takes a DataTable. Any time a table or table column is used as a rule argument, the expression is evaluated against the individual TypedDataRow wrappers, and not against the TypedDataTable.