带通用 UI 的规则引擎






4.84/5 (10投票s)
具有通用规则的通用控件。
引言
规则引擎是一种软件组件,它允许非程序员在业务流程管理 (BPM) 系统中添加或更改业务逻辑。规则是描述业务策略或程序的语句。业务逻辑描述了与数据库中的数据相关联的操作序列,以执行该规则。
业务规则引擎通过将业务规则的执行代码与业务流程管理系统的其余部分分离来工作。这允许最终用户更改业务规则,而无需请求程序员的帮助。当进行更改时,引擎将评估更改对系统中其他规则的影响,并在存在冲突时向用户发出警告。
通用UI引擎是UI组件,它们在各种类型的用户界面中是通用的。这意味着:拥有可以使用通用接口访问的通用UI组件。通用UI引擎可以映射到自定义规则引擎。
背景
使用代码
具有通用UI的规则引擎由以下部分组成:
- UI配置语言 (XML)
- 规则配置语言 (XML)
- UI引擎/解析器 (XSLT转换)
- 规则解析器 (C#)
这是UI配置xml文件的示例:
<FORM>
<PAGES>
<PAGE title="GENERIC RULE ENGINE CONFIGURATION 1" id="page_1">
<FIELDS>
<FIELD type="DropDownList" label="Catagory" cType="USERCONTROL">
<PROPERTIES>
<PROPERTY name="ID">DropDownList1</PROPERTY>
<PROPERTY name="BINDING_TYPE">pre</PROPERTY>
<PROPERTY name="BINDING">candidate.cat</PROPERTY>
<PROPERTY name="PRE_RULEID">2</PROPERTY>
<PROPERTY name="AutopostBack">true</PROPERTY>
</PROPERTIES>
</FIELD>
<FIELD type="DropDownList" label="Sub-Catagory" cType="USERCONTROL">
<PROPERTIES>
<PROPERTY name="ID">DropDownList2</PROPERTY>
<PROPERTY name="BINDING_TYPE">pre</PROPERTY>
<PROPERTY name="BINDING">candidate.subcat</PROPERTY>
<PROPERTY name="PRE_RULEID">3</PROPERTY>
</PROPERTIES>
</FIELD></FIELDS>
</PAGE>
;</PAGES>
</FORM>
此UI配置文件通过XSLS(可扩展样式表语言转换 (XSLT) 允许您将源XML文档的内容转换为格式或结构不同的另一个文档)转换为HTML。
这是示例XSLT文件
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:asp="remove">
<xsl:output method="html" indent="yes" encoding="utf-8"
omit-xml-declaration="yes" version="4.0"></xsl:output>
<xsl:param name="pageid">page_1</xsl:param>
<xsl:template match="/">
<!-- start form page -->
<table id="dyfrm" cellpadding="0" cellspacing="5">
<!-- set title of the current page -->
<tr>
<td colspan="3" align="center" style="font-size:25px">
<xsl:value-of select="FORM/PAGES/PAGE[@id=$pageid]/@title" />
</td>
</tr>
<tr><td colspan="3" style="height:20px"></td></tr>
<!-- iterate through page fields -->
<xsl:for-each select="FORM/PAGES/PAGE[@id=$pageid]/FIELDS/FIELD">
<xsl:element name="tr">
<xsl:attribute name="id">TR_<xsl:value-of select=
"PROPERTIES/PROPERTY[@name='ID']"></xsl:value-of></xsl:attribute>
<!-- hide the row -->
<xsl:if test="@display='none'">
<xsl:attribute name="style">display:none;</xsl:attribute>
</xsl:if>
<xsl:choose>
<!-- html control -->
<xsl:when test="@type='HTML'">
<td colspan="3">
<!-- #include file="<xsl:value-of select="@src"></xsl:value-of>" -->;
</td>
</xsl:when>
<xsl:when test="@type='GridView'">
<td colspan="3" align="center">
<!-- field element -->
<xsl:element name="asp:{@type}">
<xsl:attribute name="runat">server</xsl:attribute>
<xsl:for-each select="./PROPERTIES/PROPERTY">
<xsl:attribute name="{@name}">
<xsl:value-of select="current()"></xsl:value-of>
</xsl:attribute>
</xsl:for-each>
<xsl:for-each select="./LISTITEMS/LISTITEM">
<asp:ListItem value="{@value}">
<xsl:value-of select="current()"></xsl:value-of>
</asp:ListItem>
</xsl:for-each>
</xsl:element>
</td>
</xsl:when>
<!-- other controls -->
<xsl:otherwise>
<!-- field label column -->
<td valign="top">
<xsl:value-of select="@label" />
</td>
<!-- field column -->
<td>
<!-- field element -->
<xsl:element name="asp:{@type}">
<xsl:attribute name="runat">server</xsl:attribute>
<xsl:for-each select="./PROPERTIES/PROPERTY">
<xsl:attribute name="{@name}"><xsl:value-of select=
"current()"></xsl:value-of></xsl:attribute>
</xsl:for-each>
<xsl:for-each select="./LISTITEMS/LISTITEM">
<asp:ListItem value="{@value}"><xsl:value-of select=
"current()"></xsl:value-of></asp:ListItem>
</xsl:for-each>
</xsl:element>
</td>
<!-- validation message column -->
<td>
<xsl:if test="@required='true'">
<asp:RequiredFieldValidator ErrorMessage="Required" runat="server"
ControlToValidate="{PROPERTIES/PROPERTY[@name='ID']}" />
</xsl:if>
<xsl:if test="@validation='Date'">
<asp:CompareValidator ErrorMessage="Dates Only" runat="server"
Operator="DataTypeCheck" Type="Date"
ControlToValidate="{PROPERTIES/PROPERTY[@name='ID']}" />
</xsl:if>
<xsl:if test="@validation='Number'">
<asp:CompareValidator ErrorMessage="Numbers Only" runat="server"
Operator="DataTypeCheck"
Type="Integer" ControlToValidate="{PROPERTIES/PROPERTY[@name='ID']}" />
</xsl:if>
<xsl:if test="@validation='Currency'">
<asp:CompareValidator ErrorMessage="Currency Only" runat="server"
Operator="DataTypeCheck" Type="Currency"
ControlToValidate="{PROPERTIES/PROPERTY[@name='ID']}" />
</xsl:if>
</td>
</xsl:otherwise>
</xsl:choose>
</xsl:element>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>
规则引擎配置示例
<Rule id="4">
<PROPERTIES type="selectquery">
<PROPERTY name="DataTextField">#</PROPERTY>
<PROPERTY name="tablename">TABLE1</PROPERTY>
<PROPERTY name="condition">FIELD1=?</PROPERTY>
<PARAMETERS>
<!--master OR DropdownList.Value OR DropdownList.Text-->
<!--RadioButtonList= (Value, Text)-->
<!--CheckBoxList= (Value, Text)-->
<!--TextBox= (Text)-->
<!--GridView= ("Columnname".Text)-->
<!--ListBox= (Value, Text)-->
<PARAMETER type="string">100001</PARAMETER>
</PARAMETERS>
<BUSINESS>
<CONDITION ID="1">
<!--DropDownList= (Value, Text)-->
<!--RadioButtonList= (Value, Text)-->
<!--CheckBoxList= (Value, Text)-->
<!--TextBox= (Text)-->
<!--ListBox= (Value, Text)-->
<!--op={>,<,==}-->
<IF leftTerm="DropDownList5.Value" op="=="
rightTerm="2" type="System.Int32">
<!--Type={int, Text, Rule}-->
<THEN TYPE="Text">FIELD2</THEN>
<ELSE>
<THEN TYPE="Text">FIELD3</THEN>
</ELSE>
</IF>
</CONDITION>
</BUSINESS>
</PROPERTIES>
</Rule>
解释:
这里#被编译后的BUSINESS标签替换,?被PARAMETER标签和类型替换。
每个解析后的控件都保存为具有以下属性的结构。
public struct UControls
{
public string ID;
public string ControlType;
public string BindingFields;
public string pre_Rulesid;
public string post_Rulesid;
public string DependingControl;
};
以下代码用于解析UI引擎配置文件,并为每个控件初始化自定义UControl结构,并且属性pre_Rulesid和post_Rulesid定义了规则配置文件中的规则ID。这里的pre_Ruleid规则是指在页面加载时加载的规则,例如,下拉列表。
var DynControls = from c in q.Descendants("FIELD")
where (string)c.Attribute("cType") == "USERCONTROL"
select c;
foreach (XElement control in DynControls.Descendants("PROPERTIES"))
{
var DynProperties = from c in control.Descendants("PROPERTY")
//where (string)c.Attribute("name") == "ID"
select c;
UControls tempUC = new UControls();
tempUC.ControlType = control.Parent.Attribute("type").Value;
if (control.Parent.Attribute("DependingControl") != null)
{
tempUC.DependingControl = control.Parent.Attribute("DependingControl").Value;
}
foreach (XElement properties in DynProperties)
{
if (properties.Attribute("name").Value == "ID")
{
tempUC.ID = properties.Value;
}
if (properties.Attribute("name").Value == "BINDING")
{
tempUC.BindingFields = properties.Value;
}
if (properties.Attribute("name").Value == "POST_RULEID")
{
tempUC.post_Rulesid = properties.Value;
}
if (properties.Attribute("name").Value == "PRE_RULEID")
{
tempUC.pre_Rulesid = properties.Value;
}
}
现在我们有了控件结构数组和规则。这里是规则解析器,它通过规则配置文件生成数据库查询和业务条件评估函数。
public List<string> QuerySelectionRuleByControl(
IEnumerable<XElement> SelectRule, object gb,DataSet master)
{
var R_properties = from c in SelectRule.Descendants("PROPERTY")
select c;
GlobalData gd = (GlobalData)gb;
StringBuilder sb = new StringBuilder();
List<string> li = new List<string>();
string dataTextField = "";
string dataValueField = "";
string ruletype = "";
sb.Append("Select * ");
foreach (XElement x in R_properties)
{
ruletype = x.Parent.Attribute("type").Value;
if (x.Attribute("name").Value == "DataTextField")
{
dataTextField = ApplyLogic(x.Value, SelectRule, master, gd);
}
if (x.Attribute("name").Value == "DataValueField")
{
dataValueField = ApplyLogic(x.Value, SelectRule, master, gd);
}
if (x.Attribute("name").Value == "tablename")
{
sb.Append("from " + x.Value);
}
if (x.Attribute("name").Value == "condition")
{
string t = x.Value;
if (x.Value.Contains("?"))
{
var R_Parameters = from c in SelectRule.Descendants(
"PARAMETERS").Descendants("PARAMETER")
select c;
foreach (XElement xe in R_Parameters)
{
Control ParameterValueControl=null;
foreach (Control c in gd.xsl.ControlHolder.Controls[1].Controls)
{
if (c.ID == xe.Value.Split('.')[0].ToString())
{
ParameterValueControl = c;
}
}
if (ParameterValueControl != null)
{
if (ParameterValueControl.GetType().Name == "DropDownList")
{
string[] tempstr = xe.Value.Split('.');
DropDownList dTEmp = (DropDownList)ParameterValueControl;
if (tempstr[1] == "Value")
{
string pData = dTEmp.SelectedItem.Value;
t = x.Value.Insert(x.Value.IndexOf('?'), "'" + pData + "'");
}
if (tempstr[1] == "Text")
{
string pData = dTEmp.SelectedItem.Text;
t = x.Value.Insert(x.Value.IndexOf('?'), "'" + pData + "'");
}
}
}
if (xe.Value.Contains("RadioButtonList"))
{
string[] tempstr = xe.Value.Split('.');
RadioButtonList dTEmp = (RadioButtonList)ParameterValueControl;
if (tempstr[1] == "Value")
{
string pData = dTEmp.SelectedItem.Value;
t = x.Value.Insert(x.Value.IndexOf('?'), "'" + pData + "'");
}
if (tempstr[1] == "Text")
{
string pData = dTEmp.SelectedItem.Text;
t = x.Value.Insert(x.Value.IndexOf('?'), "'" + pData + "'");
}
}
if (xe.Value.Contains("CheckBoxList"))
{
string[] tempstr = xe.Value.Split('.');
CheckBoxList dTEmp = (CheckBoxList)ParameterValueControl;
if (tempstr[1] == "Value")
{
string pData = dTEmp.SelectedItem.Value;
t = x.Value.Insert(x.Value.IndexOf('?'), "'" + pData + "'");
}
if (tempstr[1] == "Text")
{
string pData = dTEmp.SelectedItem.Text;
t = x.Value.Insert(x.Value.IndexOf('?'), "'" + pData + "'");
}
}
if (xe.Value.Contains("TextBox"))
{
string[] tempstr = xe.Value.Split('.');
TextBox dTEmp = (TextBox)ParameterValueControl;
if (tempstr[1] == "Text")
{
string pData = dTEmp.Text;
t = x.Value.Insert(x.Value.IndexOf('?'), "'" + pData + "'");
}
}
if (xe.Value.Contains("ListBox"))
{
string[] tempstr = xe.Value.Split('.');
ListBox dTEmp = (ListBox)ParameterValueControl;
if (tempstr[1] == "Value")
{
string pData = dTEmp.SelectedItem.Value;
t = x.Value.Insert(x.Value.IndexOf('?'), "'" + pData + "'");
}
if (tempstr[1] == "Text")
{
string pData = dTEmp.SelectedItem.Text;
t = x.Value.Insert(x.Value.IndexOf('?'), "'" + pData + "'");
}
} if (xe.Value.Contains("GridView"))
{
string[] tempstr = xe.Value.Split('.');
GridView dTEmp = (GridView)ParameterValueControl;
if (tempstr[2] == "Text")
{
DataRow dr = ((DataRowView)dTEmp.SelectedRow.DataItem).Row;
string pData = dr[tempstr[1]].ToString();
t = x.Value.Insert(x.Value.IndexOf('?'), "'" + pData + "'");
}
}
if (xe.Value.Contains("Image"))
{
//string[] tempstr = xe.Value.Split('.');
//DropDownList dTEmp = (DropDownList)cObj;
//if (tempstr[1] == "Value")
//{
// string pData = dTEmp.SelectedItem.Value;
// t = x.Value.Insert(x.Value.IndexOf('?'), "'" + pData + "'");
//}
//if (tempstr[1] == "Text")
//{
// string pData = dTEmp.SelectedItem.Text;
// t = x.Value.Insert(x.Value.IndexOf('?'), "'" + pData + "'");
//}
} if (xe.Value.Contains("BulletedList"))
{
//string[] tempstr = xe.Value.Split('.');
//DropDownList dTEmp = (DropDownList)cObj;
//if (tempstr[1] == "Value")
//{
// string pData = dTEmp.SelectedItem.Value;
// t = x.Value.Insert(x.Value.IndexOf('?'), "'" + pData + "'");
//}
//if (tempstr[1] == "Text")
//{
// string pData = dTEmp.SelectedItem.Text;
// t = x.Value.Insert(x.Value.IndexOf('?'), "'" + pData + "'");
//}
}
if (xe.Value.Contains("HiddenField"))
{
//string[] tempstr = xe.Value.Split('.');
//DropDownList dTEmp = (DropDownList)cObj;
//if (tempstr[1] == "Value")
//{
// string pData = dTEmp.SelectedItem.Value;
// t = x.Value.Insert(x.Value.IndexOf('?'), "'" + pData + "'");
//}
//if (tempstr[1] == "Text")
//{
// string pData = dTEmp.SelectedItem.Text;
// t = x.Value.Insert(x.Value.IndexOf('?'), "'" + pData + "'");
//}
}
if (xe.Value.Contains("Literal"))
{
//string[] tempstr = xe.Value.Split('.');
//DropDownList dTEmp = (DropDownList)cObj;
//if (tempstr[1] == "Value")
//{
// string pData = dTEmp.SelectedItem.Value;
// t = x.Value.Insert(x.Value.IndexOf('?'), "'" + pData + "'");
//}
//if (tempstr[1] == "Text")
//{
// string pData = dTEmp.SelectedItem.Text;
// t = x.Value.Insert(x.Value.IndexOf('?'), "'" + pData + "'");
//}
}
if (xe.Value.Contains("Calender"))
{
//string[] tempstr = xe.Value.Split('.');
//DropDownList dTEmp = (DropDownList)cObj;
//if (tempstr[1] == "Value")
//{
// string pData = dTEmp.SelectedItem.Value;
// t = x.Value.Insert(x.Value.IndexOf('?'), "'" + pData + "'");
//}
//if (tempstr[1] == "Text")
//{
// string pData = dTEmp.SelectedItem.Text;
// t = x.Value.Insert(x.Value.IndexOf('?'), "'" + pData + "'");
//}
}
if (xe.Value.Contains("master"))
{
string[] tempstr = xe.Value.Split('.');
string pData = master.Tables[tempstr[1]].Rows[0][tempstr[2]].ToString();
if (xe.Attribute("type").Value == "string")
{
t = x.Value.Insert(x.Value.IndexOf('?'), "'" + pData + "'");
t = t.Remove(t.IndexOf('?'), 1);
}
if (xe.Attribute("type").Value == "int")
{
t = x.Value.Insert(x.Value.IndexOf('?'), pData);
t = t.Remove(t.IndexOf('?'), 1);
}
}
}
}
sb.Append(" where " + ApplyLogic(t.Replace("?", ""), SelectRule, master, gd));
}
}
li.Add(sb.ToString());
li.Add(dataTextField);
li.Add(dataValueField);
li.Add(ruletype);
return li;
}
private string ApplyLogic(string str_blogic,
IEnumerable<XElement> SelectRule, DataSet master,GlobalData gd)
{
if(str_blogic.Contains("#"))
{
var R_Business = from c in SelectRule.Descendants("BUSINESS").Descendants("CONDITION")
select c;
foreach(XElement b_Condition in R_Business)
{
if (b_Condition.Descendants().First().Name.ToString() == "IF")
{
string leftterm = b_Condition.Descendants().First().Attribute("leftTerm").Value;
string rightterm = b_Condition.Descendants().First().Attribute("rightTerm").Value;
string op = b_Condition.Descendants().First().Attribute("op").Value;
string ConditionType = b_Condition.Descendants().First().Attribute("type").Value;
Control ParameterValueControlleft=null;
Control ParameterValueControlright=null;
foreach (Control c in gd.xsl.ControlHolder.Controls[1].Controls)
{
if (c.ID == leftterm.Split('.')[0].ToString())
{
ParameterValueControlleft = c;
}
if (c.ID == rightterm.Split('.')[0].ToString())
{
ParameterValueControlright = c;
}
}
if (ParameterValueControlleft != null)
{
if (ParameterValueControlleft.GetType().Name == "DropDownList")
{
string[] tempstr = leftterm.Split('.');
DropDownList dTEmp = (DropDownList)ParameterValueControlleft;
if (tempstr[1] == "Value")
{
leftterm = dTEmp.SelectedItem.Value;
}
if (tempstr[1] == "Text")
{
leftterm = dTEmp.SelectedItem.Text;
}
}
}
if (ParameterValueControlright != null)
{
if (ParameterValueControlright.GetType().Name == "DropDownList")
{
string[] tempstr = rightterm.Split('.');
DropDownList dTEmp = (DropDownList)ParameterValueControlright;
if (tempstr[1] == "Value")
{
rightterm = dTEmp.SelectedItem.Value;
}
if (tempstr[1] == "Text")
{
rightterm = dTEmp.SelectedItem.Text;
}
}
}
if (leftterm.Contains("master"))
{
string[] tempstr = leftterm.Split('.');
leftterm = master.Tables[tempstr[1]].Rows[0][tempstr[2]].ToString();
}
if (rightterm.Contains("master"))
{
string[] tempstr = rightterm.Split('.');
rightterm = master.Tables[tempstr[1]].Rows[0][tempstr[2]].ToString();
}
bool testResult = TestCondition(leftterm, rightterm,ConditionType, op);
if (testResult)
{
if (b_Condition.Descendants().First().Descendants().First().Attribute(
"TYPE").Value == "Text")
{
string str1 = b_Condition.Descendants().First().Descendants().First().Value;
str_blogic = str_blogic.Insert(str_blogic.IndexOf('#'), str1);
str_blogic= str_blogic.Remove(str_blogic.IndexOf('#'), 1);
}
}
else
{
XElement x= b_Condition.Descendants().Descendants("ELSE").First();
if (b_Condition.Descendants().Descendants("ELSE").First(
).Descendants().First().Attribute("TYPE").Value == "Text")
{
string str1 = b_Condition.Descendants().Descendants(
"ELSE").First().Descendants().First().Value;
str_blogic = str_blogic.Insert(str_blogic.IndexOf('#'), str1);
str_blogic = str_blogic.Remove(str_blogic.IndexOf('#'), 1);
}
}
}
}
return str_blogic;
}
else
{
return str_blogic;
}
}
private bool TestCondition(string obj1, string obj2, string objType, string op)
{
Type tt = Type.GetType(objType);
object lvalue = Convert.ChangeType(obj1, Type.GetType(objType));
object rvalue = Convert.ChangeType(obj2, Type.GetType(objType));
switch (op)
{
case "==":
bool temp = (lvalue.ToString() == rvalue.ToString());
return temp;
case ">":
if (lvalue.GetType().Name == "Int32")
return int.Parse(lvalue.ToString()) > int.Parse(rvalue.ToString());
else if (lvalue.GetType().Name == "Double")
return double.Parse(lvalue.ToString()) > double.Parse(rvalue.ToString());
else if (lvalue.GetType().Name == "Char")
return char.Parse(lvalue.ToString()) > char.Parse(rvalue.ToString());
else
return false;
case "<":
if (lvalue.GetType().Name == "Int32")
return int.Parse(lvalue.ToString()) < int.Parse(rvalue.ToString());
else if (lvalue.GetType().Name == "Double")
return double.Parse(lvalue.ToString()) < double.Parse(rvalue.ToString());
else if (lvalue.GetType().Name == "Char")
return char.Parse(lvalue.ToString()) < char.Parse(rvalue.ToString());
else
return false;
case "<=":
if (lvalue.GetType().Name == "Int32")
return int.Parse(lvalue.ToString()) <= int.Parse(rvalue.ToString());
else if (lvalue.GetType().Name == "Double")
return double.Parse(lvalue.ToString()) <= double.Parse(rvalue.ToString());
else if (lvalue.GetType().Name == "Char")
return char.Parse(lvalue.ToString()) <= char.Parse(rvalue.ToString());
else
return false;
case ">=":
if (lvalue.GetType().Name == "Int32")
return int.Parse(lvalue.ToString()) >= int.Parse(rvalue.ToString());
else if (lvalue.GetType().Name == "Double")
return double.Parse(lvalue.ToString()) >= double.Parse(rvalue.ToString());
else if (lvalue.GetType().Name == "Char")
return char.Parse(lvalue.ToString()) >= char.Parse(rvalue.ToString());
else
return false;
default:
return false;
}
}
关注点
这个概念有很多改进的空间,可以为不同的用户生成通用的网页。