Just because of the missing debugging feature something I implemented on our side.
First a logging function which is working in all client, service and web application (even in log views):
Public Shared Function CCC_CreateLogEntry(logType As MsgSeverity, logMessage As String) As String
NLog.LogManager.GetCurrentClassLogger()
Dim stackpos As Integer = 1
Dim stackframe As New Diagnostics.StackFrame(1)
' Dim stackLine As Integer ' Zeile unbekannt da keine Debugging Symbol Datei vorhanden (.pdb)
Dim methName As String = stackframe.GetMethod().Name
Dim stackPosition As String
If methName.StartsWith("CCC_Create") And methName.EndsWith("Log") Then
stackframe = New Diagnostics.StackFrame(2)
stackpos = 2
End If
stackposition = stackframe.GetMethod().Name
'stackLine = stackFrame.GetFileLineNumber() ' Zeile unbekannt da keine Debugging Symbol Datei vorhanden (.pdb)
stackpos = stackpos + 1
stackframe = New Diagnostics.StackFrame(stackpos)
methName = stackframe.GetMethod().Name
While methname.StartsWith("CCC_")
stackposition = stackframe.GetMethod().Name + "." + stackposition
stackpos = stackpos + 1
stackframe = New Diagnostics.StackFrame(stackpos)
methName = stackframe.GetMethod().Name
'stackLine = stackFrame.GetFileLineNumber() ' Zeile unbekannt da keine Debugging Symbol Datei vorhanden (.pdb)
End While
' evt. noch Stacktrace aller CCC_Funktionen nach oben aufbauen...
Dim severity As String = System.Enum.GetName(GetType(MsgSeverity), CObj(logType))
Dim appTypeVal As String = System.Enum.GetName(GetType(AppType), CObj(AppData.Instance.AppType))
CCC_CreateLogEntry = ";""" + Date.Now.ToString("dd.MM.yyyy HH:mm:ss") + """;""" + severity + """;""" + appTypeVal + """;""" + AppData.Instance.AppName + """;""" + stackposition + """;""" + logMessage + """"
AppData.Instance.RaiseMessage(logType, CCC_CreateLogEntry)
' In der Gui landen die Meldungen nur in der Statusleiste bzw. im error Log-Fenster - durch NLog landen sie auch in einer entsprechenden Datei
If AppData.Instance.AppType = AppType.Gui OrElse AppData.Instance.AppType = AppType.Web Then
Dim nlogSeverity As NLog.LogLevel = NLog.LogLevel.Info
Select Case logType
Case MsgSeverity.Info: nlogSeverity = NLog.LogLevel.Info
Case MsgSeverity.Warning: nlogSeverity = NLog.LogLevel.Warn
Case MsgSeverity.Serious: nlogSeverity = NLog.LogLevel.Error
End Select
NLog.LogManager.GetCurrentClassLogger().Log(nlogSeverity, CCC_CreateLogEntry)
End If
If AppData.Instance.AppName.ToLower() = "systemdebugger" Then
System.Diagnostics.Debug.WriteLine(CCC_CreateLogEntry)
End If
End Function
Public Shared Function CCC_CreateInfoLog(logMessage As String) As String
CCC_CreateInfoLog = CCC_CreateLogEntry(MsgSeverity.Info, logMessage)
End Function
And now you can use CCC_CreateInfoLog at all places in any script, even in valuation, validation and data dependency scripts.
The log will be in the imxclient.log file or the process window in vsCode or similar.
Its really scary how often valuation scripts are called.
And to get rid of all the boiler plate code in the request parameter scripts I made these wrapper function
Delegate Sub CCC_ReqPropExecParams( ps As ParameterSet, lc As String, init As Boolean)
Enum CCC_ReqPropType
Valuation
Validation
DataDependency
End Enum
Public Function CCC_RPVal(ps As ParameterSet, propName As String) As Object
Dim x As Parameter = ps(propName)
If x Is Nothing Then Throw New ViException($"{propName} not known on parameterset", ExceptionRelevance.EndUser)
If Not x.HasValue Then
Select Case x.Type
Case ValType.String: Return ""
Case ValType.Bool: Return False
Case ValType.Short, ValType.Int, ValType.Long, ValType.Decimal, ValType.Double, ValType.Byte: Return 0
Case ValType.Date: Return DBVal.MinDate
Case Else: Return ""
End Select
End If
Return x.Value
End Function
Public Sub CCC_RPExec(ps As ParameterSet, context As CCC_ReqPropType, myPropName As String, handler As CCC_ReqPropExecParams)
Try
Dim isInit As Boolean = DbVal.ConvertTo(Of Boolean)(Variables("IsInit"))
Dim logContext As String = context.ToString() + " " + myPropName + ": "
CCC_CreateInfoLog($"Start {logContext} isInit = {isInit}")
handler(ps, logContext, isInit)
'CCC_CreateInfoLog($"Finished {context} {myPropName}")
Catch e As ViException
If e.Relevance = ExceptionRelevance.EndUser
CCC_CreateWarnLog("Error {logContext} " + e.ToString())
Else
CCC_CreateErrorLog("Error {logContext} " + e.ToString())
End If
If context = CCC_ReqPropType.Valuation Then ' Valuation-Skripte können nicht mit Exceptions umgehen. Gib den Fehler als Wert zurück
Value = "Error {logContext} " + e.ToString()
Exit Sub
End If
If e.Relevance = ExceptionRelevance.EndUser Then Throw
'Throw New ViException("Error {logContext} " + e.ToString(), ExceptionRelevance.Technical, e)
Throw New ViException("Error {logContext} " + e.ToString(), ExceptionRelevance.EndUser, e)
Catch e As Exception
Throw New ViException("Error {logContext} " + e.ToString(), ExceptionRelevance.EndUser, e)
End Try
End Sub
This can now be called in valuation, validation, dependency-scripts like
CCC_RPExec(ParameterSet, CCC_ReqPropType.Valuation, "MyPropName", Sub (ps As ParameterSet, lc As String, isInit As Boolean)
CCC_CreateInfoLog("Test1 = " + CCC_RPVal(ps, "Test1").ToString())
Value = "xy"
additional code
End Sub
)
Value is still working like using it in normal templates.
Beware of using CCC_RPVal in Valuation-scripts for your own value. This will lead to recursion and stack overflow and finally to a crash of the application. This behavior is not new to this implementation and occurs too, if you are using default for field XY and type
dim x = ParameterSet("XY").Value
Value = x