tbf/TBF/Rig/Output/DataStorage/UniDataStorageWriter/UI/WriterCfgCtrl.cs
Marek Frniak 054075a341 <Feat>: Add XML result generation with file and MSSQL output support, increase revision to 3.9.3145.100
Cause:

- ResultsWriter required a generic way to generate customer-specific XML result data from TBF measurement results.
- The customer reference XML contains example runtime values and repeated result structures, so it cannot be used directly as the generated output.
- TBF result variables must be explicitly mapped to destinations in the customer XML structure.
- The generated XML result data must support two output targets:
  - direct creation of an XML file,
  - delivery of the XML payload to a Microsoft SQL stored procedure.
- Preview generation must allow the XML structure and configured mappings to be verified without executing the production database write.
- Increase revision to 3.9.3145.100.

Solution:

1. Added XML reference analysis
   - Creates a clean base XML structure.
   - Extracts the repeating result prototype.
   - Prevents sample runtime values from the customer reference XML from leaking into generated results.
2. Added configurable TBF-to-XML result mapping
   - Allows explicit mapping of TBF result variables to customer XML destinations.
   - Supports one-time and repeating XML destinations.
   - Keeps the mapping independent of the semantic meaning of customer XML attribute names.
3. Added XML destination viewer and configurator
   - Shows the current mapping.
   - Highlights repeating destinations.
   - Identifies already used one-time destinations.
4. Added runtime XML result generation
   - Uses the configured TBF-to-XML mappings.
   - Uses measurement procedure result data.
   - Builds the output from the clean base XML and repeating XML prototype.
   - Generates repeated result records according to the executed measurement procedure.
5. Added simulation-based Preview request
   - Generates a complete XML payload using simulation values.
   - Allows XML structure and mapping verification before production execution.
   - Does not execute the production stored procedure.
6. Added direct XML file output support to UniDataStorageWriter
   - Supports File / .xml as a physical output target.
   - Creates the generated XML result file in the configured output directory.
7. Added Microsoft SQL stored-procedure XML output
   - Supports Microsoft SQL / StoredProcedure as a physical output target.
   - Passes the generated XML payload through the configured stored procedure parameter.
8. Added optional XML payload archiving
   - Allows generated XML payloads to be stored in the configured Payload archive.
   - Can be used together with the Microsoft SQL stored-procedure output.
9. Kept ResultsWriter independent of the physical output target
   - ResultsWriter generates the result payload.
   - UniDataStorageWriter decides how and where the payload is physically written.
   - The same ResultsWriter XML generation mechanism is therefore used for both XML file and MSSQL outputs.
10. Increased revision
   - Updated revision to 3.9.3145.100.
2026-09-02 12:05:10 +02:00

1897 lines
63 KiB
C#

