At the time of this writing, version 2.0 of Web Rule control was released as beta. All described features and APIs may change in the final release without further notice.
Version 2.0 of Web Rule control includes lots of new features. You can test it and download its MVC 3 and ASP.NET 4.0 demo projects at
Test.CodeEffects.com. As always, any feedback sent to us using our
support form is
GREATLY appreciated.
Here is what's new in version 2.0:
- .NET 4.0. Version 2.0 will not support .NET 3.5 and below. We will continue to support all previous versions, though. The current version of Web Rule (v 1.5) will still be offered in our online store as a discounted option after the final version 2.0 is released. Version 1.5 will also continue to receive related updates and bug fixes.
- MVC. Web Rule now supports ASP.NET MVC. You can now use Web Rule in traditional HTTP post-based MVC applications as well as running Web Rule as a JavaScript object in client-side applications based on MVC platform. Both our demo projects illustrate client-side capabilities of Web Rule (see /Views/Ajax/Index.cshtml view in MVC demo and /Ajax.aspx page in ASP.NET demo for implementation details).
- New engine. Web Rule now comes with our brand new rule evaluation engine. It's much faster than the current engine which already has an industry-average performance. It also allows developers not only evaluate business rules against source objects but also filter collections of "in-memory" objects based on rules (data filtering is described below). And it will make the future development of Web Rule technology easier and more cost effective.
Coding against the new engine is a bit different, comparing to the current engine. To evaluate a rule, in version 1.5 you would type:
CodeEffects.Rule.Common.Result r = CodeEffects.Rule.Evaluator.Evaluate(sourceInstance, ruleXml);
if (r.Success)
{
}
The new engine lets you do the same in two ways. First, you can create a new instance of Evaluator<TSource> type and call its Evaluate method like this:
CodeEffects.Rule.Core.Evaluator<MySourceObject> e =
new CodeEffects.Rule.Core.Evaluator<MySourceObject>(ruleXml);
bool success = e.Evaluate(sourceInstance);
Although it's similar to the old way, it's easy to notice that the new Evaluator class is generic, resides in CodeEffects.Rule.Core namespace, and doesn't have static Evaluate and Execute methods like the old one did. In fact, it doesn't have the Execute method with any accessor at all - the new rule format (described below) allows the engine to figure out the type of rule automatically. This eliminates the need for developers to make one extra decision in their code: If the rule is of execution type then call evaluator.Execute(...) else call evaluator.Evaluate(...). Just call the Evaluate method for any type of rule you have - the new engine will figure out the rest.
Version 2.0 also adds a new extension method. It's called Filter. I'm going to talk about it later in this post.
The above mentioned way to evaluate rules (the "instance" way) is great when you have an "in-memory" collection of source objects of the same type and need to get the result of evaluation of each of those objects against the same business rule. The constructor of the Evaluator class that I used here takes rule XML as its parameter, so it can compile that rule before iterating through objects in the collection. This allows reusing of the same compiled rule for each source instance, saving you a little bit of time and great deal of CPU cycles:
using System.Linq;
using CodeEffects.Rule.Core;
...
Evaluator<MySourceObject> e = new Evaluator<MySourceObject>(ruleXml);
listOfSourceObjects.ForEach(source => { bool success = e.Evaluate(source); });
Note that if you only need to know which of the instances (or how many of them) passed (or didn't pass) the rule, you are better off using the new Filter method. But again, I'll get to that shortly.
The other way to evaluate a source against your rule is to use Evaluate extension method (the "extension" way):
using CodeEffects.Rule.Core;
...
bool success = sourceInstance.Evaluate(ruleXml);
Just by looking at the code above, you can probably guess that this way the engine gets initialized on each call. This makes sense if you have one or more source instances and each one needs to be evaluated against a different rule.
If you must use the old engine - don't worry. The old Evaluator class is still where it's always been - in the main CodeEffects.Rule namespace. It works the same way it always did but now it has been deprecated. Because the Result class and its original purpose weren't really popular, it didn't find its way into the new model. It's still there but we made it obsolete as well. The new Evaluate method simply returns a Boolean value, indicating the success or failure of evaluation of the rule.
- New rule XML format. This is part of the new engine. The new format is less verbal, so XML is smaller in size. It's structured around the more traditional "nested" approach when it comes to describing business rules. We will (finally) publish proper XSD schema, so you'll be able to validate your rules against it without even loading Web Rule. Also, you'll be able to enjoy full IntelliSense support when you work with rules. Not that we encourage you to manually edit rules' XML (we do not!) but it's nice to have this feature, anyway.
The old format is still supported by ASP.NET part of Web Rule. You can load rules of either format using the same LoadRuleXml() or LoadRuleFile() methods without any changes. But you need to pass a new parameter of RuleFormatType type when you're getting rule's XML using the GetRuleXml() method:
string ruleXml = webRuleControl.GetRuleXml(RuleFormatType.FormatVersion2);
It's easy to convert old rules into the new format:
string oldXml = YourStorage.GetOldRule();
webRuleControl.LoadRuleXml(oldXml);
string newXml = webRuleControl.GetRuleXml(RuleFormatType.FormatVersion2);
YourStorage.SaveNewRule(newXml);
Each rule now has its own ID of System.String type. It's automatically created and maintained by Web Rule. Its default value is System.Guid.ToString() but you can always replace this default value with any of your own. This ID is used when saving, deleting and loading rules. Both MVC and ASP.NET RuleEditor classes supply and understand this rule ID. See our demo projects for details.
- New RuleEditor class. Because of MVC support, version 2.0 introduces two new namespaces: CodeEffects.Rule.Asp and CodeEffects.Rule.Mvc. Each namespace includes its own RuleEditor class. For ASP.NET control, the CodeEffects.Rule.Asp.RuleEditor is a replacement of the old CodeEffects.Rule.AspControl. As I said earlier, all existing functionality of the AspControl class is still there but the class is now deprecated. Eventually, it'll be phased out.
Declaration of Web Rule control in ASP.NET page is pretty much the same now as it was before. The difference is in namespaces:
<%@ Register assembly="CodeEffects.Rule" namespace="CodeEffects.Rule.Asp" tagprefix="rule" %>
<rule:RuleEditor ID="ruleControl" runat="server" SourceAssembly="Assembly"
SourceType="Assembly.Source" Mode="Execution" />
You also need to subscribe to and handle RuleEditor's SaveRule, LoadRule and DeleteRule events if you use reusable rules (discussed later in this post). Download our ASP.NET demo project to see detailed implementation. Notice the new Mode property. I'll talk about it later in this post.
In MVC, declaration of the Web Rule is somewhat more complicated. First, you have to create a rule model in your controller:
using CodeEffects.Rule.Models;
...
[HttpGet]
public ActionResult Index()
{
ViewBag.Rule = RuleModel.Create(typeof(MySourceObject));
return View();
}
This is where you tell Web Rule which source object it should use. Don't forget to register CodeEffects namespaces in web.config:
...
<pages pageBaseType="System.Web.Mvc.WebViewPage">
<namespaces>
...
<add namespace="CodeEffects.Rule.Mvc" />
<add namespace="CodeEffects.Rule.Common"/>
</namespaces>
</pages>
...
Second, declare Web Rule in your view (assuming that the view uses some master view):
@{
Html.CodeEffects().Styles()
.SetTheme(ThemeType.Blue)
.Render();
}
<div>
<span style="color:Red;">Some info message</span>
</div>
<div>
@{
Html.CodeEffects().RuleEditor()
.Id("ruleEditor")
.SaveAction("SaveAction", "MyController")
.DeleteAction("DeleteAction", "MyController")
.LoadAction("LoadAction", "MyController")
.Mode(RuleType.Execution)
.Rule(ViewBag.Rule)
.Render();
}
</div>
@{
Html.CodeEffects().Scripts().Render();
}
It would probably be a better idea to use a view model instead of ViewBag if your model contains more than just a Web Rule. The /Views/Filter/Index.cshtml view in our MVC demo project uses such view model.
As you can see, declaration of Web Rule in MVC view consists of three blocks of code. The first one tells MVC which theme Web Rule should use (this is optional). The second actually inserts Web Rule component into view's HTML. And the third code writes client data. Save, Delete, and Load actions are discussed later in this post.
In a nutshell, when declaring Web Rule in MVC you must supply its ID and RuleModel instance. Save, Delete and Load actions are required, too, unless this Web Rule is client-only ( .RuleEditor().ClientOnly(true) ) or nested rules are not used (described later in the post). All other properties of Web Rule are optional. The Html.Codeffects().Scripts().Render() call is required in all situations (can be invoked from anywhere inside the view).
- Reusable rules. Version 2.0 has added support for "reusable" (or "nested") rules. Developers can allow rule authors to reuse any rule of evaluation type as a field in any other rule. For example, suppose that a lot of your large rules use this simple condition: Age is greater than 18. Obviously, if tomorrow the value of 18 changes to 17, you'd need to update all those rules with the new value. And that pretty much goes against the core philosophy of business rules.
In version 2.0 you can simply save that condition as a separate rule of evaluation type with the name like "Is age legal" and reuse it in all your other rules of any type as a field, like this: If Zip code is "30040" and Is age legal is True then Register else Decline("We accept only local applications from adults").
By dynamically adding items to the ContextMenuRules collection of RuleEditor, developers have complete control over which rules users can reuse. If ContextMenuRules has items, they show up in context menu as regular rule fields. Filter them by rules' ownership, user roles, date of creation, etc. before adding to the collection. It cannot get simpler than that. Just make sure you don't add rules of execution type to that collection - having execution type rule reused in any other rule will result in exception during the rule evaluation. The "Eval type" item in the screenshot below is an example of such reusable rule:
- Tool Bar. Until version 2.0 Web Rule didn't really offer any UI for rule management. For example, you had to implement your own grid if you needed to list all rules that are available to users. Version 2.0 adds Tool Bar - the UI tool that helps rule authors to manage their rules.
The UI that you see the first time you load Web Rule on your page looks like this:
The Tool Bar is at the top. Help String is right under it. The Rule Area is at the bottom. In 2.0, the Help String can only be displayed at the top of the Rule Area. The AspControl.HelpPosition property was deprecated, as well as HelpPosition enumerator.
Tool Bar contains several elements: Rules menu, rule title and rule description boxes, and Save and Delete buttons (the Delete button is only visible when an existing rule is loaded).
It's very easy to create and manage a business rule using the Tool Bar. To begin, click the Rules menu in the Tool Bar and select the execution or evaluation type rule (there will be only one "New rule..." option if the Mode of the RuleEditor is set to Evaluation):
Assuming that you selected the execution type, add a new rule inside of the Rule Area. Name it "Test Rule" and give it some description:
(I'm using some test source for these screenshots, so obviously rules here are completely bogus.) Click the Save button to save this rule. Details of saving rules differ between ASP.NET and MVC models but in general Web Rule supplies rule data in form of event arguments (ASP.NET) or action parameter (MVC). It is up to you, the developer, where and how to store your rules. Download our demo projects to see examples of how this could be done.
After saving the rule, add it to the ToolBarRules collection of RuleEditor (see demo projects for code examples). This will add this rule to Rules menu, so users could select it to load it into the Rule Area for editing. Note that this is the execution type rule and cannot be "reusable". Therefore, we won't add it to the ContextMenuRules collection of RuleEditor.
Select the Test Rule from the Rules menu to load it into the Rule Area:
Web Rule supplies ID of the rule that was requested. You can retrieve the rule from your storage by this ID and load it into the Rule Area. Again, see demo projects for examples on how to do that - it's dead simple. Notice the Delete button. As with rule loading, Web Rule will give you the ID of the rule to be deleted if this button is clicked. It's up to you how to load and delete rules.
Rule names are used as item labels in context menus. Rule descriptions are displayed in the Help String (if it's enabled) when the user highlights the correspondent rule in a context menu.
All labels and default names that you see on the Tool Bar are customizable and can be changed by altering the Help XML file (described later in this post). This will be helpful in multilingual applications.
You can disable Tool Bar completely and go back to the old UI. To do that, set the ShowToolBar property of the RuleEditor class to false. The /Filter.aspx page of the ASP.NET demo and /Views/Filter/Index.cshtml view of the MVC demo both use the old UI.
- Data filtering. Version 2.0 includes a brand new functionality - data filtering. It's implemented as an extension method and uses evaluation type rules created with Web Rule control to filter "in-memory" collections of source objects. This can be very useful if, for instance, your system receives collections of source objects and you need to keep only those that pass a business rule:
using System.Linq;
using System.Collections.Generic;
using CodeEffects.Rule.Core;
...
List<MySourceObject> list = SomeDataService.GetSourceObjects();
string ruleXml = YourStorage.GetRule();
List<MySourceObject> goodOnes = list.Filter(ruleXml).ToList<MySourceObject>();
Please note that the current implementation of the Filter extension doesn't support SQL yet. This is planned for future releases of Web Rule. Therefore, for performance reasons it's not advisable to include filtration in Entity Framework or Linq to SQL statements that select data from large tables. For instance, the following code is perfectly valid. It returns collection of customers filtered by some rule. But you have to remember that the current version of the Filter method does not include proper Where clause in resulting SQL. Therefore, EF loads the entire Customers table into server's memory first and only then it invokes the Filter method to filter objects:
using System.Linq;
using CodeEffects.Rule.Core;
...
string ruleXml = YourStorage.GetRule();
AdventureWorksEntities db = new AdventureWorksEntities();
var filtered = (from c in db.Customers select c).Filter(ruleXml);
Again, even though it's fine to filter a table that only has a thousand records, you will face some performance hit if you attempt to filter records in larger tables. One way to combat this limitation - before we add SQL support - is to create a SQL view that would pre-filter records down to a manageable number with its own Where clause and then filter its output with business rules.
- Attributes. As you know, you can use custom attributes to add metadata to your source objects. New functionality was added to several attribute classes in order to better support new RuleEditor classes and new rule engine. (All Web Rule attribute classes are in the CodeEffects.Rule.Attributes namespace.)
Default values. Due to its relative insignificance and potential for a great deal of confusion, we decided not to implement the previously announced DefaultValue property of the FieldAttribute class.
Display names. The DisplayName property of ClauseAttribute and FlowAttribute as well as all named string properties of the OperatorAttribute class (such as Is, IsNot, Contains, etc.) were deprecated and will be ignored by the engine. Use the new capabilities of Help XML as described elsewhere in this post.
Field, method, and rule descriptions. The ActionAttribute, ExternalActionAttribute, ExternalMethodAttribute, FieldAttribute, MethodAttribute, ParentAttribute and SystemMethodAttribute now have the Description property of the System.String type. Use this property to display description of the field, action or method in the Help String as rule author goes through context menu items using the mouse or the arrow keys. This property is ignored by Web Rule if RuleEditor.ShowHelpString property is set to False. See also the discussion on reusable rules in the Tool Bar topic elsewhere in this post.
SourceAttribute. Web Rule used to rely on the SourceAttribute quite a bit. Not anymore. All properties of this attribute class were deprecated, except for the MaxTypeNestingLevel. Functionality of all "label" properties is now handled by the Help XML(see its topic later in this post).
RuleEditor got a new Mode property of the RuleType type that replaces the Type property of the SourceAttribute. Use this Mode property to set the type of rule that rule author can create. Note that if the Mode is set to Execution and Tool Bar is enabled (RuleEditor.ShowToolBar is set to True) authors can create both evaluation and execution types of rules.
Included operators. As I mentioned above, all named string properties of the OperatorAttribute class were deprecated. As you might remember, those properties had two purposes: to set display names for all operators if the default names need to be changed for any reason, and to limit the amount of client data that Web Rule inserts into page's HTML by including only those operators that are actually needed. Since those properties are obsolete now, we included only one property IncludedOperators of the CodeEffects.Rule.Common.IncludedOperator enum type that replaces all of them.
- Help XML. Help XML plays a much bigger role in version 2.0 that it used to. To refresh your memory, Help XML contains all help and error messages that are displayed on the Help String (if it's enabled). In version 2.0, most of the display names and labels moved from their attribute classes to that file as well. Everything you see on the Tool Bar, Help String and Rule Area is customizable through the Help XML. This gives developers much greater flexibility when they build multilingual applications or when the default messages are not good enough.
Help XML is a resource XML file embedded into the CodeEffects.Rule.dll assembly. You can create your own version (or any number of versions) of this file and tell Web Rule to read UI labels from your file instead of the default one. For example, if your application displays its UI in multiple languages, create a separate help file for each language and load the corresponding one for each user. To create your own version of the Help XML, first you need to get the content of the default file. To do that, instantiate Web Rule in any .NET code and call it's GetHelpXml() method:
using CodeEffects.Rule.Asp;
...
RuleEditor control = new RuleEditor
{
ID = "ruleEditor",
SourceType = "MyAssembly",
SourceAssembly = "MySourceObject"
};
string helpXml = control.GetHelpXml();
Run this code in a debugger. Save the value of helpXml variable as XML file and use this file with Web Rule. In ASP.NET it'll look like this:
<%@ Register assembly="CodeEffects.Rule" namespace="CodeEffects.Rule.Asp" tagprefix="rule" %>
<rule:RuleEditor ID="ruleControl" runat="server"
SourceAssembly="Assembly"
SourceType="Assembly.Source"
Mode="Execution"
HelpXmlFile="/MyHelpFolder/MyHelp.config" />
And in MVC:
@{
Html.CodeEffects().RuleEditor()
.Id("ruleEditor")
.SaveAction("SaveAction", "MyController")
.DeleteAction("DeleteAction", "MyController")
.LoadAction("LoadAction", "MyController")
.Mode(RuleType.Execution)
.Help("/MyHelpFolder/MyHelp.config")
.Rule(ViewBag.Rule)
.Render();
}
Help XML can be also set as System.Xml.XmlDocument type by assigning value of the RuleEditor.HelpXml property if working with virtual paths is not possible or not desirable.
See documentation of Help XML (available soon at Rule.CodeEffects.com) for details on help format.