Managing Business Rules with Toolbar

The purpose of the Toolbar is to give rule authors an easy to use UI that provides basic rule management functionality such as creating, editing, and deleting rules. It also can be used to select (load) an existing rule, and name and describe new or existing rules.

The Toolbar does not actually store your rules anywhere, nor does it delete or update them in their physical location. Instead, it simply provides you, the developer, with the rule's data every time a rule author wants to work with that rule. When receiving that data, you have to decide where to store that rule, or where to load it from, or whether this particular rule author has the right to do anything with that rule at all.

The Toolbar can be removed from Code Effects' UI by setting the RuleEditor.ShowToolBar property to False in ASP.NET or MVC. It is shown by default unless the value of RuleEditor.Mode is set to Filter. You have to implement your own controls, such as Save and Load buttons, and "connect" those controls with the server-side code if you hide the Toolbar. The Data Filtering demo projects use this scenario.

If shown, the Toolbar always appears on top of the Rule Area. It consists of several UI elements:

  • Rules menu. This menu provides options to create new rules and load existing ones, if there are any. The number of options to create new rules and labels of those options depends on the Mode property of the RuleEditor class. If the Mode is set to RuleType.Execution, the Toolbar will include two options - New execution type rule... and New evaluation type rule.... Selecting one of those options will clear the Rule Area for the new rule of the selected type. If the Mode is set to RuleType.Evaluation or RuleType.Loop, the Toolbar will include only one option named New rule.... Selecting that option will clear the Rule Area for a new rule of evaluation type in Evaluation mode or execution type in Loop mode. As you can see, it is important to understand Rule Editor's modes and how they operate. You can easily change the labels of all these menu options to your own values by employing the Help XML.

    You can also include options in this menu to load existing rules for editing. It is entirely up to you, the developer, to decide which rules to load in this menu, as long as they correlate with the current value of the RuleEditor.Mode. You can load any type of rule if the Mode is set to RuleType.Execution. But Code Effects component may not work properly if you load any execution type rule into the Rules menu while the Mode is set to RuleType.Evaluation (its default value). Both of our demo projects provide the code to load rules into the Rules menu of the Toolbar. Here is an excerpt from the ASP.NET demo (from OnLoad event handler in the /Default.aspx.cs code-behind file):

    this.ruleControl.ToolBarRules = this.ruleControl.Mode == RuleType.Execution ?
    	Storage.GetAllRules() : this.ruleControl.ContextMenuRules;

    The MVC demo uses the LoadMenuRules() method declared in PostController and used in the controller's constructor, as well as almost all POST actions of that controller, to load existing rules into the Rules menu:

    private void LoadMenuRules()
    {
    	ViewBag.ToolBarRules = this.storage.GetAllRules();
    	...
    }

    In ASP.NET you don't need to do anything else in order to pass the rules to the client - everything will be done automatically. Because the view and the controller are semi-disconnected in MVC, you need to tell the RuleEditor which collection of rules to use in the Rules menu on the client:

    <div>
    	@{
    		Html.CodeEffects().RuleEditor()
    			.Id("ruleEditor")
    			.SaveAction("Save""Post")
    			.DeleteAction("Delete""Post")
    			.LoadAction("Load""Post")
    			.Mode(RuleType.Execution)
    			.ToolBarRules(ViewBag.ToolBarRules)
    			.Rule(ViewBag.Rule)
    			.Render();
    	}
    </div>

    RuleEditor.ToolBarRules is the property that you need to populate for rules to appear in the Rules menu on the client. It's of the ICollection<MenuItem> type.

    In ASP.NET, selecting one of the existing rules from the Rules menu raises the LoadRule event of the RuleEditor class, passing the ID of the selected rule to the server with the RuleEventArgs arguments class.

    private void LoadRule(object sender, RuleEventArgs e)
    {
    	this.ruleControl.LoadRuleXml(Storage.LoadRuleXml(e.RuleID));
    }

    In MVC, selecting one of the rules posts the view and passes the rule ID to the LoadAction as a parameter.

    [HttpGet]
    public ActionResult Load(string id)
    {
    	// Load rule from storage
    	string ruleXml = this.storage.LoadRuleXml(id);
     
    	// Create a new model and store it in the bag
    	ViewBag.Rule = RuleModel.Create(ruleXml, typeof(SourceObjectType));
     
    	return View("Index");
    }
  • Name text box. Rule authors can name rules by typing the rule name into the Name text box. The name is stored in Rule XML. By default it's a required field unless the Toolbar is hidden. Set RuleEditor.RuleNameIsRequired to False to make this field optional. The default "empty" label of the box can easily be changed with the Help XML.

  • Description text box. Rule authors can also give rules short descriptions by typing them into the Description box. This field is optional. The Rule Editor displays rule descriptions in the Help String when rule authors move through the list of existing rules in the Rules menu with the mouse or keyboard. The rule editor also shows descriptions of reusable rules when a rule author hovers the mouse over the rule in the Rule Area. The default "empty" label of the box can easily be changed with the Help XML.

  • Save button. The Save button allows rule authors to notify the server code that the current rule needs to be saved. Code Effects component does not actually save the rule for you since it has no knowledge of the infrastructure or systems involved in your rule processing. Instead, it just passes the rule data to the server and makes it available to your code as a compact string-based XML document, together with some other useful data. It's up to you how to handle that data.

    In ASP.NET, clicking the Save button raises the SaveRule event of the RuleEditor class. The event arguments of the SaveEventArgs type contain everything you need to successfully save the rule, including Rule XML as a separate document or just a single node.

    private void SaveRule(object sender, SaveEventArgs e)
    {
    	if(this.ruleControl.IsEmpty || !this.ruleControl.IsValid)
    		return;
    
    	string file = "C:\\MyRules.xml";
     
    	if(!File.Exists(file)) File.WriteAllText(file, e.RuleXmlAsDocument);
    	else
    	{
    		// Load the rules document (in this example
    		// all rules are stored in one big file)
    		XmlDocument xml = new XmlDocument();
    		xml.Load(file);
     
    		// Create a temp node and give it the rule as its inner XML
    		XmlElement temp = xml.CreateElement("temp");
    		temp.InnerXml = e.RuleXmlAsNode;
     
    		// Check if the document already contains this rule
    		foreach(XmlNode node in xml.DocumentElement.ChildNodes)
    		{
    			if(node.Attributes["id"].Value == e.RuleID)
    			{
    				node.InnerXml = temp.FirstChild.InnerXml;
     
    				// Replace the whole doc with the new one
    				File.Delete(file);
    				xml.Save(file);
     
    				// We are done
    				return;
    			}
    		}
     
    		// This is a new rule, just append it to the doc
    		xml.DocumentElement.AppendChild(temp.FirstChild);
    		File.Delete(file);
    		xml.Save(file);
    	}
    }

    In MVC, clicking the Save button posts the view and passes an instance of the RuleModel to the SaveAction of the controller. Notice that even though the type of the parameter is RuleModel, its name must be the server ID of the RuleEditor that you used in the view (see the MVC snippet in the Rules menu section of this topic). This is the default behavior of MVC when a custom TypeConverter is used (and it is used in Code Effects). We certainly could have let you incorporate the rule data into your own view model and enjoy the "true" MVC experience, but that would unnecessarily complicate things. Remember that ASP.NET MVC is not really a "component-friendly" pattern, so we rely on a naming convention in order to simplify integration.

    // "ruleEditor" is the ID of the RuleEditor instance declared in the view.
    [HttpPost]
    public ActionResult Save(RuleModel ruleEditor)
    {
    	// At this point the rule model cannot possibly know which type
    	// to use as its source object
    	// You must "bind" the type of the source to the instance of the RuleModel
    	ruleEditor.BindSource(typeof(Patient));
    	ViewBag.Rule = ruleEditor;
     
    	if (ruleEditor.IsEmpty() || !ruleEditor.IsValid())
    	{
    		ViewBag.Message = "The rule is empty or invalid";
    		return View("Index");
    	}
     
    	// This code sample assumes that we store all our rules in a single XML file
    	string file = "C:\\MyRules.xml";
     
    	if(!System.IO.File.Exists(file))
    		System.IO.File.WriteAllText(file, ruleEditor.GetRuleXml());
    	else
    	{
    		XmlDocument xml = new XmlDocument();
    		xml.Load(file);
     
    		XmlElement temp = xml.CreateElement("temp");
     
    		// See our MVC demo for implementation of the ExtractRuleNode method
    		temp.InnerXml = this.ExtractRuleNode(ruleEditor.GetRuleXml()).OuterXml;
     
    		// Check if a rule with the same ID already exists
    		foreach(XmlNode node in xml.DocumentElement.ChildNodes)
    		{
    			if(node.Attributes["id"].Value == ruleEditor.Id)
    			{
    				node.InnerXml = temp.FirstChild.InnerXml;
    				System.IO.File.Delete(file);
    				xml.Save(file);
    				return View("Index");
    			}
    		}
     
    		xml.DocumentElement.AppendChild(temp.FirstChild);
    		System.IO.File.Delete(file);
    		xml.Save(file);
    	}
     
    	return View("Index");
    }
  • Delete button. The Delete button allows rule authors to notify the server code that the current rule needs to be deleted. As with every other piece of Toolbar's functionality, Code Effects component does not (and cannot) actually delete your rule. Instead, it lets your code know which rule the user wants to delete. This button is only visible on the Toolbar when a previously saved rule is loaded into the Rule Area.

    In ASP.NET, clicking the Delete button raises the DeleteRule event of the RuleEditor class, passing the ID of the rule that needs to be deleted with the RuleEventArgs arguments class.

    private void DeleteRule(object sender, RuleEventArgs e)
    {
    	// This code sample assumes that we store all our rules in a single XML file
    	string file = "C:\\MyRules.xml";
     
    	XmlDocument xml = new XmlDocument();
    	xml.Load(file);
     
    	// Check if this rule is still referenced in other rules
    	if(e.IsEvaluationTypeRule == null || !(bool)e.IsEvaluationTypeRule)
    	{
    		foreach(XmlNode node in xml.DocumentElement.ChildNodes)
    			if(node.Attributes["id"].Value != e.RuleID &&
    				node.InnerXml.Contains(e.RuleID))
    					throw new RuleException(
    						"The rule is still referenced in other rules.");
    	}
     
    	// Find and remove the rule
    	foreach(XmlNode node in xml.DocumentElement.ChildNodes)
    	{
    		if(node.Attributes["id"].Value == e.RuleID)
    		{
    			xml.DocumentElement.RemoveChild(node);
    			File.Delete(file);
    			xml.Save(file);
    			return;
    		}
    	}
    	throw new RuleException("Could not find the rule with ID " + e.RuleID);
    }

    In MVC, clicking the Delete button posts the view and passes the rule ID to DeleteAction as a parameter.

    [HttpGet]
    public ActionResult Delete(string id)
    {
    	string file = "C:\\MyRules.xml";
     
    	XmlDocument xml = new XmlDocument();
    	xml.Load(file);
     
    	// Check if this rule is still referenced in other rules
    	foreach(XmlNode node in xml.DocumentElement.ChildNodes)
    		if(node.Attributes["id"].Value != id && node.InnerXml.Contains(id))
    			throw new Exception("The rule is still referenced in other rules.");
     
    	// Find and remove the rule
    	foreach(XmlNode node in xml.DocumentElement.ChildNodes)
    	{
    		if(node.Attributes["id"].Value == id)
    		{
    			xml.DocumentElement.RemoveChild(node);
    			System.IO.File.Delete(file);
    			xml.Save(file);
    			return View("Index");
    		}
    	}
     
    	// Create a new model and store it in the bag
    	ViewBag.Rule = RuleModel.Create(typeof(Patient));
     
    	return View("Index");
    }
Post your support requests on Stackoverflow.com. You can also post your comments and product feedback using the form at the bottom of this page.
Comments: 0
Name (optional):
Comment (URLs are allowed and must start with http:// or https://; all tags will be encoded):
Remaining character count: