- Set "suppress_trailing_delimiters" to "true" in your main node (works if empty lines are olny in the end).
- Set "suppress_empty_nodes" to "true" in schemainfo (should work if empty lines are in the middle as well, might cause isses if missing values in records are possible).
- Add special "empty" elements that correspond to empty lines to your schema (seems to be what Genuine Basil is doing).
- Clean the file up in receive pipeline (Might require some coding in your part).
Monday, January 10, 2011
Flat File dissasembling and empty rows problem
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!!!!!!!!!!!