# .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

### Contents

- API Object
- Sorts and Filters
- Settings
- Role Permissions
- Advanced Configuration
- Scheduling
- Managing Files and Folders

# API Object

An API object must be instantiated at the start of an API application, as 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.

## Constructors

Basic constructor. appPath is 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

Where to direct the Exago interface when first loaded. Edit or Execute actions work on "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 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

    reportObject.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",
        error.ReportErrorType.ToString(),
        error.Data1,
        error.Data2);
    }

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;

## GetExecute

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 or JSON, etc. 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();

# 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.

    api.ReportObjectFactory.SaveToApi(report);

Changes could be saved back to the report file on disk

    api.ReportObjectFactory.SaveToRepository(report);

Save edited report as a new report

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

## Sorts

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, and 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;
    }

## Filters

See the existing filters in a report

    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.DisplayOperatorText,
        f.DisplayValue,
        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)
    {
      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.DisplayOperatorText,
        filter.Name,
        filter.GroupName,
        filter.IgnoreOtherGroups ? "ignoring other groupings" : "",
        (i < report.GroupFilters.Count()) ? ", " : "" );
      i++;
    }

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)
    {
      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 == TopNAction.top ? "Top" : "Bottom",
          filter.Number,
          report.Cells.GetCellById(filter.CellId).DisplayText,
          (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       = TopNAction.top,
      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.Add(topN);
    else
      report.TopNItems[0] = topN;

# Settings

Change the values of config settings and parameters dynamically, often depending 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

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

    role.Activate();

# 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

    api.DataSources.GetDataSource("dataSourceName").DataConnStr;

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

    Entity entity = api.Entities.NewEntity();

    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.Name,
       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...";

## Joins

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",
          col.FromColumn.FullKeyName,
          join.JoinText,
          col.ToColumn.FullKeyName,
          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)
    newJoin.JoinColumns.FromColumns.Add(
      new KeyColumn(fromEntity.GetColumn("fromColName").ActualFullName));
    newJoin.JoinColumns.ToColumns.Add(
      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();";

    // 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
    });

# Scheduling

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}' ",
          job.Name,
          api.ReportScheduler.GetReportScheduleInfoByJobId(job.JobId.ToString()).ReportBaseName
        );

        switch (job.Status)
        {
          case JobStatus.Completed:
            jobList += string.Format("ran on {0}, at host {1}.\n",
              job.LastExecuteDate.ToString("MMM d hh:mm tt"),
              api.ReportScheduler.GetHost(api.ReportScheduler.GetHostIdxForJob(job.JobId))
            );
            break;
          case JobStatus.Ready:
            jobList += string.Format("ready to run on {0}.\n",
              job.NextExecuteDate.ToString("MMM d hh:mm tt")
            );
            break;
           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"),
               api.ReportScheduler.GetHost(api.ReportScheduler.GetHostIdxForJob(job.JobId))
             );
             break;
           default:
             jobList += string.Format("status unknown.\n");
             break;
        }
      }
    }

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 {
      api.ReportScheduler.AddReport(
        new ReportSchedule(api.PageInfo) { ScheduleInfo = newSchedule }, out jobId, out hostIdx);
    }
    catch (Exception) { }

Recurring schedules (additional options)

### Daily

    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
    }

### Weekly

    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    Thurs Fri    Sat
      IsDayOfWeek = new bool[] { false, true, false, false, true, false, false }
    }

### Monthly

    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
    }

### Yearly

    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("email@company.com"); // 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("supervisor@example.com");       // 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 job id

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

Update an existing schedule

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

Delete an existing schedule

    api.ReportScheduler.DeleteSchedulerJob("jobIdToDelete");

# 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);