Report expressions
RDL reports are mostly expressions: =Fields!Sales.Value, =Sum(Fields!Sales.Value, "Region"), =Globals!PageNumber & " of " & Globals!TotalPages. DocWright.Reporting evaluates them with an interpreter: no compiler, no reflection and no code generation, with identical results on every runtime.
Report processing evaluates expressions for you. Use the engine directly to evaluate an expression in a designer, a preview or a test.
Parse and evaluate
using System.Globalization;
using DocWright.Reporting.Expressions;
// Parse once (syntax, names and constants are checked here), evaluate many times.
ReportExpression expression = ReportExpression.Parse(
"=Format(Parameters!Amount.Value * 1.1, \"C\") & \" for \" & Parameters!Region.Label");
var context = new ReportExpressionContext();
context.Parameters["Amount"] = new ReportParameterValue(1250);
context.Parameters["Region"] = new ReportParameterValue("W", "West");
Console.WriteLine(ReportExpressionEvaluator.Evaluate(expression, context));
// The culture is never the machine's: en-US unless you say otherwise.
context.Culture = CultureInfo.GetCultureInfo("de-DE");
Console.WriteLine(ReportExpressionEvaluator.Evaluate(expression, context));
// What the expression reads, with positions for a designer's rename.
foreach (ReportExpressionReference reference in expression.References)
{
Console.WriteLine($" {reference.Kind} {reference.Name} at {reference.Position}");
}
Output
$1,375.00 for West
1.375,00 € for West
Parameter Amount at 8
Parameter Region at 56
DXP9106: The expression reads the execution time, and none was supplied: set ReportExpressionContext.ExecutionTime. The clock is never read.
2026-10-01 09:00
- Parse once, evaluate many times.
Parsechecks syntax, names, argument counts and constants. A parsed expression is immutable and safe to share across threads. - Text not starting with
=is a literal and evaluates to itself. - Results are the .NET values Visual Basic produces:
Integerisint,DateisDateTime,Nothingisnull. - Names are case-sensitive, as in Reporting Services.
References,UsesAggregates,UsesPageGlobals,UsesCustomCodeandUsesExecutionTimedescribe what an expression depends on.
Nothing comes from the machine
The culture defaults to en-US, which is what Reporting Services uses for a report with no <Language>, measured. Now(), Today() and Globals!ExecutionTime return the ExecutionTime you supply. Without one, they are refused rather than given the clock:
using DocWright.Reporting.Expressions;
// Now() is never the machine's clock: it is the ExecutionTime you supply.
try
{
ReportExpressionEvaluator.Evaluate(ReportExpression.Parse("=Year(Now())"), new ReportExpressionContext());
}
catch (ReportExpressionException error)
{
Console.WriteLine($"{error.DiagnosticCode}: {error.Message}");
}
var timed = new ReportExpressionContext { ExecutionTime = new DateTime(2026, 10, 1, 9, 0, 0) };
Console.WriteLine(ReportExpressionEvaluator.Evaluate(ReportExpression.Parse("=Format(Now(), \"yyyy-MM-dd HH:mm\")"), timed));
This differs from Reporting Services on purpose: binding every date function to one instant makes a report reproducible.
Custom code
A report's <Code> block is Visual Basic from the file, and running it in a server would mean running whatever an uploaded file contains. DocWright never executes it. Implement ReportCodeHost to provide the functions you trust. The SSRS tutorial shows one.
Limits
An expression is a program written by someone else, so evaluation is bounded, and these limits are on by default:
| Limit | Default |
|---|---|
MaxReportExpressionDepth |
256 levels |
MaxReportExpressionSteps |
10,000,000 per evaluation |
MaxReportExpressionAllocation |
16,000,000 characters and array elements per evaluation |
=Space(2000000000) is refused rather than allocating four gigabytes.