.NET API General Reference

This article contains a list of examples for using the .NET API's various features. The .NET API can be used for many purposes, and the most common is to create secured access points for end-users via browser sessions. However, you can also use the API to create and manage schedules, generate config files and reports programmatically, edit existing reports, and more.

Note: This guide is kept up to date for the most current version available. For a list of changes in the API, see the Updating Guide.


Getting Started

Exago BI .NET applications must include a reference to the file WebReportsApi.dll, found in the bin folder of the application directory. This contains all the classes and method groups necessary for the examples in this article.

Optional: If you are connecting to a .NET Assembly Data Source, you will need to include a reference to WebReportsAsmi.dll. A reference to WebReports.dll is unnecessary, except in some rare cases that are not discussed in this guide.

The following Exago BI namespaces are used in the examples in this guide:

  • WebReports.Api
  • WebReports.Api.Reports
  • WebReports.Api.Common
  • WebReports.Api.Data
  • WebReports.Api.Roles
  • WebReports.Api.Programmability
  • WebReports.Api.Scheduler
  • WebReports.Api.ReportMgmt
  • WebReports.Api.Theme

As well as the following system namespaces:

  • System.Collections.Generic
  • System.Linq
  • System.Xml



API Object

An API object must be instantiated at the start of an API application since it is the access point for all API activity. API objects also encompass sessions which are used to encapsulate user-specific changes, such as security and permissions settings.

Note: Variable names, such as "api" and "report", are declared in their respective sections in this guide, then referenced throughout the remainder.


Basic constructor: appPath is the file path or virtual path to Exago base install.

Api api = new Api("appPath");

Specify a config file (other than the default WebReports.xml)

Api api = new Api("appPath", "configFn");

Custom config file and its location in Azure storage (must match web.config)

Api api = new Api("appPath", "configFn", "azurePath");

Let the host application configure log4net

Api api = new Api("appPath", bool isLogCustomConfig, "configFn");

Write to the default log file (v2018.1+: [Temp]\WebReportsApiLog.txt)

Logger logger = Log.GetLogger();
logger.Info("message"); // Writes at info level

API Action

The location to direct the Exago interface when first loaded. Edit or Execute actions work on an "active" report.

api.Action = wrApiAction.Home;

Hide tabbed UI

api.ShowTabs = false;

Active Report

Load a report from repository. Makes it the active report. "reportName" is the fully qualified path relative to the base report directory without the file extension. ReportObject is a generic class encompassing all report types.

ReportObject reportObject = api.ReportObjectFactory.LoadFromRepository("reportName");

Report is validated (checked for errors) unless overridden

ReportObject reportObject = api.ReportObjectFactory.LoadFromRepository("reportName", bool validate);

Manually validate


Check the errors list. See list of error types.

