IAdHocExtension Samples
This article describes integration mode full-code samples for the IAdHocExtension APIs, which allow customization code to hook in the report life cycle.
Reference Izenda libraries in a New Project
-
Create a new web project in Visual Studio.
-
Name it IAdHocExtensionSample.
-
Select a location (e.g. D:\Projects).
-
Select to create new solution.
-
Give the solution name IAdHocExtensionSample.
-
Tick the Create directory for solution check-box.
-
Click OK to create the project and solution.
-
Create a new folder for Izenda libraries D:\Projects\IAdhocExtensionSample\IAdhocExtensionSample\izendadll.
-
Copy the file Izenda.BI.Framework.dll from Izenda installation folder into the newly-created folder.
-
Open Solution Explorer, right-click References in project IAdHocExtensionSample and select Add Reference.
-
In Reference Manager pop-up, click Browse and select the file Izenda.BI.Framework.dll (in D:\Projects\SetupHiddenFilter).
-
Verify the interface by double-clicking Izenda.BI.Framework in References to open Object Browser.
-
Expand the nodes to Izenda.BI.Framework.CustomConfiguration and select DefaultAdHocExtension class to see the methods to override.
-
Similarly reference the library Izenda.BI.Core.dll and the third-party Newtonsoft.Json.dll.
-
Also reference System.Composition.AttributedModel.dll (https://www.nuget.org/packages/System.Composition.AttributedModel version 1.0.31) and System.Web (in Assemblies > Framework).
Sample Method Implementations
using Izenda.BI.Core;
using Izenda.BI.Core.QueryEngine;
using Izenda.BI.Framework.Components.QueryExpressionTree;
using Izenda.BI.Framework.Constants;
using Izenda.BI.Framework.CustomConfiguration;
using Izenda.BI.Framework.Models;
using Izenda.BI.Framework.Models.Common;
using Izenda.BI.Framework.Models.ReportDesigner;
using Izenda.BI.Framework.Utility;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Composition.AttributedModel.dll;
using Izenda.BI.Framework.Components.QueryExpressionTree.Operator;
using Operator = Izenda.BI.Framework.Enums.DateTimeOperator;
namespace IAdHocExtensionSample
{
[Export(typeof(IAdHocExtension))]
public class AdHocExtensionSample : DefaultAdHocExtension
{
/// <summary>
/// Sample Requirement:
/// If logged user has role Manager --> look up "South America" => "SA", "North America" => "NA"
/// If logged user has role Employee --> look up "Europe" => "EU"
/// </summary>
/// <param name="filterField"></param>
/// <param name="data"></param>
/// <returns></returns>
public override List<string> OnPostLoadFilterData(ReportFilterField filterField, List<string> data)
{
// override dropdown value based on user role for filter on view "OrderDetailsByRegion" and field "CountryRegionName"
if (filterField.SourceDataObjectName == "OrderDetailsByRegion" && filterField.SourceFieldName == "CountryRegionName"
&& (HttpContext.Current.User.IsInRole("Manager") || HttpContext.Current.User.IsInRole("Employee")))
{
// override dropdown's value based on User role
//Manager, look up "South America" => "SA", "North America" => "NA"
if (HttpContext.Current.User.IsInRole("Manager"))
{
var indexSA = data.IndexOf("South America");
if (indexSA != -1)
data[indexSA] = "SA";
var indexNA = data.IndexOf("North America");
if (indexNA != -1)
data[indexNA] = "NA";
}
// Employee, look up "Europe" => "EU"
if (HttpContext.Current.User.IsInRole("Employee"))
{
var indexEU = data.IndexOf("Europe");
if (indexEU != -1)
data[indexEU] = "EU";
}
}
return base.OnPostLoadFilterData(filterField, data);
}
/// <summary>
/// Sample Requirement:
/// If logged user has role Manager --> show some regions ("South America", "North America")
/// If logged user has role Employee --> show only one region ("Europe")
/// </summary>
/// <param name="fieldInfo"></param>
/// <returns></returns>
public override List<ValueTreeNode> OnLoadFilterDataTree(QuerySourceFieldInfo fieldInfo)
{
var result = new List<ValueTreeNode>();
if (fieldInfo.QuerySourceName == "OrderDetailsByRegion" && fieldInfo.Name == "CountryRegionName"
&& (HttpContext.Current.User.IsInRole("Manager") || HttpContext.Current.User.IsInRole("Employee")))
{
//Node [All] is required for UI to render.
var rootNode = new ValueTreeNode { Text = "[All]", Value = "[All]" };
rootNode.Nodes = new List<ValueTreeNode>();
if (HttpContext.Current.User.IsInRole("Manager"))
{
rootNode.Nodes.Add(new ValueTreeNode { Text = "South America", Value = "South America" });
rootNode.Nodes.Add(new ValueTreeNode { Text = "North America", Value = "North America" });
}
if (HttpContext.Current.User.IsInRole("Employee"))
{
rootNode.Nodes.Add(new ValueTreeNode { Text = "Europe", Value = "Europe" });
}
result.Add(rootNode);
}
return result;
}
/// <summary>
/// Sample Requirement:
/// If logged user has role Manager --> show some regions ("WA", "[Blank]")
/// If logged user has role Employee --> show only one region ("Europe")
/// </summary>
/// <param name="fieldInfo"></param>
/// <returns></returns>
public override ReportFilterSetting SetHiddenFilters(SetHiddenFilterParam param)
{
var filterFieldName = "ShipRegion";
Func<ReportFilterSetting, int, QuerySource, QuerySourceField, Guid, Relationship, int> addHiddenFilters = (result, filterPosition, querySource, field, equalOperator, rel) =>
{
var firstFilter = new ReportFilterField
{
Alias = $"ShipRegion{filterPosition}",
QuerySourceId = querySource.Id,
SourceDataObjectName = querySource.Name,
QuerySourceType = querySource.Type,
QuerySourceFieldId = field.Id,
SourceFieldName = field.Name,
DataType = field.DataType,
Position = ++filterPosition,
OperatorId = equalOperator,
Value = "WA",
RelationshipId = rel?.Id,
IsParameter = false,
ReportFieldAlias = null
};
var secondFilter = new ReportFilterField
{
Alias = $"ShipRegion{filterPosition}",
QuerySourceId = querySource.Id,
SourceDataObjectName = querySource.Name,
QuerySourceType = querySource.Type,
QuerySourceFieldId = field.Id,
SourceFieldName = field.Name,
DataType = field.DataType,
Position = ++filterPosition,
OperatorId = equalOperator,
Value = "[Blank]",
RelationshipId = rel?.Id,
IsParameter = false,
ReportFieldAlias = null
};
result.FilterFields.Add(firstFilter);
result.FilterFields.Add(secondFilter);
var logic = $"({filterPosition - 1} OR {filterPosition})";
if (string.IsNullOrEmpty(result.Logic))
{
result.Logic = logic;
}
return filterPosition;
};
var filterSetting = new ReportFilterSetting()
{
FilterFields = new List<ReportFilterField>()
};
var position = 0;
var ds = param.ReportDefinition.ReportDataSource;
// Build the hidden filters for CountryRegionName
foreach (var querySource in param.QuerySources // Scan thru the query sources that are involved in the report
.Where(x => x.QuerySourceFields.Any(y => y.Name.Equals(filterFieldName, StringComparison.OrdinalIgnoreCase)))) // Take only query sources that have filter field name
{
// Pick the relationships that joins the query source as primary source
// Setting the join ensure the proper table is assigned when using join alias in the UI
var rels = param.ReportDefinition.ReportRelationship.
Where(x => x.JoinQuerySourceId == querySource.Id)
.ToList();
// Find actual filter field in query source
var field = querySource.QuerySourceFields.FirstOrDefault(x => x.Name.Equals(filterFieldName, StringComparison.OrdinalIgnoreCase));
// Pick the equal operator
var equalOperator = Izenda.BI.Framework.Enums.FilterOperator.FilterOperator.EqualsManualEntry.GetUid();
// In case there is no relationship that the query source is joined as primary
if (rels.Count() == 0)
{
// Just add hidden filter with null relationship
position = addHiddenFilters(filterSetting, position, querySource, field, equalOperator, null);
}
else
{
// Loop thru all relationships that the query source is joined as primary and add the hidden field associated with each relationship
foreach (var rel in rels)
{
position = addHiddenFilters(filterSetting, position, querySource, field, equalOperator, rel);
}
}
}
return filterSetting;
}
/// <summary>
/// Sample Requirement:
/// Remove all Map report parts before running
/// </summary>
/// <param name="fieldInfo"></param>
/// <returns></returns>
public override ReportDefinition OnPreExecute(ReportDefinition report)
{
if (report.ReportPart.Any(x => x.ReportPartContent.Type == ReportPartContentType.Map))
{
var filteredReportPart = report.ReportPart.Where(x => x.ReportPartContent.Type != ReportPartContentType.Map).ToList();
report.ReportPart = filteredReportPart;
}
return report;
}
/// <summary>
/// Sample Requirement:
/// Limit the execution result to the first 1000 rows only (although the database may return more than that)
/// </summary>
/// <param name="fieldInfo"></param>
/// <returns></returns>
public override List<IDictionary<string, object>> OnPostExecute(QueryTree executedQueryTree, List<IDictionary<string, object>> result)
{
return result.Take(1000).ToList();
}
/// <summary>
/// Sample Requirement:
/// Log the queries without result limit operator
/// </summary>
/// <param name="fieldInfo"></param>
/// <returns></returns>
public override QueryTree OnExecuting(QueryTree queryTree)
{
var nodeVisitor = new QueryTreePathAnalyzeVisitor(new ExtensibilityFactory(), queryTree.ContextData);
nodeVisitor.ContextData = queryTree.ContextData;
queryTree.Root.Accept(nodeVisitor);
var resultLimitOperator = new ResultLimitOperator()
{
ChildOperand = new Operand()
{
QuerySource = new QuerySource()
}
};
try
{
nodeVisitor.Visit(resultLimitOperator);
}
catch (Exception)
{
Console.WriteLine("LOG: Query with no limit");
}
return queryTree;
}
/// <summary>
/// Sample Requirement:
/// If report filter includes only OrderDetailsByRegion.CountryRegionName, return pre-defined list and skip querying the database
/// If not, let system query the database
/// </summary>
/// <param name="fieldInfo"></param>
/// <returns></returns>
public override List<string> OnPreLoadFilterData(ReportFilterSetting filterSetting, out bool handled)
{
handled = false;
List<String> result = null;
if (filterSetting.FilterFields.Count == 1
&& filterSetting.FilterFields.Any(
x => x.SourceDataObjectName.Equals("OrdersByRegion")
&& x.SourceFieldName.Equals("CountryRegionName")))
{
handled = true;
result = new List<string>()
{
"Europe",
"North America",
"South America"
};
}
return result;
}
/// <summary>
/// Sample Requirement:
/// Returns a custom list of 6 In Time Periods
/// </summary>
public override List<CustomTimePeriod> LoadCustomTimePeriod()
{
var result = new List<CustomTimePeriod>
{
new CustomTimePeriod("Tomorrow",
DateTime.Now, DateTime.Now.AddDays(1), Operator.BetweenDateTime),
new CustomTimePeriod("Previous Date -> DateTime Now",
() => DateTime.Now.AddDays(-1), () => DateTime.Now, Operator.BetweenDateTime),
new CustomTimePeriod("Less Than 2 Days Old",
2, Operator.LessThanDaysOld),
new CustomTimePeriod("Greater Than 2 Days Old",
() => 2, Operator.GreaterThanDaysOld),
new CustomTimePeriod(">= Date Time Now + 2 Days",
DateTime.Now.AddDays(2), Operator.GreaterThanOrEqualsCalender),
new CustomTimePeriod("<= Date Time Now - 2 Days",
() => DateTime.Now.AddDays(-2), Operator.LessThanOrEqualsCalendar)
};
// using Operator = Izenda.BI.Framework.Enums.DateTimeOperator;
return result;
}
/// <summary>
/// Sample Requirement:
/// Returns a custom list of 3 data formats
/// </summary>
public override List<DataFormat> LoadCustomDataFormat()
{
var result = new List<DataFormat>
{
new DataFormat
{
Name = "0,000",
DataType = DataType.Numeric,
Category = IzendaKey.CustomFormat,
FormatFunc = (x) =>
{
return ((int)x).ToString("0,000");
}
},
new DataFormat
{
Name = "$0,000",
DataType = DataType.Numeric,
Category = IzendaKey.CustomFormat,
FormatFunc = (x) =>
{
return ((int)x).ToString("$0,000");
}
},
new DataFormat
{
Name = "$0,000",
DataType = DataType.Numeric,
Category = IzendaKey.CustomFormat,
FormatFunc = (x) =>
{
return ((decimal)x).ToString("C0");
}
}
};
return result;
}
}
}
Add the New Library
- Build then copy the IAdhocExtensionSample.dll file (it can be found at D:\Projects\IAdhocExtensionSample\IAdhocExtensionSample\bin) to the Izenda Back-end API folder (at C:\inetpub\wwwroot\Izenda\API\bin).
- Restart the Izenda Back-end website.
UnitTest for the Samples
Add UnitTest Project
- Rick click Solution in Solution Explorer and select Add > New Project.
- Add a Class Library project named AdHocExtensionSampleTest.
- Reference the project IAdHocExtensionSample (Add Reference and tick IAdHocExtensionSample in Projects > Solution).
- Also reference System.Web (in Assemblies > Framework).
- Reference xUnit Library.
- Open NuGet Package Manager pop-up from Tools > NuGet Package Manager > Manage NuGet Packages for Solution…
- Click Browse tab and enter xunit in the text box to search.
- Select xunit on the left and tick the AdHocExtensionSampleTest project check-box on the right.
- Select version 2.2.0 (working at the time of writing) and click Install.
- Similarly install xunit.runner.visualstudio version 2.2.0 to AdHocExtensionSampleTest project.
Implement the UnitTests
-
Add a MockUser class to help testing:
Code for MockUser.cs:
usingSystem.Security.Principal;namespaceIAdHocExtensionSample{classMockUser:IPrincipal{privatestringrole;publicMockUser(stringrole){this.role=role;}publicIIdentityIdentity{get;privateset;}publicboolIsInRole(stringrole){return(role==this.role)?true:false;}}}
-
Right-click the default Class1.cs file in Solution Explorer and rename it to AdHocExtensionSampleTest.cs, also agree to change the class name to AdHocExtensionSampleTest when asked.
-
Implement the tests in xUnit.
usingSystem;usingSystem.Collections.Generic;usingXunit;usingSystem.IO;usingSystem.Web;usingIzenda.BI.Framework.Models.ReportDesigner;usingIzenda.BI.Framework.Models;usingIzenda.BI.Framework.Constants;usingIzenda.BI.Framework.Components.QueryExpressionTree;namespaceIAdHocExtensionSample{publicclassAdhocExtensionSampleTest{privatestaticGuidquerySourceId1=Guid.Parse("39984D52-C1DE-4388-9ED2-FB7C8C62FD01");privatestaticGuidfieldId11=Guid.Parse("39984D52-C1DE-4388-9ED2-FB7C8C62FD11");privatestaticGuidfieldId12=Guid.Parse("39984D52-C1DE-4388-9ED2-FB7C8C62FD12");privatestaticGuidrelationshipId1=Guid.Parse("39984D52-C1DE-4388-9ED2-FB7C8C62FD03");/// <summary>/// Test Filter Manager/// </summary> [Fact]publicvoidExecute_HiddenFilter_Manager_Success(){HttpContext.Current=newHttpContext(newHttpRequest("","http://tempuri.org",""),newHttpResponse(newStringWriter()));HttpContext.Current.User=newMockUser("Manager");varparam=newSetHiddenFilterParam(){QuerySources=newList<QuerySource>(){newQuerySource(){Name="Orders",Id=querySourceId1,Type="Table",QuerySourceFields=newList<QuerySourceField>(){newQuerySourceField(){Name="ShipRegion",Id=fieldId11,DataType="varchar"}}}},ReportDefinition=newReportDefinition(){ReportDataSource=newList<ReportDataSource>(),ReportRelationship=newList<Relationship>()}};varreportFilterSetting=(newAdHocExtensionSample()).SetHiddenFilters(param);Assert.Equal(reportFilterSetting.FilterFields.Count,2);Assert.Equal(reportFilterSetting.FilterFields[0].Value,"WA");Assert.Equal(reportFilterSetting.FilterFields[1].Value,"[Blank]");}/// <summary>/// Test Filter Employee/// </summary> [Fact]publicvoidExecute_HiddenFilter_Employee_Success(){HttpContext.Current=newHttpContext(newHttpRequest("","http://tempuri.org",""),newHttpResponse(newStringWriter()));HttpContext.Current.User=newMockUser("Employee");varparam=newSetHiddenFilterParam(){QuerySources=newList<QuerySource>(){newQuerySource(){Name="Orders",Id=querySourceId1,Type="Table",QuerySourceFields=newList<QuerySourceField>(){newQuerySourceField(){Name="ShipRegion",Id=fieldId11,DataType="varchar"}}}},ReportDefinition=newReportDefinition(){ReportDataSource=newList<ReportDataSource>(),ReportRelationship=newList<Relationship>()}};varreportFilterSetting=(newAdHocExtensionSample()).SetHiddenFilters(param);Assert.Equal(reportFilterSetting.FilterFields.Count,1);Assert.Equal(reportFilterSetting.FilterFields[0].Value,"Europe");}/// <summary>/// Test Filter Manager with Duplicated FieldAlias/// </summary> [Fact]publicvoidExecute_HiddenFilter_Manager_Duplicated_FieldAlias(){HttpContext.Current=newHttpContext(newHttpRequest("","http://tempuri.org",""),newHttpResponse(newStringWriter()));HttpContext.Current.User=newMockUser("Manager");varparam=newSetHiddenFilterParam(){QuerySources=newList<QuerySource>(){newQuerySource(){Name="Orders",Id=querySourceId1,Type="Table",QuerySourceFields=newList<QuerySourceField>(){newQuerySourceField(){Name="ShipRegion",Id=fieldId11,DataType="varchar"}}},newQuerySource(){Name="Customers",Id=querySourceId1,Type="Table",QuerySourceFields=newList<QuerySourceField>(){newQuerySourceField(){Name="ShipRegion",Id=fieldId12,DataType="varchar"}}}},ReportDefinition=newReportDefinition(){ReportDataSource=newList<ReportDataSource>(),ReportRelationship=newList<Relationship>(){newRelationship(){JoinQuerySourceId=querySourceId1,Id=relationshipId1}}}};varreportFilterSetting=(newAdHocExtensionSample()).SetHiddenFilters(param);Assert.Equal(reportFilterSetting.FilterFields.Count,4);Assert.Equal(reportFilterSetting.FilterFields[0].Value,"WA");Assert.Equal(reportFilterSetting.FilterFields[1].Value,"[Blank]");Assert.Equal(reportFilterSetting.FilterFields[2].Value,"WA");Assert.Equal(reportFilterSetting.FilterFields[3].Value,"[Blank]");}/// <summary>/// Test OnLoadFilterDataTree Manager/// </summary> [Fact]publicvoidExecute_OnLoadFilterDataTree_Manager_Success(){HttpContext.Current=newHttpContext(newHttpRequest("","http://tempuri.org",""),newHttpResponse(newStringWriter()));HttpContext.Current.User=newMockUser("Manager");varfield=newQuerySourceFieldInfo(){QuerySourceName="OrderDetailsByRegion",Name="CountryRegionName"};varresult=(newAdHocExtensionSample()).OnLoadFilterDataTree(field);Assert.Equal(result.Count,1);Assert.Equal(result[0].Nodes.Count,2);Assert.Equal(result[0].Nodes[0].Value,"South America");Assert.Equal(result[0].Nodes[1].Value,"North America");}/// <summary>/// Test OnLoadFilterDataTree Employee/// </summary> [Fact]publicvoidExecute_OnLoadFilterDataTree_Employee_Success(){HttpContext.Current=newHttpContext(newHttpRequest("","http://tempuri.org",""),newHttpResponse(newStringWriter()));HttpContext.Current.User=newMockUser("Employee");varfield=newQuerySourceFieldInfo(){QuerySourceName="OrderDetailsByRegion",Name="CountryRegionName"};varresult=(newAdHocExtensionSample()).OnLoadFilterDataTree(field);Assert.Equal(result.Count,1);Assert.Equal(result[0].Nodes.Count,1);Assert.Equal(result[0].Nodes[0].Value,"Europe");}/// <summary>/// Test OnPostLoadFilterData Manager/// </summary> [Fact]publicvoidExecute_OnPostLoadFilterData_Manager_Success(){HttpContext.Current=newHttpContext(newHttpRequest("","http://tempuri.org",""),newHttpResponse(newStringWriter()));HttpContext.Current.User=newMockUser("Manager");varfield=newReportFilterField(){SourceDataObjectName="OrderDetailsByRegion",SourceFieldName="CountryRegionName"};vardata=newList<string>(){"Antarctica","Europe","North America","South America"};varresult=(newAdHocExtensionSample()).OnPostLoadFilterData(field,data);Assert.Equal(result.Count,4);Assert.NotEqual(result.IndexOf("SA"),-1);Assert.NotEqual(result.IndexOf("NA"),-1);}/// <summary>/// Test OnPostLoadFilterData Employee/// </summary> [Fact]publicvoidExecute_OnPostLoadFilterData_Employee_Success(){HttpContext.Current=newHttpContext(newHttpRequest("","http://tempuri.org",""),newHttpResponse(newStringWriter()));HttpContext.Current.User=newMockUser("Employee");varfield=newReportFilterField(){SourceDataObjectName="OrderDetailsByRegion",SourceFieldName="CountryRegionName"};vardata=newList<string>(){"Antarctica","Europe","North America","South America"};varresult=(newAdHocExtensionSample()).OnPostLoadFilterData(field,data);Assert.Equal(result.Count,4);Assert.NotEqual(result.IndexOf("EU"),-1);}/// <summary>/// Test OnPreExecute/// </summary> [Fact]publicvoidExecute_OnPreExecute_Success(){varoriginalReport=newReportDefinition(){ReportPart=newList<ReportPartDefinition>{newReportPartDefinition{ReportPartContent=newReportPartMap{Type=ReportPartContentType.Map}},newReportPartDefinition{ReportPartContent=newReportPartGrid{Type=ReportPartContentType.Grid}},newReportPartDefinition{ReportPartContent=newReportPartMap{Type=ReportPartContentType.Map}}}};varreport=(newAdHocExtensionSample()).OnPreExecute(originalReport);Assert.Equal(report.ReportPart.Count,1);}/// <summary>/// Test OnPostExecute/// </summary> [Fact]publicvoidExecute_OnPostExecute_Success(){varoriginalList=newList<IDictionary<string,object>>();for(vari=1;i<=1010;i++){originalList.Add(newDictionary<string,object>());}varqt=newQueryTree();varlist=(newAdHocExtensionSample()).OnPostExecute(qt,originalList);Assert.Equal(list.Count,1000);}/// <summary>/// Test OnPreLoadFilterData/// </summary> [Fact]publicvoidExecute_OnPreLoadFilterData_Success(){varfilterSetting=newReportFilterSetting(){FilterFields=newList<ReportFilterField>(){newReportFilterField(){SourceDataObjectName="OrdersByRegion",SourceFieldName="CountryRegionName"}}};boolhandled;varlist=(newAdHocExtensionSample()).OnPreLoadFilterData(filterSetting,outhandled);Assert.Equal(handled,true);Assert.Equal(list.Count,3);}/// <summary>/// Test LoadCustomTimePeriod/// </summary> [Fact]publicvoidExecute_LoadCustomTimePeriod_Success(){varlist=(newAdHocExtensionSample()).LoadCustomTimePeriod();Assert.Equal(list.Count,6);}/// <summary>/// Test LoadCustomDataFormat/// </summary> [Fact]publicvoidExecute_LoadCustomDataFormat_Success(){varlist=(newAdHocExtensionSample()).LoadCustomDataFormat();Assert.Equal(list.Count,3);}}}
Run the UnitTests
- Open Test Explorer from Menu > Test > Windows.
- Click Run All in Test Explorer.
- All the tests should be passed.