thinking in rules

Business Rule Reporting with Custom irAuthor Extensions

John Hauppa | 9/22/2015

The InRule authoring tool, irAuthor, comes with a built-in rule application report feature. This report feature generates an HTML report that contains most of the details about the business rules in a given rule application.

However, what if you want a report with alternate formatting, or that contains a few different details about the rules that are not in the default HTML report? In this post, we will explore an approach for creating a custom rule application report. In doing so, we will also review the powerful irAuthor Extension SDK that allows developers to add custom functionality directly into irAuthor.

For the report format, we will use a Word document instead of HTML. In Office 2007 and later, Word has supported the DOCX format. The DOCX file is really a ZIP file that happens to contain text files that represent the contents of the Word document. We could write code that parses text and XML files to do everything from scratch, but instead the Open XML SDK from Microsoft will give us most of the classes that we need to read and write Word documents. More information on the Open XML SDK is here.

While we are generating the report, we will include the rich text of the Business Language rules as our main focus. The InRule SDK can return the text for a language rule in raw, unformatted text, or in HTML. The HTML is the best choice for the report, since it maintains the formatting of the Business Language rule including indentions and bold highlighting. However, to use this HTML in Word, we would still need write some custom code to translate HTML elements into Open XML elements. Thankfully, there is another SDK available on Codeplex that can perform that translation. The DLLs for that SDK are available here.

Now that we have all the libraries that we need to work quickly, we can walk through the steps to implement an irAuthor Extension DLL that will add our custom report functionality into irAuthor.

Step 1: Decide on what type of irAuthor customization that the report will use

The “Home” ribbon in irAuthor already has a “Reports” area that contains other reports that irAuthor supports “out-of-the-box”. Let’s add a new button to this section that will let us generate our Word report.

Step 2: Create an irAuthor Extension Project

To add a new button to irAuthor, we will create an irAuthor Extension. An Extension is a .NET DLL file that contains classes that inherit from the InRule.Authoring.Windows.ExtensionBase class in the InRule SDK.

Create a new .NET 4 class library in Visual Studio. This new project should reference the following files in the InRule SDK:

  • InRule.Authoring.dll
  • InRule.Authoring.Windows.dll
  • InRule.Repository.dll
  • HtmlToOpenXml.dll
  • DocumentFormat.OpenXml.dll
  • From WPF – PresentationCore, PresentationFramework, WindowsBase

The solution in Visual Studio should look like this by the time is has all of its references:

Step 3: Add setup code to the Extension

Add a class to the project called Extension, and have that class inherit from ExtensionBase. Fill out the constructor of the class and then the Enable method on the ExtensionBase interface. This code will tell irAuthor to load a button into the Report ribbon, and allow code to be executed when the button is clicked.
Each Extension added into irAuthor should have a new GUID (globally unique identifier), and a unique name.

 public class Extension : ExtensionBase
{
        private VisualDelegateCommand _command;

        public Extension()
            : base(
                "InRule Word Report Writer", "Writes out a Word document that contains rules",
                new Guid("{968C6036-DF4A-4C8A-A90D-3B3465965CED}"))
        {
        }

        public override void Enable()
        {
            var group = IrAuthorShell.HomeTab.GetGroup("Reports");
            _command = new VisualDelegateCommand(RunReport, "Word Report",
                                                 ImageFactory.GetImageThisAssembly("Images/Report16.png"),
                                                 ImageFactory.GetImageThisAssembly("Images/Report32.png"));
            group.AddButton(_command);

            SelectionManager.SelectedItemChanged += WhenSelectedItemChanged;
        }

        void WhenSelectedItemChanged(object sender, InRule.Authoring.Services.SelectionChangedEventArgs e)
        {
             _command.IsEnabled = RuleApplicationService.RuleApplicationDef != null;
        } 

 

Step 4 : Write code to generate the custom Word report

Our VisualDelegateCommand created in the Enable method has been populated so that it will run a method called RunReport when the button is clicked.

In the RunReport method, we will prompt the user for a file name for the report. After the file name is provided, the report will be generated on a background thread. The InRule authoring SDK contains a class called BackgroundWorkerWaitWindow that presents the user with a progress bar and “cancel” button while the background thread builds the report.

The code below generates a Word document for the rule application. Each rule set that contains at least one Business Language rule is written out into the document. A few notes about this code:

  • The Open XML SDK is used to efficiently manipulate the Word document
  • The Template Engine in the InRule SDK is used to produce the HTML text for rules
  • The HtmlToOpenXml component is used to translate the Business Language HTML in the Open XML Word doc elements
  • Errors and completion messages are marshalled back to the user interface using the RunWorkerComplete callback built into the InRule Authoring SDK.
 private void RunReport(object obj)
        {
            try
            {
                var dialog = new SaveFileDialog {DefaultExt = ".docx", Filter = "Word documents (.docx)|*.docx"};
                if (dialog.ShowDialog() == true)
                {
                    var window = new BackgroundWorkerWaitWindow("Creating Report",
                                                                "Please wait while the rules are processed...", true, false);

                    window.DoWork += (sender, args) =>
                                         {
                                             using (var wordDoc = WordprocessingDocument.Create(dialog.FileName, WordprocessingDocumentType.Document))
                                             {
                                                 var mainPart = wordDoc.AddMainDocumentPart();
                                                 mainPart.Document = new Document();
                                                 var body = new Body();
                                                 mainPart.Document.Body = body;

                                                 var ruleAppDef = RuleApplicationService.RuleApplicationDef;
                                                 var templateEngine = new TemplateEngine();
                                                 templateEngine.LoadStandardTemplateCatalog();
                                                 templateEngine.LoadRuleApplication(ruleAppDef);

                                                 var converter = new HtmlConverter(mainPart);
                                                 foreach (EntityDef entity in ruleAppDef.Entities)
                                                 {
                                                     var ruleSets = entity.GetAllRuleSets();
                                                     foreach (var ruleSetDef in ruleSets)
                                                     {
                                                         if (
                                                             ruleSetDef.Rules.Any(
                                                                 r => r.GetType() == typeof(LanguageRuleDef)))
                                                         {
                                                             var ruleSetParagraph = body.AppendChild(new Paragraph());
                                                             var ruleSetRun = ruleSetParagraph.AppendChild(new Run());
                                                             ruleSetRun.RunProperties = new RunProperties
                                                                                            {
                                                                                                Bold = new Bold(),
                                                                                                Color =
                                                                                                    new Color
                                                                                                        {
                                                                                                            Val =
                                                                                                                "B0E0E6"
                                                                                                        },
                                                                                                FontSize =
                                                                                                    new FontSize
                                                                                                        {
                                                                                                            Val
                                                                                                                =
                                                                                                                "28"
                                                                                                        }
                                                                                            };

                                                             ruleSetRun.AppendChild(
                                                                 new Text(string.Format("Rule Set: {0}", ruleSetDef.Name)));

                                                             foreach (var rule in ruleSetDef.Rules)
                                                             {
                                                                 if (rule is LanguageRuleDef)
                                                                 {
                                                                     var output =
                                                                         RuleAppReport.GetBusinessLanguageText(
                                                                             (LanguageRuleDef)rule,
                                                                             templateEngine,
                                                                             TextOutputFormat.Html);

                                                                     var pars = converter.Parse(output);
                                                                     foreach (var p in pars)
                                                                     {
                                                                         body.AppendChild(p);
                                                                     }
                                                                 }
                                                             }
                                                         }
                                                     }
                                                 }


                                                 foreach (var r in wordDoc.MainDocumentPart.Document.Descendants<Run>())
                                                 {
                                                     var rPr = new RunProperties(new RunFonts { Ascii = "Segoe UI" });
                                                     r.PrependChild(rPr);
                                                 }

                                                 wordDoc.Close();
                                             }
                                         };

                    window.RunWorkerCompleted +=
                        (sender, e) =>
                        {
                            if (e.Error != null)
                            {
                                MessageBoxFactory.Show(
                                    string.Format("Report failed with the following error:\r\n{0}", e.Error.ToString()),
                                    "Word Rule Export Failed");
                            }
                            else
                            {
                                MessageBoxFactory.Show(
                                    string.Format("Report file written to {0}", dialog.FileName),
                                    "Word Rule Export Completed");
                            }
                        };

                    window.ShowDialog();
                }
            }
            catch (Exception ex)
            {
                MessageBoxFactory.Show(ex.ToString(), "There was an error running the Word Rule Export Report",
                                       MessageBoxFactoryImage.Error);
            }

Step 5 : Deploy and register the irAuthor Extension

Once the code is ready, compile the project into a DLL. Copy the DLL into the irAuthor Extensions directory which will be located at C:\Program Files (x86)\InRule\irAuthor\Extensions by default.



Start irAuthor, then navigate to File -> Extensions. The new report extension should now be available to select in the Extension list. Once the extension has been checked, it should load into the ribbon in irAuthor. There should be a new button in the Report group on the Home ribbon.

Step 6 : Generating the Word report

Now that the Extension is published, a user can generate a Word report by clicking the new ribbon button. The user will be prompted for a file name. After all the language rules in the rule application are solved, the Word doc will be written to disk.
The code in the Extension can be further customized to match the requirements of your custom report. An example of a snippet from the Word doc is included below:

Conclusion

The irAuthor tool from InRule is filled with powerful features for visualizing, authoring, and testing rules. However, it can also be easily extended with custom functionality using the Extensions classes in the InRule SDK.


comments powered by Disqus