foreach (ReportValidationError error in report.ValidationErrors)
  string.Format("Error: {0}\n\t{1}\n\t{2}\n",

Get the report type

string reportType = reportObject.ReportType.ToString();

Cast the report object to higher level class

Report report = (Report)reportObject;

Validate the export type

bool IsReportAllowPdfExport = report.IsExportTypeAllowed(wrExportType.Pdf);
bool IsConfigAllowPdfExport = api.General.AllowPdfOutput;

Set the export type

report.ExportType = wrExportType.Pdf;

Launch Exago and Execute Report

To launch in a browser frame with specified Action, get the App URL to redirect the browser. Redirecting to the App URL closes the API to further changes. It should be the last thing done.

Get the App URL, with the default home page "ExagoHome.aspx"

string appUrl = api.GetUrlParamString();

Specify a custom Exago home page, "homePage" is the file name without the file extension

string appUrl = api.GetUrlParamString("homePage");

Set ShowErrorDetail to true, for more detailed user error messages

string appUrl = api.GetUrlParamString(null, true);
// This is equivalent to:
string appUrl = api.GetUrlParamString() + "?ShowErrorDetail=true";

Redirect the browser to the App URL (this will close the API to any further changes)

Frame.Src = baseUrl + appUrl;


As an alternative to Api Action, reports can be executed and the data returned directly to the application. This will not close the API. Since this does not launch Exago, report interactivity is not supported, only bare HTML, JSON, and so on. Acts on the specified report, not the active report.

string reportHtml = report.GetExecuteHtml();
string reportJson = report.GetExecuteJson();
byte[] reportData = report.GetExecuteData();
string[] reportSql = report.GetExecuteSql();

While report interactivity is not supported for the GetExecute functions, they will still trigger the following Server Events:

This functionality is included in order to ensure that reports are properly executing according to their design, which may be affected by these Server Events. For example, a report could trigger OnReportExecuteStart in order to modify the SQL generated, which would affect the data that is returned upon execution.


Sorts and Filters

You can add sorts and filters to reports at runtime. After making changes, save back to the API for execution in the session.


Changes could be saved back to the report file on disk


Save edited report as a new report

api.ReportObjectFactory.Copy(report, "newName", null);


See the existing sorts in a report

foreach (Sort sort in report.Sorts)
  string.Format("{0}, {1}\n", sort.SortText, sort.Direction);

Add a new sort to a report

report.Sorts.Add(new Sort(api.PageInfo)
  // Use object Alias name, as well as the field Alias name if set
  SortText  = objectName + '.' + fieldName,
  Direction = wrSortDirection.Ascending

Formula sort

SortText = string.Format("=Formula({{{0}.{1}}})", objectName, fieldName);

Sorts is an ordered list. Reorder the list to change the precedence of the sorts.

// Move a sort from position 3 to position 1
for (int i = 3; i > 1; i--)
  Sort temp = report.Sorts[i - 1];
  report.Sorts[i - 1] = report.Sorts[i];
  report.Sorts[i] = temp;


See the existing filters in a report

Note: As of v2018.2+, Filter objects now reference the property FilterText instead of Name.
string filterString = ""; // Build a filter summary string for display
foreach (Filter f in report.Filters)
  filterString += string.Format("{0}{1} {2} {3}{4}",
    new string('(', f.GroupStartCnt),
    f.Name, //f.FilterText (v2018.2+)
    f.GroupEndCnt > 0 ? new string(')', f.GroupEndCnt) : ' ' + f.AndOrValue + ' ');

Add a new filter

// Get the data field info (should first check that the report contains the entity)
EntityColumn field = report.Entities.GetEntity("objectName").GetColumn("fieldName");

report.Filters.Add(new Filter(api.PageInfo)
//FilterText (v2018.2+) Name = field.FullName, // Object and field Alias names
DataType = field.DataType, Operator = wrFilterOperator.OneOf, // This filter type takes multiple values DataValues = new DataValueCollection() { new DataValue(api.PageInfo, field) { Value = "value1" }, new DataValue(api.PageInfo, field) { Value = "value2" } } });

Group Min/Max filters

string groupFilterString = ""; // Build a filter summary string for display
int i = 0;
foreach (GroupFilter filter in report.GroupFilters)
  groupFilterString += string.Format("{0} {1} for each {2} {3}{4}",
    filter.Name, //filter.FilterText (v2018.2+)
    filter.IgnoreOtherGroups ? "ignoring other groupings" : "",
    (i < report.GroupFilters.Count()) ? ", " : "" );

Add a new group filter

// Should check that the entity and sorts exist on the report
EntityColumn field = report.Entities.GetEntity("objectName").GetColumn("fieldName");
Sort sort = report.Sorts.GetSort("sortName");

report.GroupFilters.Add(new GroupFilter(api.PageInfo)
//FilterText (v2018.2+) Name = field.FullName, // Object and field Alias names
Operator = wrGroupFilterOperator.Maximum, GroupName = sort.SortText // Or sort.Entities[0].Name, or "EntireDataSet" });

See Top N filter

// Build a filter summary string for display
// Reports limited to one Top N filter, max one foreach group per filter
string topNFilterString = "";
if (report.TopNItems.Count() > 0)
  if (report.TopNItems[0].UseTopNItem)
    TopNItem filter = report.TopNItems[0];
    topNFilterString = string.Format("{0} {1} {2}{3}",
      filter.Action == ? "Top" : "Bottom",
      (filter.ForEachGroup.Count() > 0) ? " for each " + filter.ForEachGroup[0] : "");

Add Top N filter

// Check that the cell exists
int cellId = report.Cells.GetCell(int row, int col).Id;

TopNItem topN = new TopNItem(api.PageInfo)
  Action       =,
  Number       = int N, // the N in "Top N"
  CellId       = cellId,
  ForEachGroup = new List<string> { },
  UseTopNItem  = true

// Must be at zero-index position
if (report.TopNItems.Count() == 0)
  report.TopNItems[0] = topN;



Change the values of config settings and parameters dynamically, which often depend on user access rights. A dynamic config can be thought of as an extension of a Role, although Roles aren't necessarily required.

Change config setting

api.General.anySetting = newValue;

See Config File and API Setting Reference for the full list of config settings.


Parameters are "buckets" for values that persist throughout a session, and are reachable by extensions. You can use them to set custom environment variables. userId and companyId are built-in parameters for storing user information.

Caution: The .NET namespace System.Web.UI.WebControls also contains a class called Parameter. You may wish to alias one or both classes to resolve name conflicts.
using wrParameter = WebReports.Api.Common.Parameter;

List config parameters

foreach (Parameter parameter in api.Parameters)
  string.Format("Name: {0}, Value: {1}\n", parameter.Id, parameter.Value);

Get a specific parameter by Id

Parameter parameter = api.Parameters.GetParameter("parameterId");

Add a new parameter

api.Parameters.Add(new Parameter(api.PageInfo)
  Id       = "foo", // No spaces
  DataType = (int)DataType.String,
  Value    = "bar"

Non-hidden parameters are usable on reports

parameter.IsHidden = false;

Prompting parameters will ask the user for a value when running a containing report

parameter.PromptText = "Enter a value";

Prompting parameters can display a selection list based on a data field or custom SQL

Entity entity = api.Entities.GetEntity("entityName");

parameter.DropdownDataSourceId      = entity.DataSourceId;
parameter.DropdownObjectType        = entity.ObjectType;
parameter.DropdownDbName            = entity.DbName;
parameter.DropdownValueField        = entity.GetColumn("colName").Name;
parameter.DropdownDisplayValueField = entity.GetColumn("colName").Name;

Save to Disk

Creates .xml and encrypted .enc files in the Temp directory. If isPermanent = true, creates the files in the Config directory, overwriting the existing config files.

api.SaveData(bool isPermanent);


Role Permissions

Roles are a neat way to encapsulate a collection of permissions. Roles also allow for some more fine grained control over data and folder access than the base config settings. Only one role can be active at a time.

Get a specific role

Role role = api.Roles.GetRole("roleId");

Create new role

api.Roles.Add(new Role(api.PageInfo) { Id = "roleId", IsActive = true });

Edit role settings

role.General.AnySetting = "value";

Folder/Object/Row Security

role.Security.Folders.IncludeAll = true;
role.Security.Folders.Add(new Folder() { Name = "folderPath", ReadOnly = true });
role.Security.DataObjects.Add(new DataObject() { Name = "objectName" });
role.Security.DataObjectRows.Add(new DataObjectRow()
  ObjectName   = "objectName",
  FilterString = "filterString"

Activate a role



Advanced Configuration

You can dynamically change data sources, objects, joins, etc., in the API, or simply use these settings to programmatically generate a config file.

Data Sources

View data source


Create a new data source

api.DataSources.Add(new DataSource(api.PageInfo)
  Name        = "dataSourceName",
  DbType      =  Constants.DatabaseType.SqlServer,
  DataConnStr = "connectionString"

Data Objects

View data objects

foreach (Entity entity in api.Entities)
  // three ways to identify an entity
  string.Format("Alias: {0}, Id: {1}, Database Name: {2}\n",
    entity.Name,  // Alias
    entity.Id,    // Id
    entity.DbName // Database Name

Get a specific data object

Note: This is the recommended way to get an entity by name, but there are other methods provided as well
Entity entity = api.Entities.GetEntity("entityAlias"); 
// Returns null if it does not exist // Best to retrieve entities by Alias name, because Aliases are unique and required // Ids are unique, but not required; Db names are required, but not unique (across sources)

View object fields

foreach (EntityColumn column in entity.Columns)
  string.Format("Alias: {0}, Database name: {1}\n",
    column.Name,      // Alias (or actual if no alias)
    column.ActualName // Database name

Create new data object

Note: As of 2019.1+, NewEntity() has the new required parameter EntityName.
Entity entity = api.Entities.NewEntity(entityName);

entity.DataSourceId = api.DataSources.GetDataSource("dataSourceName").Id;
entity.ObjectType   = DataObjectType.Table;
entity.DbName       = "databaseName"; // Required
entity.Name         = "aliasName";    // Required, unique
entity.Id           = "idName";       // Unique

// Add key column
entity.KeyColumns.Add(new KeyColumn(entity.GetColumn("colName").ActualFullName));

SQL Object

entity.SqlStmt = "SELECT * FROM Employees";

Add tenanting to object

entity.Tenants.Add(new EntityTenant(api.PageInfo,
   entity.GetColumn("colName").ActualFullName, // Get col by Alias but supply ActualFullName
  "parameterId" // Tenant parameter

Filter dropdowns

entity.FilterObjectType = DataObjectType.Table; // Table, View, Function, Procedure, etc.
entity.FilterDbName = "FilterObjectName";
// or custom SQL:
entity.FilterObjectType = DataObjectType.SqlStmt;
entity.FilterSqlStmt = "SELECT etc...";


See all config joins

// Build the join string for display
string joinString = "";

// All config joins; for report joins, use report.Joins
foreach (Join join in api.Joins.OrderByDescending(x => x.Weight)) // Order by weight
  foreach (JoinColumn col in join.JoinColumns)
    joinString += string.Format("{0} {1} {2}{3}\n",
      join.RelationType == 1 ? "(s)" : "" // 1-to-Many
  if (join.Weight > 0) joinString += ("Weight: " + join.Weight + "\n");

Get specific join

Join join = api.Joins.GetItem("fromEntity", "toEntity", false);

Create a new join

Note: For creation of Advanced Joins, see Advanced Joins.
Entity fromEntity = api.Entities.GetEntity("fromName");
Entity toEntity = api.Entities.GetEntity("toName");

Join newJoin = new Join(api.PageInfo)
  EntityFromName = fromEntity.Name,
  EntityToName   = toEntity.Name,
  Type           = (int)JoinType.Inner,
  RelationType   = 0, // 0: One-to-One, 1: One-to-Many
  Weight         = 0

// Add the key columns (legacy version, pre-v2017.2)
  new KeyColumn(fromEntity.GetColumn("fromColName").ActualFullName));
  new KeyColumn(toEntity.GetColumn("toColName").ActualFullName));

// Add the key columns (v2017.2+)
newJoin.JoinColumns.Add(new JoinColumn(
new KeyColumn(fromEntity.Name, fromEntity.GetColumn("fromColName").ActualFullName),
new KeyColumn(toEntity.Name, toEntity.GetColumn("toColName").ActualFullName)
// Add to the config; for reports, use report.Joins.Add() api.Joins.Add(newJoin); // If there is an active report, recreate the joins report.CreateJoins();

Custom Functions

Get a specific function

UdfFunction function = api.CustomFunctions.GetItem("functionName");

Create a new function

UdfFunction newFunction = new UdfFunction(api.PageInfo)
  Name        = "functionName",
  AvailableIn = UdfFunctionAvailableType.Formula, // Custom or filter function
  //MinArgs = 0, (deprecated in v2017.2)
//MaxArgs = 0, (deprecated in v2017.2)
ArgumentsJson = "[{'Name':'argName','Required':true,'Description':'desc'}]", // (v2017.2+) Language = CodeLanguage.CSharp.ToString(), ProgramCode = "Code();" }; // Add any references and namespaces newFunction.Namespaces.Add("Program.Namespace"); newFunction.References.Add("Reference.dll"); // Add to the config api.CustomFunctions.Add(newFunction);

Server Events

Get a specific server event

ServerEvent serverEvent = api.ServerEvents.GetByName("eventName");

Create a new server event

ServerEvent newEvent = new ServerEvent(api.PageInfo)
  Name      = "eventName",
  EventType =  ServerEventType.OnReportExecuteStart, // Global event, or "None"

// Custom code
newEvent.ServerCode.CustomCode.Language    =  CodeLanguage.CSharp;
newEvent.ServerCode.CustomCode.ProgramCode = "Code();";

//Add a namespace
// Code from data source newEvent.ServerCode.DataSourceId = api.DataSources.GetDataSource("eventsAssembly").Id; newEvent.ServerCode.FunctionName = "functionName"; // Add to config api.ServerEvents.Add(newEvent); // Add to a report (must be in config) report.ServerEvents.Add(new ReportServerEvent(api.PageInfo) { EventType = ServerEventType.OnDataCombined, EventId = api.ServerEvents.GetByName("eventName").Id });



View schedule list

List<Exception> exceptions;

// Build the job list string
string jobList = "";

foreach (List<JobInfo> schedule in api.ReportScheduler.GetJobList(out exceptions))
  foreach (JobInfo job in schedule.OrderBy(x => x.NextExecuteDate).ThenBy(x => x.Name))
    jobList += string.Format("Job '{0}' for report '{1}' ",

    switch (job.Status)
      case JobStatus.Completed:
        jobList += string.Format("ran on {0}, at host {1}.\n",
          job.LastExecuteDate.ToString("MMM d hh:mm tt"),
      case JobStatus.Ready:
        jobList += string.Format("ready to run on {0}.\n",
          job.NextExecuteDate.ToString("MMM d hh:mm tt")
       case JobStatus.Deleted:
       case JobStatus.Removed:
       case JobStatus.Abended:
       case JobStatus.UserAbort:
         jobList += string.Format("ended. Last run on {0}, at host {1}.\n",
           job.LastExecuteDate.ToString("MMM d hh:mm tt"),
         jobList += string.Format("status unknown.\n");

Create an immediate schedule (basic options)

// Run-once, immediately save to disk
string jobId; // Use to retrieve schedule info later for editing
int hostIdx;  // Assigned execution host id

ReportScheduleInfo newSchedule = new ReportScheduleInfoOnce()
  ScheduleName      = "Immediate Schedule",             // Schedule name
  ReportName        = @"Report\Full\Path",              // Report path
  ReportType        = wrReportType.Advanced,            // Report type
  RangeStartDate    = DateTime.Today,                   // Start date
  ScheduleTime      = new TimeSpan(DateTime.Now.Ticks), // Start time
  SendReportInEmail = false                             // Email or save

// Send to the scheduler; wrap in try/catch to handle exceptions
try {
    new ReportSchedule(api.PageInfo) { ScheduleInfo = newSchedule }, out jobId, out hostIdx);
catch (Exception) { }

Recurring schedules (additional options)


ReportScheduleInfo newSchedule = new ReportScheduleInfoDaily()
  ... // Include basic options
  // Range of recurrence, every N days, or every weekday
  DailyPattern = ReportScheduleInfo.DailyPatternType.EveryNDays,
  EveryNDays   = 2, // N days

  // End date (optional):
  RangeEndDate     = DateTime.Parse("December 25 2017"), // End on a specific date
  RangeNOccurences = 10,                                 // Or end after N occurrences

  // intraday recurrence (optional):
  RepeatEvery        = true, // Enable intraday recurrence
  RepeatEveryHours   = 4,    // Repeat every N hours
  RepeatEveryMinutes = 0,    // And N minutes
  RepeatEveryEndTime = new TimeSpan(DateTime.Parse("12:00 PM").Ticks) // Optional end time


ReportScheduleInfo newSchedule = new ReportScheduleInfoWeekly()
  ... // Include basic options
  ... // Optional end date, optional intraday recurrence
  EveryNWeeks = 1, // Every N weeks
  // On these days:          Sun    Mon   Tues   Wed    Thu   Fri    Sat
  IsDayOfWeek = new bool[] { false, true, false, false, true, false, false }


ReportScheduleInfo newSchedule = new ReportScheduleInfoMonthly()
  ... // Include basic options
  ... // Optional end date, optional intraday recurrence
  // Specific or relative day pattern
  MonthlyPattern = ReportScheduleInfo.MonthlyPatternType.SpecificDayOfMonth,
  // Specific: day X of every N months
  SpecificDayOfMonth   = 7, // Day of the month
  SpecificEveryNMonths = 2, // Every N months

  // Relative: the Xth day-of-week of every N months
  RelativeWeekOfMonth  = ReportScheduleInfo.WeekOfMonthType.First,  // Week of month
  RelativeDayOfWeek    = ReportScheduleInfo.DayOfWeekType.Weekday,  // Day of week
  RelativeEveryNMonths = 2                                          // Every N months


ReportScheduleInfo newSchedule = new ReportScheduleInfoYearly()
  ... // Include basic options
  ... // Optional end date, optional intraday recurrence
  // Specific or relative day pattern
  YearlyPattern = ReportScheduleInfo.YearlyPatternType.SpecificDayOfYear,

  // Specific: Month/Day of every year
  SpecificMonthOfYear = 3,  // Month of the year
  SpecificDayOfMonth  = 15, // Day of the month

  // Relative: the Xth day-of-week for month N
  RelativeWeekOfMonth = ReportScheduleInfo.WeekOfMonthType.Last, // Week of month
  RelativeDayOfWeek   = ReportScheduleInfo.DayOfWeekType.Friday, // Day of week
  RelativeMonthOfYear = 3                                        // Month N

Email job

newSchedule.SendReportInEmail = true;             // Enable email
newSchedule.EmailSubject      = "Subject Text";   // Email subject
newSchedule.EmailBody         = "Hello World!";   // Email body
newSchedule.EmailToList.Add(""); // To addresses
// newSchedule.EmailCCList.Add();                 // CC addresses
// newSchedule.EmailBccList.Add();                // BCC addresses

Batch email

// Batch addresses entity (must be in the batch report)
Entity batchAddresses = report.Entities.GetEntity("entityName");

newSchedule.IsBatchReport = true;                                 // Enable batch
newSchedule.BatchEmailToList.Add("");       // Summary recipients
// newSchedule.BatchEmailCcList.Add();                            // Summary cc recipients
newSchedule.BatchEntity = batchAddresses.Name;                    // Email address object
newSchedule.BatchField  = batchAddresses.GetColumn("Email").Name; // Email address field
newSchedule.IncludeReportAttachment = true;                       // Include the report

Access existing schedule by jobId

ReportScheduleInfo schedule = api.ReportScheduler.GetReportScheduleInfoByJobId("jobId");

Update an existing schedule

api.ReportScheduler.UpdateExistingSchedule(newSchedule, "jobIdToUpdate");

Delete an existing schedule



Managing Files and Folders

Initialize the manager class for the type of folder mgmt in use

// File System
ReportMgmtFileSystem manager = new ReportMgmtFileSystem(api.PageInfo);
// Database
ReportMgmtMethod manager = new ReportMgmtMethod(api.PageInfo);
// Cloud drive
ReportMgmtCloud manager = new ReportMgmtCloud(api.PageInfo);

View the full reports tree

XmlDocument tree = new XmlDocument() { InnerXml = manager.GetReportListXml() };

View themes list by type

List<string> evThemes = manager.GetThemeList(ReportTheme.ReportThemeType.ExpressView.ToString());

Get a specific theme (class depends on theme type)

ExpressViewTheme evTheme = (ExpressViewTheme)ReportTheme.GetTheme(
  api.PageInfo, ReportTheme.ReportThemeType.ExpressView, "themeName");

View list of templates

List<string> templates = manager.GetTemplateList();

Add a new folder

manager.AddFolder("parentFolder", "newFolderName");

Move or rename a folder

manager.RenameFolder("oldPath", "newPath");

Move or rename a report

manager.RenameReport("oldPath", "newPath");

Duplicate an existing report

api.ReportObjectFactory.Copy("reportName", "newName");

Save a new report to disk

api.ReportObjectFactory.Copy(report, "reportName", null);

Hidden Article Information

Article Author
Exago Development
created 2017-07-19 19:21:45 UTC
updated 2019-06-24 20:28:45 UTC

API, .NET, configuration, config, session, sort, filter, action, example, parameter, schedule, object, reference, settings, role, method, class, programmatically, scheduling, cheat, sheet, notes, codex, brief,
Have more questions? Submit a request