Helpful Development Tips
Below is a collection of helpful pointers to steer you towards implementing better code within the OneStream solution space.
Text Box Input
While using the OneStream Text Box component to capture user input, it is important to note that there are some potential risks to be mindful of.
XF Component - Parameter Reference
When creating a parameter for usage with a Text Box and actionable component (button, checkbox, combo-box, etc.), it is common to pass this input as an argument to a Business Rule. This could present a problem if the input data referenced contains commas within the string, especially if your actionable component has multiple parameters. Be sure to wrap your referenced parameters in square brackets, to aid in safeguarding against input data with commas.
BEFORE
XF Component - Function Call
{Business Rule Name}{Function / Method Name}{Argument Name = |!Parameter Name!|}
AFTER
XF Component - Function Call
{Business Rule Name}{Function / Method Name}{Argument Name = [|!Parameter Name!|]}
Business Rule - Parameter Retrieval
When consuming a Text Box input parameter from the UI, via Business Rule, it is common to extract this value using the args.NameValuePairs.XFGetValue
. This could be problematic if the input data referenced contains square brackets within the string, especially a closing bracket. If you are passing a parameter, only pass the name of the parameter, without the exclamation marks or pipes, that is linked to your Text Box.
BEFORE
XF Component - Function Call
{SolutionHelper}{SaveFormData}{ArgumentName = [|!ParameterName!|]}
Business Rule - Get Parameter Value
string parameterValue = args.NameValuePairs.XFGetValue("ArgumentName", string.Empty);
AFTER
XF Component - Function Call
{SolutionHelper}{SaveFormData}{ArgumentName = [ParameterName]}
Business Rule - Get Parameter Value (Option #1)
string parameterName = args.NameValuePairs.XFGetValue("ArgumentName", string.Empty);
string parameterValue = args.SelectionChangedTaskInfo.CustomSubstVarsWithUserSelectedValues.XFGetValue(parameterName, string.Empty);
Business Rule - Get Parameter Value (Option #2)
string parameterValue = SelectInputValue(si, args, "ArgumentName");
// This requires the Parameter Function (Option#2) below
Business Rule - Parameter Function (Option #2)
// Requires Get Parameter Value (Option #2) above
/// <summary>
/// Select input value
/// </summary>
/// <param name="si">Session Info</param>
/// <param name="args">Dashboard Extender Arguments</param>
/// <param name="keyName">Parameter Key Name</param>
/// <returns>string</returns>
public static string SelectInputValue(SessionInfo si, DashboardExtenderArgs args, String keyName)
{
try
{
string parameter = args.NameValuePairs.XFGetValue($"{keyName}", string.Empty);
if (args.SelectionChangedTaskInfo.CustomSubstVarsWithUserSelectedValues.ContainsKey(parameter))
{
return args.SelectionChangedTaskInfo.CustomSubstVarsWithUserSelectedValues[parameter];
}
return string.Empty;
}
catch (Exception ex)
{
MethodBase method = MethodBase.GetCurrentMethod();
String methodName = (method != null ? method.Name : "Unknown");
throw BRApi.ErrorLog.LogError(si, new XFException(si, $"An unhandled exception occurred in {methodName}", ex.Message, ex.InnerException));
}
}
Using Session State
When there is a need to access data across an application, modules of a solution, or even resuming the last state a user obtained within a system, OneStream session state may be an ideal choice. Expand below for details.
Business Rule - Storing Session Data
It is important to note that a session is only meant to store small amounts of data and should be used sparingly. At the moment, only String and Data Table objects are supported by default. However, some class objects may be serialized into a JSON string and stored. Session values are unique to a client instance and can be updated by calling the SetSessionState
method a subsequent time.
STRING
Business Rule - Store String Value
var sessionStateKey = "{YOUR PARAMETER NAME}";
var sessionStateValue = "{YOUR PARAMETER VALUE}";
BRApi.State.SetSessionState(si, false, si.ClientModuleType, string.Empty, string.Empty, sessionStateKey, string.Empty, sessionStateValue, null);
DATA TABLE
Business Rule - Serialize Function
/// <summary>
/// Serialize class object
/// </summary>
/// <typeparam name="TObject">Type Object</typeparam>
/// <param name="classObject">Class Object To Serialize</param>
/// <returns>byte[]</returns>
public static byte[]? SerializeObject<TObject>(TObject classObject)
{
byte[]? serializedObject = null;
if (classObject != null) { serializedObject = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(classObject)); }
return serializedObject;
}
Business Rule - Store Data Table Object
var sessionStateKey = "{YOUR PARAMETER NAME}";
var serializedObject = SerializeObject({YOUR DATATABLE OBJECT});
BRApi.State.SetSessionState(si, false, si.ClientModuleType, string.Empty, string.Empty, sessionStateKey, string.Empty, string.Empty, serializedObject);
Business Rule - Retrieving Session Data
Retrieving session values or objects is made easy with the GetSessionState
method, although checking for potential null values is essential.
STRING
Business Rule - Obtain String Value
var sessionStateKey = "{YOUR PARAMETER NAME}";
var userStateObject = BRApi.State.GetSessionState(si, false, si.ClientModuleType, string.Empty, string.Empty, sessionStateKey, string.Empty);
var text = string.Empty;
if (userStateObject != null)
{
text = userStateObject.TextValue;
}
DATA TABLE
Business Rule - Deserialize Function
/// <summary>
/// Deserialize session object
/// </summary>
/// <typeparam name="TResult">Type Result</typeparam>
/// <param name="sessionObject">Session Object To Deserialize</param>
/// <returns>TResult</returns>
public static TResult? DeserializeObject<TResult>(XFUserState sessionObject)
{
TResult? deserializedObject = default;
if (sessionObject != null) { deserializedObject = JsonConvert.DeserializeObject<TResult>(Encoding.UTF8.GetString(sessionObject.BinaryValue)); }
return deserializedObject;
}
Business Rule - Obtain Data Table Object
var sessionStateKey = "{YOUR PARAMETER NAME}";
var userStateObject = BRApi.State.GetSessionState(si, false, si.ClientModuleType, string.Empty, string.Empty, sessionStateKey, string.Empty);
var dt = new DataTable();
if (userStateObject != null)
{
dt = DeserializeObject<DataTable>(userStateObject);
}
Deserializing JSON
If you ever need to consume JSON payload data and parse through its contents, deserialization may be a critical step.
Business Rule - Retrieving JSON Data
While retrieving and transforming JSON data is pretty simple, creating a class structure that matches your payload is essential.
INPUT DATA
JSON Payload - Sample
{
"Data" :
[
{
"Category": "Category One",
"Description": "This is category #1",
"Items": [
{
"Name": "Item A",
"Details": "This is item A"
}
]
},
{
"Category": "Category Two",
"Description": "This is category #2",
"Items": []
}
]
}
LIBRARY REFERENCES
Business Rule
using Newtonsoft.Json;
CLASS DEFINITIONS
Business Rule - Sample
/// <summary>
/// JSON Data Object
/// </summary>
public class JsonData
{
public List<JsonRoot> Data { get; set; }
}
/// <summary>
/// JSON Root Object
/// </summary>
public class JsonRoot
{
public string Category { get; set; }
public string Description { get; set; }
public List<JsonItem> Items { get; set; }
}
/// <summary>
/// JSON Object Item
/// </summary>
public class JsonItem
{
public string Name { get; set; }
public string Details { get; set; }
}
DESERIALIZE FUNCTION
Business Rule - Sample
var result = JsonConvert.DeserializeObject<JsonData>(payload);
Code Organization (Assemblies)
After migrating your solution to version 8, or any above, it can be helpful to take advantage of workspace assembly features. This applies to developing new solutions as well.
Business Rule - Code Structure
Many of us have already migrated our existing solutions to platform version 8 and relocated our business rules to workspace assemblies. However, there is an opportunity to break our code base down into smaller files, increasing organization and readability. The below is just a sample, and not a defacto standard. Be creative and apply what best suits your project requirements. Although when restructuring your solution, it is important to consider that Windows limits us to a file path length of 256 characters (XF Project Extract).
BEFORE
Business Rule Assembly
- Sample Hierarchy
- Dependencies
- Files
- ZZZ_HelperQueries.cs
- ZZZ_ParamHelper.cs
- ZZZ_SharedHelper.cs
- ZZZ_SolutionHelper.cs
AFTER
Business Rule Assembly
- Sample Hierarchy
- Dependencies
- Files
- Business
- DashboardDataSets.cs
- DashboardExtenders.cs
- DashboardStrings.cs
- Database
- DatabaseHelper.cs
- Queries.cs
- Extensions
- ExtendedTaskResult.cs
- Installation
- Setup.cs
- Shared
- Common.cs
- Business