Custom Question
Infiniti supports the majority of eForm inputs, but occasionally a unique user interface or control, specific to an implementation is required. In these cases, a Custom Question Extension may be developed.
A Custom Question Extension (generally referred to as a Custom Question) allows organizations to develop their own question types that can be used where needed throughout their Infiniti Projects. A custom question may define a user interface to capture information or may be used for data manipulation.
Custom Question Extension Development Walkthrough
Custom Question Extensions can be developed in-house or by a third party to handle custom or unique situations when necessary. Custom Questions 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 SampleCustomQuestionExtension
. Ensure that the .NET Framework 4.6.2 is selected.
2. Rename Class1
to SimpleCustomQuestion
.
3. Click ‘Yes’ to the rename all references prompt.
4. Add the following references to the project.
.Net References:
System.Configuration
Infiniti References, usually located C:\inetpub\wwwroot\Infiniti\Produce\bin
Intelledox.Extension.dll
Intelledox.Extension.CustomQuestion.dll
Intelledox.QAWizard.dll
Intelledox.QAWizard.Design.dll
Intelledox.Model.dll
Intelledox.Common.dll
Good Practice
- Reference Paths point to correct Infiniti deployment path.
- Copy Local property should be set to
False
for all Infiniti References, as this could corrupt your instance if an older reference is copied to an upgraded site.
5. Inherit the Intelledox.Extension.CustomQuestion.CustomQuestionExtension
and override necessary CustomQuestionExtension methods, as per the sample below.
using System;
using System.Collections.Specialized;
using System.IO;
using Intelledox.Extension.CustomQuestion;
using Intelledox.QAWizard;
namespace SampleCustomQuestionExtension
{
public class CustomQuestion : Intelledox.Extension.CustomQuestion.CustomQuestionExtension
{
public override ExtensionIdentity ExtensionIdentity { get => throw new NotImplementedException(); protected set => throw new NotImplementedException(); }
public override byte[] Icon16x16Png => throw new NotImplementedException();
public override byte[] Icon48x48Png => throw new NotImplementedException();
public override void UpdateAttributes(string controlPrefix, NameValueCollection postedFormValues, CustomQuestionProperties props)
{
throw new NotImplementedException();
}
public override void WriteHtml(string controlPrefix, CustomQuestionProperties props, TextWriter writer)
{
throw new NotImplementedException();
}
}
}
Method | Return | Description |
---|---|---|
ExtensionIdentity | ExtensionIdentity | Method to get and set the Extension Name and Id. (ModuleId is an optional parameter that provides a licensing module identifier) |
Icon16x16Png | byte[] | Icon image used in the left side panel in Design. |
Icon48x48Png | byte[] | Icon image used in top tool bar in Design |
UpdateAttributes | void | Sets the value of the attribute. If the attribute id doesn’t exist in the collection yet, it is created. |
WriteHtml | void | Allows a custom UI such as a button to be displayed in the form |
Build Successfuly
The project should build at this point without error.
6. Implement the ExtensionIdentity property so that it initializes the ExtensionIdentity object with an Id and a Name to register the Custom Question within Infiniti. The Id needs to be unique and the name is displayed to a designer in Design.
public override ExtensionIdentity ExtensionIdentity { get; protected set; } = new ExtensionIdentity()
{
Id = new Guid("E7213603-044E-419B-B00E-92205B378E74"),
Name = "Simple Question"
};
7. An image/icon can also be defined for custom questions. From the same ‘SampleCustomQuestionExtensions’ project, add two images with dimensions of 16x16 and 48x48 as Embedded Resources. Change the two images as Embedded Resources by right-clicking them, and choosing Properties. In the Properties dialogue box, locate the Build Action property. By default, this property is set to Content. Click the property and change the Build Action property to Embedded Resource.
8. From the same SampleCustomQuestionExtension
project, add a new class and call it IconHelper
. Code the ‘IconHelper’ class as per the sample below.
using System;
using System.IO;
namespace SampleCustomQuestionExtensions
{
internal class IconHelper
{
public static Byte[] GetResourceBytes(string resourceName)
{
byte[] buffer = null;
using (Stream resourceStream = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
{
buffer = new byte[(int)resourceStream.Length];
resourceStream.Read(buffer, 0, (int)resourceStream.Length);
}
return buffer;
}
}
}
9. Code the Icon16x16Png() and Icon48x48Png() to behave as such.
public override byte[] Icon16x16Png => IconHelper.GetResourceBytes("SampleCustomQuestionExtensions.Question16.png");
public override byte[] Icon48x48Png => IconHelper.GetResourceBytes("SampleCustomQuestionExtensions.Question48.png");
10. While it is possible to create Custom Questions that don’t display anything on the screen, in most cases, you will want to display something. For this simple example, we’ll just display the words “Hello world” on the screen. You do this by defining the HTML that should display in the form in the location where the question is. This is done in the WriteQuestionHtml function by writing to the StreamWriter that is passed into the function.
In this case, we’ll just have our question write an input with the default value of “Hello World” on the screen.
public override void WriteHtml(string controlPrefix, CustomQuestionProperties props, TextWriter writer)
{
writer.Write("<input name=\"" + controlPrefix + "_myInput\" id=\"" + controlPrefix + "_myInput\" value=\"Hello World\"/>");
}
Note
The use of the “controlPrefix” parameter. It is important to use this prefix when defining fields from which the values are important. At this point, the value of the field isn’t useful, but it’s a good habit to always use the controlPrefix.
The controlPrefix will uniquely identify a particular instance of a Custom Question. This is needed when multiple questions of the same type might be on the screen at once (such as if your Custom Question is in a repeated Section).
In this case, we appended “_myInput” to the end of the controlPrefix.
11. Build your Custom Question to ensure it compiles without error. The Question can now be deployed and tested.
Custom Question Inputs, Outputs, Local Variables, Attributes and Runtime Behavior
Like the existing packaged question types, a custom question will have its own inputs, outputs, and variables. For custom questions, these need to be handled in a particular manner.
At runtime, only one instance of the custom question extension object is created, even when the custom question appears multiple times throughout the eForm. Thus, non-static global variables must never be used as they become shared and subsequently interfere with each other.
Therefore, in order to have a specific attribute for individual Custom Questions, an attribute collection contained on the CustomQuestionProperties object is available and is passed into most functions. The individual attribute values can be set and updated using the following functions:
- ContainsAttribute(Guid) – returns whether or not the attribute is contained in the collection.
- GetAttribute(Guid) – returns the value of the attribute as an object
- GetAttributeBool(Guid) – returns the value of the attribute as a boolean
- GetAttributeString(Guid) – returns the value of the attribute as a string
- UpdateAttribute(Guid, Object) – Sets the value of the attribute. If the attribute id doesn’t exist in the collection yet, it is created.
Note
All attributes are identified by a GUID.
In order to define inputs and outputs, it is required to define them as private global variables
private Guid _flagGuid = new Guid("23C18C01-B617-4678-B355-A014B73E23C6");
private Guid _valueGuid = new Guid("EA232F27-1D3E-4EB4-AFEA-331A20BC701D");
To register Inputs, it is required to override the method GetAvailableInputs()
.
public override List<AvailableInput> GetAvailableInputs()
{
return new List<AvailableInput>()
{
new AvailableInput()
{
Id = _flagGuid,
Name = "Max Length"
}
};
}
To register Outputs, it is required to override the method GetAvailableOutputs()
.
public override List<AvailableOutput> GetAvailableOutputs()
{
return new List<AvailableOutput>()
{
new AvailableOutput()
{
Id = _valueGuid,
Name = "Value",
OutputType = CustomQuestionOutputType.Text
}
};
}
In order for Infiniti to find the output when needed, the value of the Output should be set in the attributes collection. This is done in the UpdateAttributes function. The UpdateAttributes function is called whenever a postback occurs in the browser. This is an opportunity for the Custom Question to update its attributes based on what the user has entered.
This is also where the controlPrefix comes into play. A NameValueCollection is passed into the function with the values of all the controls on the form that started with the controlPrefix – so if don’t give a control the prefix, you’ll never be able to utilize its value.
public override void UpdateAttributes(string controlPrefix, NameValueCollection postedFormValues, CustomQuestionProperties props)
{
props.UpdateAttribute(_valueGuid, postedFormValues[controlPrefix]);
}
Depending on each case, inputs could be initialized implementing the method InitialiseInputs()
public override void InitialiseInputs(CustomQuestionProperties props)
{
props.UpdateAttribute(_flagGuid, false);
}
Deploying a Custom Question
Custom Question Extensions are deployed to an Infiniti environment by copying the Custom Question Extension dll file to the Produce bin folder and referencing it within Produce appsettings.json
files.
-
Locate your
SampleCustomQuestionExtension.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. -
Add a new Custom Question 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",
"SampleCustomQuestionExtension.CustomQuestion, SampleCustomQuestionExtensions"
],
-
Save the
appsettings.json
file. -
Navigate to Produce in your browser, an absence of error messages suggests the Custom Question has installed correctly.
-
Open an existing project in Infiniti Design and add the new Custom Question to the question set.
Debugging Custom Questions
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.Collections.Specialized;
using System.IO;
using Intelledox.Extension.CustomQuestion;
using Intelledox.QAWizard;
using SampleCustomQuestionExtensions;
using System.Collections.Generic;
using Intelledox.Model;
namespace SampleCustomQuestionExtension
{
public class CustomQuestion : CustomQuestionExtension
{
private Guid _flagGuid = new Guid("23C18C01-B617-4678-B355-A014B73E23C6");
private Guid _valueGuid = new Guid("EA232F27-1D3E-4EB4-AFEA-331A20BC701D");
public override ExtensionIdentity ExtensionIdentity { get; protected set; } = new ExtensionIdentity()
{
Id = new Guid("E7213603-044E-419B-B00E-92205B378E74"),
Name = "Simple Question"
};
public override List<AvailableInput> GetAvailableInputs()
{
return new List<AvailableInput>()
{
new AvailableInput()
{
Id = _flagGuid,
Name = "Max Length"
}
};
}
public override List<AvailableOutput> GetAvailableOutputs()
{
return new List<AvailableOutput>()
{
new AvailableOutput()
{
Id = _valueGuid,
Name = "Value",
OutputType = CustomQuestionOutputType.Text
}
};
}
public override void InitialiseInputs(CustomQuestionProperties props)
{
props.UpdateAttribute(_flagGuid, false);
}
public override byte[] Icon16x16Png => IconHelper.GetResourceBytes("SampleCustomQuestionExtensions.Question16.png");
public override byte[] Icon48x48Png => IconHelper.GetResourceBytes("SampleCustomQuestionExtensions.Question48.png");
public override void UpdateAttributes(string controlPrefix, NameValueCollection postedFormValues, CustomQuestionProperties props)
{
props.UpdateAttribute(_valueGuid, postedFormValues[controlPrefix]);
}
public override void WriteHtml(string controlPrefix, CustomQuestionProperties props, TextWriter writer)
{
writer.Write("<input name=\"" + controlPrefix + "_myInput\" id=\"" + controlPrefix + "_myInput\" value=\"Hello World\"/>");
}
}
}
More Examples
More examples are available in Intelledox Github account
Updated over 6 years ago