using Common;
using FluentNHibernate.MappingModel.Output;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;
using TBF.Rig.Generic;
using TBF.Rig.Output.DataStorage.UniDataStorageWriter.Interfaces;
using TBF.Rig.Output.DataStorage.UniDataStorageWriter.Writers;
using static TBF.Rig.Output.DataStorage.UniDataStorageWriter.Types;
namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.UI
{
/// <summary>
/// Provides configuration and diagnostic controls for
/// <see cref="Writer"/>.
/// </summary>
/// <remarks>
/// <para>
/// The control configures the storage target, technology, write mode,
/// write templates and optional payload template settings.
/// </para>
///
/// <para>
/// Microsoft SQL configurations can use
/// <see cref="WriteMode.StoredProcedure"/>. In this mode an external XML
/// template can be selected through
/// <see cref="WriterCfg.PayloadTemplatePath"/> and the generated payload
/// can be passed to the parameter configured by
/// <see cref="WriterCfg.PayloadParameterName"/>.
/// </para>
///
/// <para>
/// Diagnostic input formats are:
/// </para>
/// <list type="bullet">
/// <item><description>Insert: <c>Column=Value</c>.</description></item>
/// <item><description>Update: <c>WhereName=WhereValue;SetName=SetValue</c>.</description></item>
/// <item><description>Stored procedure: <c>@Parameter|Type=Value</c>.</description></item>
/// </list>
///
/// <para>
/// Write template entries use the format <c>WriteMode|Template</c>.
/// For stored procedures the template body is the stored procedure name,
/// for example <c>StoredProcedure|dbo.sp_InsertDashboardResults_FF</c>.
/// </para>
/// </remarks>
public partial class WriterCfgCtrl : Configs.ConfigCtrlUtils, IComponentCfgCtrl
{
private readonly List<string> batchWriteLines = new List<string>();
private WriterCfg config;
private bool isUnlocked;
private const string CsvTemplateHint =
"Write template is not used.";
private const string DefaultDatabaseInsertTemplate =
"Insert|INSERT INTO dbo.table ({0}) VALUES ({1})";
private const string DefaultDatabaseUpdateTemplate =
"Update|UPDATE dbo.table SET {2} = {3} WHERE {0} = {1}";
private const string DefaultDatabaseStoredProcedureTemplate =
"StoredProcedure|dbo.StoredProcedureName";
private const string DefaultXlsxInsertTemplate =
"Insert|INSERT INTO Sheet1 ({0}) VALUES ({1})";
private const string DefaultXlsxUpdateTemplate =
"Update|UPDATE Sheet1 SET {2} = {3} WHERE {0} = {1}";
private const int EmSetCueBanner = 0x1501;
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
private static extern IntPtr SendMessage(
IntPtr hWnd,
int msg,
IntPtr wParam,
string lParam);
public bool ShowMore
{
get { return false; }
}
public IComponentCfg Config
{
get { return config as IComponentCfg; }
set
{
config = value as WriterCfg;
Redraw();
}
}
public WriterCfgCtrl()
{
InitializeComponent();
connectToDataSourceButton.Click += connectToDataSourceButton_Click;
writeDataByParamAndTemplateButton.Click += writeDataByParamAndTemplateButton_Click;
buttonAddParam.Click += buttonAddParam_Click;
buttonRemoveParam.Click += buttonRemoveParam_Click;
listBoxWriteParams.DoubleClick += listBoxWriteParams_DoubleClick;
writeModeComboBox.SelectedIndexChanged += writeModeComboBox_SelectedIndexChanged;
dataStorageTypeComboBox.SelectedIndexChanged += dataStorageTypeComboBox_SelectedIndexChanged;
buttonAddTemplate.Click += buttonAddTemplate_Click;
buttonUpdateTemplate.Click += buttonUpdateTemplate_Click;
buttonRemoveTemplate.Click += buttonRemoveTemplate_Click;
writeTemplatesListBox.SelectedIndexChanged += writeTemplatesListBox_SelectedIndexChanged;
technologyTypeComboBox.SelectedIndexChanged += technologyTypeComboBox_SelectedIndexChanged;
browsePayloadTemplateButton.Click += browsePayloadTemplateButton_Click;
browsePayloadArchiveButton.Click += browsePayloadArchiveButton_Click;
archivePayloadCheckBox.CheckedChanged += archivePayloadCheckBox_CheckedChanged;
//
// Diagnostic XML payloads can be multiline. Keep the compact
// control layout but preserve all pasted CR/LF characters.
//
writeParamValueTextBox.Multiline = true;
writeParamValueTextBox.AcceptsReturn = true;
writeParamValueTextBox.AcceptsTab = true;
writeParamValueTextBox.WordWrap = false;
writeParamValueTextBox.ScrollBars = ScrollBars.Both;
}
private void WriterCfgCtrl_Load(object sender, EventArgs e)
{
writeModeComboBox.Items.Clear();
writeModeComboBox.Items.Add(WriteMode.Insert.ToString());
writeModeComboBox.Items.Add(WriteMode.Update.ToString());
writeModeComboBox.Items.Add(WriteMode.StoredProcedure.ToString());
if (writeModeComboBox.Items.Count > 0)
writeModeComboBox.SelectedIndex = 0;
UpdateInputHint();
if (config != null)
Redraw();
ApplyTechnologyConfiguration(true);
}
public void Closing()
{
}
private void Redraw()
{
if (config == null)
return;
classNameLabel.Text = config.Factory.ClassName;
nameTextBox.Text = config.Name;
dataSourceTextBox.Text = config.DataSource;
payloadTemplatePathTextBox.Text = config.PayloadTemplatePath ?? string.Empty;
payloadParameterNameTextBox.Text = config.PayloadParameterName ?? string.Empty;
archivePayloadCheckBox.Checked = config.ArchivePayload;
payloadArchivePathTextBox.Text = config.PayloadArchivePath ?? string.Empty;
dataStorageTypeComboBox.Items.Clear();
ICollection<string> values = config.ParamValues(0);
if (values != null)
{
foreach (string item in values)
dataStorageTypeComboBox.Items.Add(item);
}
dataStorageTypeComboBox.Text = config.DataStorageType;
UpdateTechnologyTypeUi();
if (!string.IsNullOrWhiteSpace(config.TechnologyType))
{
if (technologyTypeComboBox.Items.Contains(config.TechnologyType))
technologyTypeComboBox.SelectedItem = config.TechnologyType;
else
technologyTypeComboBox.Text = config.TechnologyType;
}
else
{
technologyTypeComboBox.Text = string.Empty;
}
writeModeComboBox.SelectedItem = config.WriteMode.ToString();
if (writeModeComboBox.SelectedIndex < 0)
writeModeComboBox.SelectedItem = WriteMode.Insert.ToString();
RefreshTemplatesListBox();
UpdateInputHint();
RefreshWriteParamsListBox();
ApplyTechnologyConfiguration(true);
ApplyPayloadConfiguration();
}
public void Unlock()
{
isUnlocked = true;
nameTextBox.Enabled = true;
dataStorageTypeComboBox.Enabled = true;
dataSourceTextBox.Enabled = true;
sourceTestResultTextBox.Enabled = true;
sourceTestResultTextBox.ReadOnly = true;
writeTestResultTextBox.Enabled = true;
writeTestResultTextBox.ReadOnly = true;
writeParamValueTextBox.Enabled = true;
listBoxWriteParams.Enabled = true;
buttonAddParam.Enabled = true;
buttonRemoveParam.Enabled = true;
writeModeComboBox.Enabled = true;
technologyTypeComboBox.Enabled = true;
ApplyTechnologyConfiguration(false);
ApplyPayloadConfiguration();
}
/// <summary>
/// Updates source hints, default templates and template control availability
/// according to the selected technology.
/// </summary>
/// <param name="initializeDefaults">
/// <see langword="true"/> to create default templates when the template list
/// is empty; otherwise, <see langword="false"/>.
/// </param>
private void ApplyTechnologyConfiguration(bool initializeDefaults)
{
string technologyType =
(technologyTypeComboBox.Text ?? string.Empty).Trim();
SetDataSourceHint(
GetDataSourceHint(
technologyType));
bool xmlFileTechnology =
technologyType ==
TechnologyTypes.Xml;
//
// XML file output always receives one complete generated document.
// SQL-style write modes and write templates are therefore not used.
//
if (xmlFileTechnology)
{
if (writeModeComboBox.Items.Contains(
WriteMode.Insert.ToString()))
{
writeModeComboBox.SelectedItem =
WriteMode.Insert.ToString();
}
writeModeComboBox.Enabled =
false;
writeTemplatesListBox.Items.Clear();
templateEditTextBox.Text =
"Write template is not used for XML payload files.";
SetTemplateControlsEnabled(
false);
UpdateInputHint();
ApplyPayloadConfiguration();
return;
}
writeModeComboBox.Enabled =
isUnlocked;
bool templatesSupported =
technologyType ==
TechnologyTypes.MicrosoftSql ||
technologyType ==
TechnologyTypes.Xlsx ||
technologyType ==
TechnologyTypes.Xls;
if (technologyType ==
TechnologyTypes.Csv)
{
writeTemplatesListBox.Items.Clear();
templateEditTextBox.Text =
CsvTemplateHint;
SetTemplateControlsEnabled(
false);
UpdateInputHint();
ApplyPayloadConfiguration();
return;
}
if (initializeDefaults &&
templatesSupported &&
writeTemplatesListBox.Items.Count == 0)
{
AddDefaultTemplates(
technologyType);
}
if (!templatesSupported)
{
writeTemplatesListBox.Items.Clear();
templateEditTextBox.Clear();
}
else if (templateEditTextBox.Text ==
CsvTemplateHint ||
templateEditTextBox.Text ==
"Write template is not used for XML payload files.")
{
templateEditTextBox.Clear();
}
SetTemplateControlsEnabled(
templatesSupported &&
isUnlocked);
UpdateInputHint();
ApplyPayloadConfiguration();
}
/// <summary>
/// Adds default write templates for the selected technology.
/// </summary>
/// <param name="technologyType">
/// Selected writer technology type.
/// </param>
private void AddDefaultTemplates(string technologyType)
{
writeTemplatesListBox.Items.Clear();
switch (technologyType)
{
case TechnologyTypes.MicrosoftSql:
writeTemplatesListBox.Items.Add(
DefaultDatabaseInsertTemplate);
writeTemplatesListBox.Items.Add(
DefaultDatabaseUpdateTemplate);
writeTemplatesListBox.Items.Add(
DefaultDatabaseStoredProcedureTemplate);
break;
case TechnologyTypes.Xlsx:
case TechnologyTypes.Xls:
writeTemplatesListBox.Items.Add(
DefaultXlsxInsertTemplate);
writeTemplatesListBox.Items.Add(
DefaultXlsxUpdateTemplate);
break;
}
if (writeTemplatesListBox.Items.Count > 0)
writeTemplatesListBox.SelectedIndex = 0;
}
/// <summary>
/// Enables or disables controls used for write template configuration.
/// </summary>
/// <param name="enabled">
/// <see langword="true"/> to enable template editing; otherwise,
/// <see langword="false"/>.
/// </param>
private void SetTemplateControlsEnabled(bool enabled)
{
writeTemplatesListBox.Enabled = enabled;
templateEditTextBox.Enabled = enabled;
buttonAddTemplate.Enabled = enabled;
buttonUpdateTemplate.Enabled = enabled;
buttonRemoveTemplate.Enabled = enabled;
examples1Button.Enabled = enabled;
}
public CfgUpdateFlags VerifyCfg(ref string message)
{
return CfgUpdateFlags.None;
}
public CfgUpdateFlags UpdateCfg()
{
CfgUpdateFlags flags = CfgUpdateFlags.None;
if (config == null)
return CfgUpdateFlags.Error;
WriteMode mode;
if (!Enum.TryParse(writeModeComboBox.Text, true, out mode))
mode = WriteMode.Insert;
if (!string.Equals(config.Name, nameTextBox.Text, StringComparison.Ordinal))
{
config.Name = nameTextBox.Text;
flags |= (CfgUpdateFlags.AnyChange | CfgUpdateFlags.RestartRqrd);
}
if (config.WriteMode != mode)
{
config.WriteMode = mode;
flags |= (CfgUpdateFlags.AnyChange | CfgUpdateFlags.RestartRqrd);
}
List<string> newTemplates = writeTemplatesListBox.Items.Cast<string>().ToList();
if (config.WriteTemplates == null ||
config.WriteTemplates.Count != newTemplates.Count ||
!config.WriteTemplates.SequenceEqual(newTemplates))
{
config.WriteTemplates = newTemplates;
flags |= (CfgUpdateFlags.AnyChange | CfgUpdateFlags.RestartRqrd);
}
flags |= UpdateDifferent(
ref config.DataStorageType,
dataStorageTypeComboBox.Text,
CfgUpdateFlags.AnyChange | CfgUpdateFlags.RestartRqrd);
flags |= UpdateDifferent(
ref config.TechnologyType,
technologyTypeComboBox.Text,
CfgUpdateFlags.AnyChange | CfgUpdateFlags.RestartRqrd);
flags |= UpdateDifferent(
ref config.DataSource,
dataSourceTextBox.Text,
CfgUpdateFlags.AnyChange | CfgUpdateFlags.RestartRqrd);
flags |= UpdateDifferent(
ref config.PayloadTemplatePath,
payloadTemplatePathTextBox.Text,
CfgUpdateFlags.AnyChange | CfgUpdateFlags.RestartRqrd);
flags |= UpdateDifferent(
ref config.PayloadParameterName,
payloadParameterNameTextBox.Text,
CfgUpdateFlags.AnyChange | CfgUpdateFlags.RestartRqrd);
if (config.ArchivePayload != archivePayloadCheckBox.Checked)
{
config.ArchivePayload = archivePayloadCheckBox.Checked;
flags |= (CfgUpdateFlags.AnyChange | CfgUpdateFlags.RestartRqrd);
}
flags |= UpdateDifferent(
ref config.PayloadArchivePath,
payloadArchivePathTextBox.Text,
CfgUpdateFlags.AnyChange | CfgUpdateFlags.RestartRqrd);
return flags;
}
private WriterCfg BuildTemporaryConfigFromUi()
{
WriterCfg tmpCfg = new WriterCfg(
string.IsNullOrWhiteSpace(nameTextBox.Text) ? "UniDataStorageWriter" : nameTextBox.Text,
config != null ? config.Factory : new Factory());
WriteMode mode;
if (!Enum.TryParse(writeModeComboBox.Text, true, out mode))
mode = WriteMode.Insert;
tmpCfg.WriteMode = mode;
tmpCfg.DataStorageType = (dataStorageTypeComboBox.Text ?? string.Empty).Trim();
tmpCfg.TechnologyType = (technologyTypeComboBox.Text ?? string.Empty).Trim();
tmpCfg.DataSource = dataSourceTextBox.Text;
tmpCfg.PayloadTemplatePath = (payloadTemplatePathTextBox.Text ?? string.Empty).Trim();
tmpCfg.PayloadParameterName = (payloadParameterNameTextBox.Text ?? string.Empty).Trim();
tmpCfg.ArchivePayload = archivePayloadCheckBox.Checked;
tmpCfg.PayloadArchivePath = (payloadArchivePathTextBox.Text ?? string.Empty).Trim();
tmpCfg.WriteTemplates = writeTemplatesListBox.Items
.Cast<string>()
.ToList();
string selectedTemplate = tmpCfg.GetTemplate(mode);
tmpCfg.QueryTemplate = selectedTemplate;
return tmpCfg;
}
private IDataStorageWriter CreateWriter(WriterCfg cfg)
{
if (cfg == null)
throw new ArgumentNullException(nameof(cfg));
string storageType = (cfg.DataStorageType ?? string.Empty).Trim();
string technologyType = (cfg.TechnologyType ?? string.Empty).Trim();
switch (storageType)
{
case StorageTypes.RestApi:
throw new NotSupportedException("REST API writer is not supported yet.");
case StorageTypes.LocalDatabase:
case StorageTypes.RemoteDatabase:
switch (technologyType)
{
case TechnologyTypes.MicrosoftSql:
return new DatabaseWriter(cfg);
case TechnologyTypes.MySqlMariaDb:
throw new NotSupportedException("MySQL (MariaDB) writer is not supported yet.");
case TechnologyTypes.SQLite:
throw new NotSupportedException("SQLite writer is not supported yet.");
default:
throw new NotSupportedException(
string.Format("Unsupported database technology type: '{0}'", cfg.TechnologyType));
}
case StorageTypes.LocalFile:
case StorageTypes.RemoteFile:
switch (technologyType)
{
case TechnologyTypes.Csv:
return new CsvWriter(cfg);
case TechnologyTypes.Xlsx:
return new XlsxWriter(cfg);
case TechnologyTypes.Json:
throw new NotSupportedException("JSON writer is not supported yet.");
case TechnologyTypes.Xml:
return new XmlFileWriter(cfg);
case TechnologyTypes.Xls:
throw new NotSupportedException("XLS writer is not supported yet.");
default:
throw new NotSupportedException(
string.Format("Unsupported file technology type: '{0}'", cfg.TechnologyType));
}
default:
throw new NotSupportedException(
string.Format("Unsupported DataStorageType: '{0}'", cfg.DataStorageType));
}
}
private void connectToDataSourceButton_Click(object sender, EventArgs e)
{
sourceTestResultTextBox.Clear();
try
{
WriterCfg cfg = BuildTemporaryConfigFromUi();
Writer component = new Writer(cfg);
WriterDiagnosticResult result = component.TestSource(true);
sourceTestResultTextBox.Text =
BuildDiagnosticHeader(cfg, "Source test") +
(result != null ? result.ToDisplayDiag() : "<null>");
}
catch (Exception ex)
{
sourceTestResultTextBox.Text = BuildExceptionText("Data source test failed", ex);
}
}
/// <summary>
/// Executes a diagnostic write using the values currently configured
/// in the control.
/// </summary>
private void writeDataByParamAndTemplateButton_Click(object sender, EventArgs e)
{
writeTestResultTextBox.Clear();
try
{
WriteMode mode;
if (!Enum.TryParse(writeModeComboBox.Text, true, out mode))
mode = WriteMode.Insert;
if ((mode == WriteMode.Insert || mode == WriteMode.Update) &&
batchWriteLines.Count == 0)
{
throw new InvalidOperationException(
"At least one batch test item must be provided.");
}
WriterCfg cfg = BuildTemporaryConfigFromUi();
Writer component = new Writer(cfg);
DataWriteRequest request = BuildWriteRequest(batchWriteLines, mode);
WriterDiagnosticResult result = component.SetData(request);
writeTestResultTextBox.Text =
BuildDiagnosticHeader(cfg, "Write test") +
(result != null ? result.ToDisplayDiag() : "<null>");
}
catch (Exception ex)
{
writeTestResultTextBox.Text = BuildExceptionText("WriteData test failed", ex);
}
}
/// <summary>
/// Builds a write request from diagnostic input lines.
/// </summary>
private DataWriteRequest BuildWriteRequest(
IEnumerable<string> sourceLines,
WriteMode mode)
{
if (sourceLines == null)
throw new ArgumentNullException(
nameof(sourceLines));
DataWriteRequest request =
new DataWriteRequest();
request.Mode =
mode;
if (IsXmlFileUiTarget())
{
string xmlPayload =
sourceLines
.Where(
line =>
!string.IsNullOrWhiteSpace(
line))
.FirstOrDefault();
if (string.IsNullOrWhiteSpace(
xmlPayload))
{
throw new InvalidOperationException(
"XML payload must be provided.");
}
ValidateXmlPayload(
xmlPayload);
request.Mode =
WriteMode.Insert;
request.Payload =
xmlPayload;
request.OutputFileName =
"UDSW_Test_" +
DateTime.Now.ToString(
"yyyyMMdd_HHmmss_fff") +
".xml";
return request;
}
foreach (string rawLine
in sourceLines)
{
string line =
rawLine == null
? string.Empty
: rawLine.Trim();
if (string.IsNullOrWhiteSpace(
line))
{
continue;
}
switch (mode)
{
case WriteMode.Insert:
request.InsertItems.Add(
ParseInsertItem(
line));
break;
case WriteMode.Update:
request.UpdateItems.Add(
ParseUpdateItem(
line));
break;
case WriteMode.StoredProcedure:
request.StoredProcedureParameters.Add(
ParseStoredProcedureParameter(
line));
break;
}
}
return request;
}
/// <summary>
/// Parses an insert parameter in the format <c>Column=Value</c>.
/// </summary>
/// <param name="text">
/// Text containing the column name and value.
/// </param>
/// <returns>
/// Parsed insert write item.
/// </returns>
/// <exception cref="InvalidOperationException">
/// Thrown when the input format is invalid.
/// </exception>
private InsertWriteItem ParseInsertItem(string text)
{
if (string.IsNullOrWhiteSpace(text))
throw new InvalidOperationException("Insert item is empty.");
string[] parts = text
.Split(new[] { '=' }, 2, StringSplitOptions.None)
.Select(p => p.Trim())
.ToArray();
if (parts.Length != 2)
throw new InvalidOperationException(
"Insert format must be: Column=Value");
if (string.IsNullOrWhiteSpace(parts[0]))
throw new InvalidOperationException(
"Insert column name cannot be empty.");
if (string.IsNullOrWhiteSpace(parts[1]))
throw new InvalidOperationException(
"Insert value cannot be empty.");
return new InsertWriteItem
{
ColumnName = parts[0],
Value = parts[1]
};
}
/// <summary>
/// Parses an update parameter in the format
/// <c>WhereName=WhereValue;SetName=SetValue</c>.
/// </summary>
/// <param name="text">
/// Text containing the update condition and target value.
/// </param>
/// <returns>
/// Parsed update write item.
/// </returns>
/// <exception cref="InvalidOperationException">
/// Thrown when the input format is invalid.
/// </exception>
private UpdateWriteItem ParseUpdateItem(string text)
{
if (string.IsNullOrWhiteSpace(text))
throw new InvalidOperationException("Update item is empty.");
string[] pairs = text
.Split(new[] { ';' }, StringSplitOptions.None)
.Select(p => p.Trim())
.ToArray();
if (pairs.Length != 2)
{
throw new InvalidOperationException(
"Update format must be: " +
"WhereName=WhereValue;SetName=SetValue");
}
string[] whereParts = pairs[0]
.Split(new[] { '=' }, 2, StringSplitOptions.None)
.Select(p => p.Trim())
.ToArray();
string[] setParts = pairs[1]
.Split(new[] { '=' }, 2, StringSplitOptions.None)
.Select(p => p.Trim())
.ToArray();
if (whereParts.Length != 2 || setParts.Length != 2)
{
throw new InvalidOperationException(
"Update format must be: " +
"WhereName=WhereValue;SetName=SetValue");
}
if (string.IsNullOrWhiteSpace(whereParts[0]))
throw new InvalidOperationException(
"WHERE column name cannot be empty.");
if (string.IsNullOrWhiteSpace(whereParts[1]))
throw new InvalidOperationException(
"WHERE value cannot be empty.");
if (string.IsNullOrWhiteSpace(setParts[0]))
throw new InvalidOperationException(
"SET column name cannot be empty.");
if (string.IsNullOrWhiteSpace(setParts[1]))
throw new InvalidOperationException(
"SET value cannot be empty.");
return new UpdateWriteItem
{
WhereParameterName = whereParts[0],
WhereValue = whereParts[1],
SetParameterName = setParts[0],
SetValue = setParts[1]
};
}
/// <summary>
/// Parses a stored procedure parameter definition entered in the
/// diagnostic UI.
/// </summary>
/// <param name="text">
/// Parameter definition in the format
/// <c>ParameterName|ParameterType=Value</c>.
/// </param>
/// <returns>
/// Parsed and strongly typed <see cref="StoredProcedureWriteParameter"/>.
/// </returns>
/// <exception cref="InvalidOperationException">
/// Thrown when the parameter definition is invalid or cannot be
/// converted to the requested type.
/// </exception>
/// <remarks>
/// Only the first <c>=</c> after the parameter type is used as the
/// separator. XML attributes containing additional <c>=</c> characters
/// are therefore preserved.
/// </remarks>
private StoredProcedureWriteParameter ParseStoredProcedureParameter(string text)
{
if (string.IsNullOrWhiteSpace(text))
throw new InvalidOperationException("Stored procedure parameter is empty.");
int typeSeparatorIndex = text.IndexOf('|');
if (typeSeparatorIndex <= 0)
{
throw new InvalidOperationException(
"Stored procedure parameter format must be: ParameterName|ParameterType=Value");
}
int valueSeparatorIndex = text.IndexOf('=', typeSeparatorIndex + 1);
if (valueSeparatorIndex <= typeSeparatorIndex + 1)
{
throw new InvalidOperationException(
"Stored procedure parameter format must be: ParameterName|ParameterType=Value");
}
string parameterName = text.Substring(0, typeSeparatorIndex).Trim();
string parameterTypeText = text.Substring(
typeSeparatorIndex + 1,
valueSeparatorIndex - typeSeparatorIndex - 1).Trim();
string valueText = text.Substring(valueSeparatorIndex + 1);
if (string.IsNullOrWhiteSpace(parameterName))
throw new InvalidOperationException("Stored procedure parameter name cannot be empty.");
if (!parameterName.StartsWith("@", StringComparison.Ordinal))
parameterName = "@" + parameterName;
StoredProcedureParameterType parameterType;
if (!Enum.TryParse(parameterTypeText, true, out parameterType))
{
throw new InvalidOperationException(
"Unsupported stored procedure parameter type: " + parameterTypeText);
}
return new StoredProcedureWriteParameter
{
ParameterName = parameterName,
ParameterType = parameterType,
Value = ConvertStoredProcedureValue(valueText, parameterType)
};
}
/// <summary>
/// Converts a diagnostic text value to the .NET value corresponding
/// to the selected stored procedure parameter type.
/// </summary>
private object ConvertStoredProcedureValue(
string value,
StoredProcedureParameterType parameterType)
{
switch (parameterType)
{
case StoredProcedureParameterType.String:
case StoredProcedureParameterType.Xml:
return value;
case StoredProcedureParameterType.Int32:
{
int parsedValue;
if (!int.TryParse(
value,
NumberStyles.Integer,
CultureInfo.InvariantCulture,
out parsedValue))
{
throw new InvalidOperationException(
"Value is not a valid Int32: " + value);
}
return parsedValue;
}
case StoredProcedureParameterType.Int64:
{
long parsedValue;
if (!long.TryParse(
value,
NumberStyles.Integer,
CultureInfo.InvariantCulture,
out parsedValue))
{
throw new InvalidOperationException(
"Value is not a valid Int64: " + value);
}
return parsedValue;
}
case StoredProcedureParameterType.Decimal:
{
decimal parsedValue;
if (!decimal.TryParse(
value,
NumberStyles.Number,
CultureInfo.InvariantCulture,
out parsedValue))
{
throw new InvalidOperationException(
"Value is not a valid Decimal: " + value);
}
return parsedValue;
}
case StoredProcedureParameterType.Boolean:
{
bool parsedValue;
if (bool.TryParse(value, out parsedValue))
return parsedValue;
if (value == "1")
return true;
if (value == "0")
return false;
throw new InvalidOperationException(
"Value is not a valid Boolean: " + value);
}
case StoredProcedureParameterType.DateTime:
{
DateTime parsedValue;
if (!DateTime.TryParse(
value,
CultureInfo.InvariantCulture,
DateTimeStyles.AllowWhiteSpaces | DateTimeStyles.RoundtripKind,
out parsedValue))
{
throw new InvalidOperationException(
"Value is not a valid DateTime: " + value);
}
return parsedValue;
}
default:
throw new InvalidOperationException(
"Unsupported stored procedure parameter type: " + parameterType);
}
}
private void buttonAddParam_Click(
object sender,
EventArgs e)
{
string text =
writeParamValueTextBox.Text == null
? string.Empty
: writeParamValueTextBox.Text.Trim();
if (string.IsNullOrWhiteSpace(
text))
{
return;
}
try
{
if (IsXmlFileUiTarget())
{
ValidateXmlPayload(
text);
//
// One diagnostic XML-file request represents one complete
// document, therefore keep only one payload row.
//
batchWriteLines.Clear();
batchWriteLines.Add(
text);
writeParamValueTextBox.Clear();
RefreshWriteParamsListBox();
return;
}
WriteMode mode;
if (!Enum.TryParse(
writeModeComboBox.Text,
true,
out mode))
{
mode =
WriteMode.Insert;
}
switch (mode)
{
case WriteMode.Insert:
ParseInsertItem(
text);
break;
case WriteMode.Update:
ParseUpdateItem(
text);
break;
case WriteMode.StoredProcedure:
ParseStoredProcedureParameter(
text);
break;
}
batchWriteLines.Add(
text);
writeParamValueTextBox.Clear();
RefreshWriteParamsListBox();
}
catch (InvalidOperationException ex)
{
writeTestResultTextBox.Text =
"Invalid write parameter:" +
Environment.NewLine +
ex.Message;
writeParamValueTextBox.Focus();
writeParamValueTextBox.SelectAll();
}
}
private void buttonRemoveParam_Click(object sender, EventArgs e)
{
int index = listBoxWriteParams.SelectedIndex;
if (index < 0 || index >= batchWriteLines.Count)
return;
batchWriteLines.RemoveAt(index);
RefreshWriteParamsListBox();
}
private void RefreshWriteParamsListBox()
{
listBoxWriteParams.Items.Clear();
for (int i = 0; i < batchWriteLines.Count; i++)
{
listBoxWriteParams.Items.Add(
string.Format(
"{0}. {1}",
i + 1,
BuildWriteParameterDisplayText(
batchWriteLines[i])));
}
}
/// <summary>
/// Creates a compact display text for one diagnostic write parameter.
/// </summary>
/// <remarks>
/// XML stored-procedure parameters are intentionally abbreviated in the
/// list because the complete XML can be inspected by double-clicking
/// the row.
/// </remarks>
private string BuildWriteParameterDisplayText(
string rawLine)
{
if (string.IsNullOrWhiteSpace(
rawLine))
{
return string.Empty;
}
if (IsXmlFileUiTarget())
{
string rootName =
TryGetXmlRootName(
rawLine);
return string.Format(
"XML payload | <{0} ...> [double-click to view]",
string.IsNullOrWhiteSpace(
rootName)
? "XML"
: rootName);
}
WriteMode mode;
if (!Enum.TryParse(
writeModeComboBox.Text,
true,
out mode) ||
mode !=
WriteMode.StoredProcedure)
{
return rawLine;
}
try
{
StoredProcedureWriteParameter parameter =
ParseStoredProcedureParameter(
rawLine);
if (parameter.ParameterType !=
StoredProcedureParameterType.Xml)
{
return rawLine;
}
string xml =
Convert.ToString(
parameter.Value);
string rootName =
TryGetXmlRootName(
xml);
return string.Format(
"{0} | Xml | <{1} ...> [double-click to view]",
parameter.ParameterName,
string.IsNullOrWhiteSpace(
rootName)
? "XML"
: rootName);
}
catch
{
return rawLine;
}
}
/// <summary>
/// Returns the root element name of an XML string when possible.
/// </summary>
private string TryGetXmlRootName(
string xml)
{
if (string.IsNullOrWhiteSpace(xml))
return null;
try
{
System.Xml.Linq.XDocument document =
System.Xml.Linq.XDocument.Parse(
xml,
System.Xml.Linq.LoadOptions.PreserveWhitespace);
return document.Root != null
? document.Root.Name.LocalName
: null;
}
catch
{
return null;
}
}
/// <summary>
/// Opens a read-only XML payload viewer for an XML stored-procedure parameter.
/// </summary>
private void listBoxWriteParams_DoubleClick(
object sender,
EventArgs e)
{
int index =
listBoxWriteParams.SelectedIndex;
if (index < 0 ||
index >=
batchWriteLines.Count)
{
return;
}
try
{
if (IsXmlFileUiTarget())
{
using (XmlPayloadViewerDlg dlg =
new XmlPayloadViewerDlg())
{
dlg.ParameterName =
"XML file payload";
dlg.XmlPayload =
batchWriteLines[index];
dlg.ShowDialog(
this);
}
return;
}
WriteMode mode;
if (!Enum.TryParse(
writeModeComboBox.Text,
true,
out mode) ||
mode !=
WriteMode.StoredProcedure)
{
return;
}
StoredProcedureWriteParameter parameter =
ParseStoredProcedureParameter(
batchWriteLines[index]);
if (parameter.ParameterType !=
StoredProcedureParameterType.Xml)
{
return;
}
string xml =
Convert.ToString(
parameter.Value);
using (XmlPayloadViewerDlg dlg =
new XmlPayloadViewerDlg())
{
dlg.ParameterName =
parameter.ParameterName;
dlg.XmlPayload =
xml;
dlg.ShowDialog(
this);
}
}
catch (Exception ex)
{
MessageBox.Show(
this,
ex.Message,
"XML payload viewer",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
}
/// <summary>
/// Updates the diagnostic input hint according to the selected write mode.
/// </summary>
private void UpdateInputHint()
{
if (IsXmlFileUiTarget())
{
labelParamValue.Text =
"XML payload: paste complete XML document";
if (string.IsNullOrWhiteSpace(
writeParamValueTextBox.Text) ||
writeParamValueTextBox.Text ==
"Column=Value" ||
writeParamValueTextBox.Text.StartsWith(
"WhereName=",
StringComparison.Ordinal) ||
writeParamValueTextBox.Text.StartsWith(
"@DashboardResults|",
StringComparison.Ordinal))
{
writeParamValueTextBox.Text =
"<BATCH />";
}
ApplyPayloadConfiguration();
return;
}
WriteMode mode;
if (!Enum.TryParse(
writeModeComboBox.Text,
true,
out mode))
{
mode =
WriteMode.Insert;
}
switch (mode)
{
case WriteMode.Insert:
labelParamValue.Text =
"Insert: Column=Value";
writeParamValueTextBox.Text =
"Column=Value";
break;
case WriteMode.Update:
labelParamValue.Text =
"Update: WhereName=WhereValue;SetName=SetValue";
writeParamValueTextBox.Text =
"WhereName=WhereValue;SetName=SetValue";
break;
case WriteMode.StoredProcedure:
labelParamValue.Text =
"Stored procedure: Parameter|Type=Value";
writeParamValueTextBox.Text =
"@DashboardResults|Xml=<BATCH />";
break;
default:
labelParamValue.Text =
"Parameter";
writeParamValueTextBox.Clear();
break;
}
ApplyPayloadConfiguration();
}
/// <summary>
/// Returns whether the UI currently represents a direct XML-file target.
/// </summary>
private bool IsXmlFileUiTarget()
{
string storageType =
(dataStorageTypeComboBox.Text ??
string.Empty)
.Trim();
string technologyType =
(technologyTypeComboBox.Text ??
string.Empty)
.Trim();
return
(storageType ==
StorageTypes.LocalFile ||
storageType ==
StorageTypes.RemoteFile) &&
technologyType ==
TechnologyTypes.Xml;
}
/// <summary>
/// Validates one complete XML diagnostic payload.
/// </summary>
private void ValidateXmlPayload(
string xml)
{
if (string.IsNullOrWhiteSpace(
xml))
{
throw new InvalidOperationException(
"XML payload is empty.");
}
try
{
System.Xml.Linq.XDocument.Parse(
xml,
System.Xml.Linq.LoadOptions.PreserveWhitespace);
}
catch (Exception ex)
{
throw new InvalidOperationException(
"XML payload is invalid: " +
ex.Message,
ex);
}
}
private string BuildDiagnosticHeader(WriterCfg cfg, string testName)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("=== " + testName + " ===");
sb.AppendLine("Storage type: " + StorageTypeToDisplayName(cfg.DataStorageType));
sb.AppendLine("Technology type: " + Safe(cfg.TechnologyType));
sb.AppendLine("Data source: " + Safe(cfg.DataSource));
sb.AppendLine("Write mode: " + Safe(writeModeComboBox.Text));
WriteMode mode;
if (!Enum.TryParse(writeModeComboBox.Text, true, out mode))
mode = WriteMode.Insert;
string template = cfg.GetTemplate(mode);
sb.AppendLine("Write template: " + Safe(template));
sb.AppendLine("Payload template: " + Safe(cfg.PayloadTemplatePath));
sb.AppendLine("Payload parameter: " + Safe(cfg.PayloadParameterName));
sb.AppendLine("Archive payload: " + cfg.ArchivePayload);
if (cfg.ArchivePayload)
sb.AppendLine("Payload archive path: " + Safe(cfg.PayloadArchivePath));
sb.AppendLine();
return sb.ToString();
}
private string BuildExceptionText(string title, Exception ex)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine(title + ":");
sb.AppendLine(ex.Message);
Exception inner = ex.InnerException;
while (inner != null)
{
sb.AppendLine();
sb.AppendLine("Inner exception:");
sb.AppendLine(inner.Message);
inner = inner.InnerException;
}
return sb.ToString();
}
public static string Safe(string text)
{
return string.IsNullOrWhiteSpace(text) ? "<empty>" : text;
}
private string StorageTypeToDisplayName(string storageType)
{
switch ((storageType ?? string.Empty).Trim())
{
case StorageTypes.RestApi:
return "REST API";
case StorageTypes.LocalDatabase:
return "Local database";
case StorageTypes.RemoteDatabase:
return "Remote database";
case StorageTypes.LocalFile:
return "Local file";
case StorageTypes.RemoteFile:
return "Remote file";
default:
return Safe(storageType);
}
}
private void dataStorageTypeComboBox_SelectedIndexChanged(object sender, EventArgs e)
{
UpdateTechnologyTypeUi();
}
private void UpdateTechnologyTypeUi()
{
string previousTechnology =
(technologyTypeComboBox.Text ?? string.Empty).Trim();
string storageType =
(dataStorageTypeComboBox.Text ?? string.Empty).Trim();
technologyTypeComboBox.Items.Clear();
switch (storageType)
{
case StorageTypes.LocalDatabase:
case StorageTypes.RemoteDatabase:
technologyTypeLabel.Text = "Database type:";
technologyTypeComboBox.Items.Add(
TechnologyTypes.MicrosoftSql);
technologyTypeComboBox.Items.Add(
TechnologyTypes.MySqlMariaDb);
technologyTypeComboBox.Items.Add(
TechnologyTypes.SQLite);
break;
case StorageTypes.LocalFile:
case StorageTypes.RemoteFile:
technologyTypeLabel.Text = "File type:";
technologyTypeComboBox.Items.Add(
TechnologyTypes.Csv);
technologyTypeComboBox.Items.Add(
TechnologyTypes.Xlsx);
technologyTypeComboBox.Items.Add(
TechnologyTypes.Xls);
technologyTypeComboBox.Items.Add(
TechnologyTypes.Json);
technologyTypeComboBox.Items.Add(
TechnologyTypes.Xml);
break;
default:
technologyTypeLabel.Text = "Technology type:";
break;
}
if (!string.IsNullOrWhiteSpace(previousTechnology) &&
technologyTypeComboBox.Items.Contains(previousTechnology))
{
technologyTypeComboBox.SelectedItem =
previousTechnology;
}
else if (technologyTypeComboBox.Items.Count > 0)
{
technologyTypeComboBox.SelectedIndex = 0;
}
ApplyTechnologyConfiguration(true);
}
private void writeModeComboBox_SelectedIndexChanged(object sender, EventArgs e)
{
UpdateInputHint();
}
private void RefreshTemplatesListBox()
{
writeTemplatesListBox.Items.Clear();
if (config == null || config.WriteTemplates == null)
return;
foreach (string item in config.WriteTemplates)
writeTemplatesListBox.Items.Add(item);
}
private void writeTemplatesListBox_SelectedIndexChanged(object sender, EventArgs e)
{
if (writeTemplatesListBox.SelectedItem == null)
{
templateEditTextBox.Clear();
return;
}
templateEditTextBox.Text = writeTemplatesListBox.SelectedItem.ToString();
}
private void technologyTypeComboBox_SelectedIndexChanged(object sender, EventArgs e)
{
ApplyTechnologyConfiguration(true);
}
private void buttonAddTemplate_Click(object sender, EventArgs e)
{
string text = (templateEditTextBox.Text ?? string.Empty).Trim();
if (string.IsNullOrWhiteSpace(text))
return;
writeTemplatesListBox.Items.Add(text);
templateEditTextBox.Clear();
}
private void buttonUpdateTemplate_Click(object sender, EventArgs e)
{
int index = writeTemplatesListBox.SelectedIndex;
if (index < 0)
return;
string text = (templateEditTextBox.Text ?? string.Empty).Trim();
if (string.IsNullOrWhiteSpace(text))
return;
// Update only the UI list. Configuration will be synchronized in UpdateCfg().
writeTemplatesListBox.Items[index] = text;
writeTemplatesListBox.SelectedIndex = index;
}
/// <summary>
/// Removes selected template from UI list.
/// Configuration is updated later in UpdateCfg().
/// </summary>
private void buttonRemoveTemplate_Click(object sender, EventArgs e)
{
// Get selected index from list
int index = writeTemplatesListBox.SelectedIndex;
// Validate selection
if (index < 0)
return;
// Remove item only from UI (NOT from config!)
writeTemplatesListBox.Items.RemoveAt(index);
// Clear edit textbox
templateEditTextBox.Clear();
// Optional: select next valid item (better UX)
if (writeTemplatesListBox.Items.Count > 0)
{
int newIndex = Math.Min(index, writeTemplatesListBox.Items.Count - 1);
writeTemplatesListBox.SelectedIndex = newIndex;
}
}
/// <summary>
/// Enables or disables payload-specific configuration controls.
/// </summary>
/// <remarks>
/// Payload templates are currently exposed for Microsoft SQL stored
/// procedure mode. The fields remain part of the configuration even
/// when the controls are disabled.
/// </remarks>
private void ApplyPayloadConfiguration()
{
WriteMode mode;
if (!Enum.TryParse(
writeModeComboBox.Text,
true,
out mode))
{
mode =
WriteMode.Insert;
}
string storageType =
(dataStorageTypeComboBox.Text ??
string.Empty)
.Trim();
string technologyType =
(technologyTypeComboBox.Text ??
string.Empty)
.Trim();
bool storedProcedurePayload =
(storageType ==
StorageTypes.LocalDatabase ||
storageType ==
StorageTypes.RemoteDatabase) &&
technologyType ==
TechnologyTypes.MicrosoftSql &&
mode ==
WriteMode.StoredProcedure;
bool xmlFilePayload =
(storageType ==
StorageTypes.LocalFile ||
storageType ==
StorageTypes.RemoteFile) &&
technologyType ==
TechnologyTypes.Xml;
bool payloadEditingEnabled =
isUnlocked &&
(storedProcedurePayload ||
xmlFilePayload);
groupBox6.Text =
xmlFilePayload
? "XML payload setting"
: "Payload template setting";
payloadTemplatePathLabel.Text =
xmlFilePayload
? "Payload reference:"
: "Payload template:";
payloadTemplatePathTextBox.Enabled =
payloadEditingEnabled;
browsePayloadTemplateButton.Enabled =
payloadEditingEnabled;
//
// A direct XML-file target does not have a stored-procedure
// parameter and does not need a second archive copy. The generated
// XML file itself is the final storage output.
//
payloadParameterNameLabel.Visible =
!xmlFilePayload;
payloadParameterNameTextBox.Visible =
!xmlFilePayload;
archivePayloadCheckBox.Visible =
!xmlFilePayload;
payloadArchivePathLabel.Visible =
!xmlFilePayload;
payloadArchivePathTextBox.Visible =
!xmlFilePayload;
browsePayloadArchiveButton.Visible =
!xmlFilePayload;
payloadParameterNameTextBox.Enabled =
isUnlocked &&
storedProcedurePayload;
archivePayloadCheckBox.Enabled =
isUnlocked &&
storedProcedurePayload;
bool archivePathEnabled =
isUnlocked &&
storedProcedurePayload &&
archivePayloadCheckBox.Checked;
payloadArchivePathTextBox.Enabled =
archivePathEnabled;
browsePayloadArchiveButton.Enabled =
archivePathEnabled;
}
/// <summary>
/// Opens a file browser used to select an external XML payload template.
/// </summary>
private void browsePayloadTemplateButton_Click(object sender, EventArgs e)
{
using (OpenFileDialog dialog = new OpenFileDialog())
{
dialog.Title = "Select XML payload template";
dialog.Filter =
"XML files (*.xml)|*.xml|All files (*.*)|*.*";
dialog.CheckFileExists = true;
dialog.Multiselect = false;
string currentPath =
(payloadTemplatePathTextBox.Text ?? string.Empty).Trim();
if (!string.IsNullOrWhiteSpace(currentPath))
{
try
{
string directory = Path.GetDirectoryName(currentPath);
if (!string.IsNullOrWhiteSpace(directory) &&
Directory.Exists(directory))
{
dialog.InitialDirectory = directory;
}
string fileName = Path.GetFileName(currentPath);
if (!string.IsNullOrWhiteSpace(fileName))
dialog.FileName = fileName;
}
catch
{
// Ignore malformed current path and open the default folder.
}
}
if (dialog.ShowDialog(this) == DialogResult.OK)
payloadTemplatePathTextBox.Text = dialog.FileName;
}
}
/// <summary>
/// Opens a folder browser used to select the payload archive directory.
/// </summary>
private void browsePayloadArchiveButton_Click(object sender, EventArgs e)
{
using (FolderBrowserDialog dialog = new FolderBrowserDialog())
{
dialog.Description =
"Select directory for generated payload archive files.";
dialog.ShowNewFolderButton = true;
string currentPath =
(payloadArchivePathTextBox.Text ?? string.Empty).Trim();
if (!string.IsNullOrWhiteSpace(currentPath) &&
Directory.Exists(currentPath))
{
dialog.SelectedPath = currentPath;
}
if (dialog.ShowDialog(this) == DialogResult.OK)
payloadArchivePathTextBox.Text = dialog.SelectedPath;
}
}
/// <summary>
/// Updates archive path control availability when payload archiving is
/// enabled or disabled.
/// </summary>
private void archivePayloadCheckBox_CheckedChanged(object sender, EventArgs e)
{
ApplyPayloadConfiguration();
}
/// <summary>
/// Returns the data source hint for the selected technology.
/// </summary>
/// <param name="technologyType">
/// Selected technology type.
/// </param>
/// <returns>
/// Data source input hint.
/// </returns>
private string GetDataSourceHint(string technologyType)
{
switch (technologyType)
{
case TechnologyTypes.Csv:
return "Insert the .csv file path";
case TechnologyTypes.Xlsx:
return "Insert the .xlsx file path";
case TechnologyTypes.Xls:
return "Insert the .xls file path";
case TechnologyTypes.Xml:
return "Insert output directory for generated .xml payload files";
case TechnologyTypes.MicrosoftSql:
case TechnologyTypes.MySqlMariaDb:
case TechnologyTypes.SQLite:
return "Insert database connection string";
default:
return "Insert data storage source";
}
}
/// <summary>
/// Sets the placeholder text displayed by the data source textbox.
/// </summary>
/// <param name="hint">
/// Placeholder text.
/// </param>
private void SetDataSourceHint(string hint)
{
if (!dataSourceTextBox.IsHandleCreated)
return;
SendMessage(
dataSourceTextBox.Handle,
EmSetCueBanner,
new IntPtr(1),
hint ?? string.Empty);
}
}
}