Datasource
Custom Data Source Extensions are a means of developing customised code to retrieve data from a third party system where the collection of built-in data source extensions are not an option.
A Data Source Extension not only needs to retrieve the data at runtime but also needs to return a list of input and outputs (referred to as filter fields and display fields) so that they can be mapped to and from the right locations during the design and testing of the actual Intelledox Infiniti form. For example, a ‘username’ might be passed to an HR system and the response containing their personal details and role information might pre-populate a form and/or be included as content within a generated document.
Some design of what operations the data source will support and how the input and output fields will be maintained is an important investment.
Data Source Extension Development Walkthrough
A Data Source Extension can be developed in-house or by a third party to handle custom or unique situations when necessary. Custom Data Sources are developed as a module that is deployed to an existing Infiniti environment.
The walkthroughs below have been created using Microsoft’s Visual Studio 2017. All sample code is based version 7.0 of the C# language and version 4.6.2 of the .NET Framework.
1. Open Visual Studio and create a new Class Library Project and give it a meaningful name. For this example, we will use SampleDatasourceExtension
. Ensure that the .NET Framework 4.5.2 is selected.
2. Rename Class1
to Datasource
3. Click ‘Yes’ to the rename all references prompt.
4. Add the following references to the project.
.Net References:
System.Configuration
Intelledox Infiniti References, usually located C:\inetpub\wwwroot\Infiniti\Produce\bin
Intelledox.Extension.dll
Intelledox.Extension.Datasource.dll
Intelledox.QAWizard.dll
Intelledox.QAWizard.Design.dll
Intelledox.Model.dll
Good Practice
Reference Paths point to correct Intelledox Infiniti deployment path.
Copy Local property should be set to
False
for all Intelledox Infiniti References, as this could corrupt your instance if an older reference is copied to an upgraded site.
5. Inherit the Intelledox.Extension.Datasource.DatasourceConnector
and override necessary ActionConnector methods, as per the sample below.
using System;
using System.Data;
using System.Threading.Tasks;
using Intelledox.Extension.Datasource;
using Intelledox.Model;
using Intelledox.QAWizard;
namespace SampleDatasourceExtension
{
public class Datasource : DatasourceConnector
{
public override ExtensionIdentity ExtensionIdentity { get => throw new NotImplementedException(); protected set => throw new NotImplementedException(); }
public override Task<DataTable> GetAvailableFilterFieldsAsync(string connectionString, Query query, Authentication auth, DatasourceProperties properties)
{
throw new NotImplementedException();
}
public override Task<DataTable> GetDataAsync(string connectionString, Query query, DataFilter criteria, Authentication auth, DatasourceProperties properties)
{
throw new NotImplementedException();
}
public override Task<IDataReader> GetDataReaderAsync(string connectionString, Query query, DataFilter criteria, Authentication auth, DatasourceProperties properties)
{
throw new NotImplementedException();
}
public override Task<DataTable> GetSchemaAsync(string connectionString, Query query, DataFilter criteria, Authentication auth, DatasourceProperties properties)
{
throw new NotImplementedException();
}
public override Task<string[]> ObjectListAsync(string connectionString, string prefixText, Guid objectType, Authentication auth, DatasourceProperties properties)
{
throw new NotImplementedException();
}
public override ObjectType[] ObjectTypes()
{
throw new NotImplementedException();
}
public override Task<bool> TestConnectionAsync(string connectionString, Authentication auth, DatasourceProperties properties)
{
throw new NotImplementedException();
}
}
}
Method | Returns | Description |
---|---|---|
TestConnectionAsync | Boolean | All data sources require a connection string. The value of the connection string depends on the type of data being retrieved; for example, it could be the location of a data file or a traditional database connection string. This function is called when users press the “Test Connection” button in Manage to test the connection string’s integrity. |
ObjectTypes | Array of ‘ObjectType’ | Data Sources maybe divided into different Data Object types. For example, the in-built SQL server Data Source extension has two types: Stored Procedures and Table/View, as they need to be handled differently when determining the input (filter) fields and what fields will be returned. Whilst many data sources extensions only support one type the list of types is displayed in the Data Object Type drop down in Manage. |
ObjectListAsync | String Array | A list of possible values for the above Data Object Type. For example, if data object type is Table/View in a database, object list would return all the actual tables available. The list can be filtered at run time if needed. |
GetAvailableFilterFieldsAsync | DataTable | Returns a data table where the column names are the identified filter fields. For example, the filter fields might be the input parameters for a web service or stored procedure. For consistency this method returns a data table, despite the fact that only the column headings are used. The data table usually contains no rows. |
GetSchemaAsync | DataTable | Returns a data table where the column names represent the fields returned by the Data Source. |
GetDataAsync | DataTable | Method called at run time where an end user is requesting data, the call will contain a collection of filter fields with real values that can be used to make an actual call to the third party system. The response should have the same columns as the equivalent call to get Schema but this time have one or more rows as part of the response. |
GetDataReaderAsync | IDataReader | Similar to GetData above but returns an IDataReader object rather than a DataTable. Although it is called by different areas of the product at runtime the method is usually calls the above GerDataAsync method and coverts its DataTable response to an IDataReader. |
ExtensionIdentity | ExtensionIdentity | Method to get and set the Extension Name and Id. (ModuleId is an optional parameter that provides a licensing module identifier) |
Build Successfully
The project should build at this point without error.
6. Implement the ExtensionIdentity property with an Id
and a Name
to register the Data Source within Infiniti. The Id needs to be unique, and the name is displayed on the Data Source screen in Manage.
public override ExtensionIdentity ExtensionIdentity { get; protected set; } = new ExtensionIdentity()
{
Id = new Guid("7D647B92-78E3-4EBC-923F-1A95C15C4C2C"),
Name = "Infiniti Simple Data Source Extension"
};
7. ObjectTypes are identified by a GUID; it is best practice to declare this GUID as a global variable.
private Guid _tableObjectType = new Guid("CFE1653E-E2F7-4713-BFBE-311E8BD484D3");
private Guid _serviceObjectType = new Guid("498B7D35-5D05-4728-B07A-DD5C7B7DF083");
8. Register the ObjectTypes in the ObjectType()
method as follow
public override ObjectType[] ObjectTypes()
{
return new ObjectType[] {
new ObjectType(_tableObjectType, "Table", multiline: false, canCache: true, selectableSchemaFields: false),
new ObjectType(_serviceObjectType, "Service", multiline: false, canCache: true, selectableSchemaFields: false)
};
}
9. The ObjectListAsync()
method returns all the Data Objects in the service to allow separation of how to handle them in the code. Code the ObjectListAsync()
method so that it returns 2 data objects.
public override Task<string[]> ObjectListAsync(string connectionString, string prefixText, Guid objectType, Authentication auth, DatasourceProperties properties)
{
string[] availableObjects = { "SampleTable1", "SampleTable2" };
return Task.FromResult(availableObjects.Where(s => s.StartsWith(prefixText, StringComparison.OrdinalIgnoreCase)).ToArray());
}
10. The GetAvailableFilterFieldsAsync()
method returns a data table where the column names are the identified filter fields. For example, the key fields might be the input parameters for a web service or stored procedure. Implement the GetAvailableFilterFieldsAsync()
method so that it returns two filter fields.
public override Task<DataTable> GetAvailableFilterFieldsAsync(string connectionString, Query query, Authentication auth, DatasourceProperties properties)
{
var filterFields = new DataTable();
filterFields.Columns.Add("Filter1", typeof(int));
filterFields.Columns.Add("Filter2", typeof(string));
return Task.FromResult(filterFields);
}
11. The GetSchemaAsync()
method returns all the available columns/fields of the data source. Implement the GetSchemaAsync()
so that it returns a DataTable with two columns.
public override Task<DataTable> GetSchemaAsync(string connectionString, Query query, DataFilter criteria, Authentication auth, DatasourceProperties properties)
{
var data = new DataTable();
data.Columns.Add("Column1", typeof(int));
data.Columns.Add("Column2", typeof(string));
return Task.FromResult(data);
}
12. The GetDataAsync()
method is the primary function of a data source that is called whenever a form needs data from the external service. Implement the GetDataAsync()
so that it returns a DataTable with 1 data row as per sample below. In this example, this data row’s values have been hardwired.
public override Task<DataTable> GetDataAsync(string connectionString, Query query, DataFilter criteria, Authentication auth, DatasourceProperties properties)
{
var data = new DataTable();
data.Columns.Add("Column1", typeof(int));
data.Columns.Add("Column2", typeof(string));
data.BeginLoadData();
DataRow currentRow = data.NewRow();
currentRow["Column1"] = 123;
currentRow["Column2"] = "ABC";
data.Rows.Add(currentRow);
data.EndLoadData();
return Task.FromResult(data);
}
13. The method GetDataReaderAsync()
is similar to GetData()
above but returns an IDataReader object rather than a DataTable. GetDataReaderAsync()
call is used when repeating documents are being created. To support a large number of rows a streaming DataReader is requested.
public async override Task<IDataReader> GetDataReaderAsync(string connectionString, Query query, DataFilter criteria, Authentication auth, DatasourceProperties properties)
{
return (await GetDataAsync(connectionString, query, criteria, auth, properties)).CreateDataReader();
}
14. The TestConnectionAsync()
method runs when the ‘Test Connection’ button in Manage is clicked to test the connection string’s integrity. Implement the TestConnectionAsync()
method to always return a successful result as per sample below.
public override Task<bool> TestConnectionAsync(string connectionString, Authentication auth, DatasourceProperties properties)
{
return Task.FromResult(true);
}
15. Build your Data Source to ensure it compiles without error. The Data Source can now be deployed and tested.
Deploying a Data Source
Data Source Extensions are deployed to an Infiniti environment by copying the Data Source Extension DLL file to the Produce and Manage bin folders and referencing it within Manage and Produce appsettings.json
files.
-
Locate your
SampleDatasourceExtension.dll
file and copy it to the Produce bin directory (usually locatedC:\inetpub\wwwroot\Infiniti\Produce\bin
). -
Open the produce
appsettings.json
file and locate the “Extensions” section of the file. It will most likely contain references to other Data Sources already. -
Add a new Data Source extension element using the following syntax to the
appsettings.json
.
"ClassName (including namespace), AssemblyName"
Example:
"Extensions": [
"Intelledox.Extension.DatasourceBuiltin.OleDbDatasource, Intelledox.Extension.DatasourceBuiltin",
"Intelledox.Extension.DatasourceBuiltin.SqlServerDatasource, Intelledox.Extension.DatasourceBuiltin",
"Intelledox.Extension.DatasourceBuiltin.CsvDatasource, Intelledox.Extension.DatasourceBuiltin",
"Intelledox.Extension.DatasourceBuiltin.OdbcDatasource, Intelledox.Extension.DatasourceBuiltin",
"Intelledox.Extension.DatasourceBuiltin.RSSDatasource, Intelledox.Extension.DatasourceBuiltin",
"Intelledox.Extension.DatasourceBuiltin.WebserviceDatasource, Intelledox.Extension.DatasourceBuiltin",
"Intelledox.Extension.DatasourceBuiltin.XmlDatasource, Intelledox.Extension.DatasourceBuiltin",
"Intelledox.Extension.DatasourceBuiltin.InfinitiDatasource, Intelledox.Extension.DatasourceBuiltin",
"Intelledox.Extension.DatasourceBuiltin.JsonDatasource, Intelledox.Extension.DatasourceBuiltin",
"Intelledox.Extension.DatasourceBuiltin.RestDatasource, Intelledox.Extension.DatasourceBuiltin",
"SampleDatasourceExtension.Datasource, SampleDatasourceExtensions"
],
-
Save the
appsettings.json
file. -
Repeat the same steps for
C:\inetpub\wwwroot\Infiniti\Manage\bin
-
Navigate to Produce and Manage in your browser, an absence of error messages suggests the Data Source has installed correctly.
-
Open an existing project in Infiniti Design and add the new Data Source to the question set.
Debugging Data Sources
After deploying a Data Source, it can be debugged by attaching Visual Studio to the w3wp.exe process and triggering the Data Source from Produce.
Final Full Code
using System;
using System.Data;
using System.Threading.Tasks;
using Intelledox.Extension.Datasource;
using Intelledox.Model;
using Intelledox.QAWizard;
using System.Linq;
namespace SampleDatasourceExtension
{
public class Datasource : DatasourceConnector
{
private Guid _tableObjectType = new Guid("CFE1653E-E2F7-4713-BFBE-311E8BD484D3");
private Guid _serviceObjectType = new Guid("498B7D35-5D05-4728-B07A-DD5C7B7DF083");
public override ExtensionIdentity ExtensionIdentity { get; protected set; } = new ExtensionIdentity()
{
Id = new Guid("7D647B92-78E3-4EBC-923F-1A95C15C4C2C"),
Name = "Infiniti Simple Data Source Extension",
ModuleId = ""
};
public override Task<DataTable> GetAvailableFilterFieldsAsync(string connectionString, Query query, Authentication auth, DatasourceProperties properties)
{
var filterFields = new DataTable();
filterFields.Columns.Add("Filter1", typeof(int));
filterFields.Columns.Add("Filter2", typeof(string));
return Task.FromResult(filterFields);
}
public override Task<DataTable> GetDataAsync(string connectionString, Query query, DataFilter criteria, Authentication auth, DatasourceProperties properties)
{
var data = new DataTable();
data.Columns.Add("Column1", typeof(int));
data.Columns.Add("Column2", typeof(string));
data.BeginLoadData();
DataRow currentRow = data.NewRow();
currentRow["Column1"] = 123;
currentRow["Column2"] = "ABC";
data.Rows.Add(currentRow);
data.EndLoadData();
return Task.FromResult(data);
}
public async override Task<IDataReader> GetDataReaderAsync(string connectionString, Query query, DataFilter criteria, Authentication auth, DatasourceProperties properties)
{
return (await GetDataAsync(connectionString, query, criteria, auth, properties)).CreateDataReader();
}
public override Task<DataTable> GetSchemaAsync(string connectionString, Query query, DataFilter criteria, Authentication auth, DatasourceProperties properties)
{
var data = new DataTable();
data.Columns.Add("Column1", typeof(int));
data.Columns.Add("Column2", typeof(string));
return Task.FromResult(data);
}
public override Task<string[]> ObjectListAsync(string connectionString, string prefixText, Guid objectType, Authentication auth, DatasourceProperties properties)
{
string[] availableObjects = { "SampleTable1", "SampleTable2" };
return Task.FromResult(availableObjects.Where(s => s.StartsWith(prefixText, StringComparison.OrdinalIgnoreCase)).ToArray());
}
public override ObjectType[] ObjectTypes()
{
return new ObjectType[] {
new ObjectType(_tableObjectType, "Table", multiline: false, canCache: true, selectableSchemaFields: false),
new ObjectType(_serviceObjectType, "Service", multiline: false, canCache: true, selectableSchemaFields: false)
};
}
public override Task<bool> TestConnectionAsync(string connectionString, Authentication auth, DatasourceProperties properties)
{
return Task.FromResult(true);
}
}
}
More Examples
More examples are available in Intelledox Github account
Updated over 6 years ago