One of the lesser known but very powerful features of Biml is the ability to directly access the error reporting system of the compiler. With just a few lines of code, you can add your own validators that will cause errors, warnings, or notifications to appear when your requirements are not satisfied. You can also inspect any validation items that might have already been logged by the standard validators or custom validators that ran in earlier scripts. All of this functionality is exposed through the ValidationReporter property that is already present within any BimlScript you have authored.
ValidationReporter provides the "Report" method, which enables you to add new validation items to the error/warning/message list. The Report method has a variety of overloads, but there are two that are most useful. The first is a general purpose reporting method that takes a Severity and a string message as parameters. You can optionally provide string format arguments for convenience of message construction. Here is a the simplest BimlScript that will produce a custom error on build:
<Biml xmlns="http://schemas.varigence.com/biml.xsd"> <# ValidationReporter.Report(Severity.Error, "This is custom error!!"); #> </Biml>
While it illustrates the basic principle of custom errors, the code snippet above isn't particularly useful. Instead, we would prefer to create an error only when something has gone wrong - for example, when a table object is missing an expected annotation:
<Biml xmlns="http://schemas.varigence.com/biml.xsd"> <Packages> <# foreach (var table in RootNode.Tables) { #> <# if (table.GetTag("ImportantMetadata") == "") { ValidationReporter.Report().Report(Severity.Error, "Please ensure that he 'ImportantMetadata' Biml annotation tag is supplied for table '{0}'", table.Name); } #> <Package Name="Package for <#=table.Name #>" ConstraintMode="Parallel" /> <# } #> </Packages> </Biml>
This is better than the first example. The error only fires when a table object is added to our solution without the expected annotation. Furthermore, we use the .NET string formatting functionality to include the name of the offending table in the error message. However, the error message doesn't give us complete information about where to find the offending table(e.g. file path and line number). This is where our second useful Report method overload is useful. In addition to supplying a Severity and message, we can also supply a reference to the Biml object that is responsible for the error. When this is done, the compiler will automatically figure out the file/line/offset information for that object and include it in the error:
<Biml xmlns="http://schemas.varigence.com/biml.xsd"> <Packages> <# foreach (var table in RootNode.Tables) { #> <# if (table.GetTag("ImportantMetadata") == "") { ValidationReporter.Report(table, Severity.Error, "Please ensure that he 'ImportantMetadata' Biml annotation tag is supplied for table {0}", table.Name); } #> <Package Name="Package for <#=table.Name #>" ConstraintMode="Parallel" /> <# } #> </Packages> </Biml>
Perhaps you are using Mist and are wondering if Transformers can also leverage this functionality. The answer is "yes!" Here is a transformer that adds a couple of tracking columns to a table, but it first generates an error if the "ImportantMetadata" tag isn't specified on the table.
<#@ target type="Table" mergemode="LocalMerge" #> <# if (TargetNode.GetTag("ImportantMetadata") == "") { ValidationReporter.Report(TargetNode, Severity.Error, "Please ensure that he 'ImportantMetadata' Biml annotation tag is supplied for table {0}", TargetNode.Name); } #> <Table> <Columns> <Column Name="TrackingColumn1" /> <Column Name="TrackingColumn2" /> </Columns> </Table>
Remember that while all of the samples above used the presence or absence of an annotation tag as the criterion for raising an error, you can use whatever business logic makes sense for your scenario.
Perhaps you don't want to create an error but just want to issue a warning or a message. Instead of using Severity.Error, you can instead use Severity.Warning or Severity.Message. Alternatively, if the error is so catastrophic that it doesn't make sense to continue with any attempt to build any other files, you can use Severity.Fatal. Once the compiler finds a fatal error, it will immediately terminate all compilation and report that error. Be careful using this severity level, since it will prevent any later errors from being shown until the fatal error is corrected.
Finally, note that you can use ValidationReporter to inspect the errors/warnings/messages that have already been logged. This will be covered in greater depth in a subsequent walkthrough. For now, take a look at the following properties on ValidationReporter which are the most useful:
- ValidationReporter.HasErrors: Indicates whether or not any errors have been logged.
- ValidationReporter.HasWarnings: Indicates whether or not any warnings have been logged.
- ValidationReporter.Errors: The collection of all error items that have been logged.
- ValidationReporter.Warnings: The collection of all warning items that have been logged.
- ValidationReporter.ValidationItems: The collection of all validation items (errors, warnings, messages) that have been logged. Note that this collection is read/write. You can remove items from the list using the Remove method and can also modify the details of the included validation items.
- ValidationReporter.Reset(): Clears out all validation items (errors, warnings, messages) that have already been logged.
Comments
There are no comments yet.