Files
Modules/EDMIAPI/Client.vb
Developer01 de918b1e88 Verbesserung der Stabilität und Fehlerbehandlung
- `MSSQLServer.vb`: Konvertierung von `Nothing`-Werten zu `DBNull.Value` und detaillierte Protokollierung von SQL-Fehlern hinzugefügt.
- `AssemblyInfo.vb`: Aktualisierung der Assembly-Version auf `2.4.0.0`.
- `Client.vb`: Einführung von `_IsReconnecting`, um parallele Reconnects zu verhindern. Verbesserte Fehlerbehandlung, Protokollierung und Ressourcenfreigabe.
- `MailSession.vb`: Konsistentere Protokollierung, Änderung von `Info` zu `Debug` beim Schließen der Verbindung.
- `EDMI.API.vbproj`: Hinzufügen der Datei `ErrorLog.txt`.
- `ErrorLog.txt`: Dokumentation von Timeout- und Verbindungsproblemen mit detaillierten Stack-Traces.

Diese Änderungen verbessern die Stabilität, Fehlertoleranz und Protokollierungsqualität der Anwendung.
2026-09-29 15:49:15 +02:00

1239 lines
47 KiB
VB.net
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
Imports System.IO
Imports System.ServiceModel
Imports System.Threading
Imports DigitalData.Modules.Base
Imports DigitalData.Modules.EDMI.API.Constants
Imports DigitalData.Modules.EDMI.API.EDMIServiceReference
Imports DigitalData.Modules.EDMI.API.Rights
Imports DigitalData.Modules.Logging
Public Class Client
Implements IDisposable
' Constants
Private Const UPDATE_INTERVAL_IN_MINUTES As Integer = 1
Private Const KIND_TYPE_DOC = "DOC"
' Helper Classes
Private ReadOnly LogConfig As LogConfig
Private ReadOnly Logger As Logger
Private ReadOnly ChannelManager As Channel
' Runtime Variables
Private _ServerAddress As ServerAddressStruct
Private _ClientConfig As GlobalStateClientConfiguration
Private _CachedTables As New List(Of String)
Private _IsOnline As Boolean
' Verhindert parallele Reconnect-Versuche (0 = frei, 1 = läuft)
Private _IsReconnecting As Integer = 0
Private _Channel As IEDMIServiceChannel
Private _ClientName As String
' Static Registry
Private Shared ReadOnly _ActiveClients As New Collections.Concurrent.ConcurrentDictionary(Of String, ClientStatus)
Private ReadOnly _ClientId As String ' ← Neu: eindeutiger interner Key
Public Shared ReadOnly Property ActiveClients As IReadOnlyDictionary(Of String, ClientStatus)
Get
Return _ActiveClients
End Get
End Property
''' <summary>
''' Returns a snapshot of all currently registered client instances
''' </summary>
Public Shared Function GetAllClientStatus() As List(Of ClientStatus)
Return _ActiveClients.Values.ToList()
End Function
' Update Timer
Private WithEvents UpdateTimer As New Timers.Timer
Private _Disposed As Boolean = False
''' <summary>
''' Closes the channel and releases all resources
''' </summary>
Public Sub Dispose() Implements IDisposable.Dispose
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
Protected Overridable Sub Dispose(disposing As Boolean)
If _Disposed Then Return
If disposing Then
_IsOnline = False ' ← Reconnect-Guard deaktivieren
UpdateTimer.Stop()
UpdateTimer.Dispose()
' Auf laufenden Reconnect warten
Dim oTimeout = DateTime.Now.AddSeconds(5)
While _IsReconnecting = 1 AndAlso DateTime.Now < oTimeout
Task.Delay(100).Wait()
End While
If _Channel IsNot Nothing Then
Channel.CloseChannel(_Channel)
_Channel = Nothing
End If
Dim oRemoved As ClientStatus = Nothing
_ActiveClients.TryRemove(_ClientId, oRemoved)
Logger.Debug("Client [{0}] unregistered. Total active clients: [{1}]", _ClientName, _ActiveClients.Count)
End If
_Disposed = True
End Sub
' Public Variables
Public ReadOnly Property CachedTables
Get
Return _CachedTables
End Get
End Property
Public ReadOnly Property ClientConfig As GlobalStateClientConfiguration
Get
If _ClientConfig Is Nothing Then
Throw New ApplicationException("ClientConfig is empty! Please connect to the service before accessing this property")
End If
Return _ClientConfig
End Get
End Property
Public ReadOnly Property IsOnline As Boolean
Get
Return _IsOnline
End Get
End Property
Public ReadOnly Property ServerAddress As String
Get
Return $"{_ServerAddress.Host}:{_ServerAddress.Port}"
End Get
End Property
''' <summary>
''' Parse a IPAddress:Port String into its parts
''' </summary>
''' <param name="AddressWithOptionalPort">A Server Address, for example: 192.168.1.50, 192.168.1.50:9000, 192.168.1.50;9000</param>
Public Shared Function ParseServiceAddress(AddressWithOptionalPort As String) As Tuple(Of String, Integer)
Dim oConnection As New Connection()
Dim oAddress As ServerAddressStruct = oConnection.ParseServiceAddress(AddressWithOptionalPort)
Return New Tuple(Of String, Integer)(oAddress.Host, oAddress.Port)
End Function
''' <summary>
''' Creates a new EDMI Client object
''' </summary>
''' <param name="pLogConfig">LogConfig object</param>
''' <param name="pServiceAdress">The IP address/hostname and port, separated by semicolon or colon, ex. 1.2.3.4:9000</param>
''' <param name="pClientName">Optional identifier for this client instance (e.g. application name or hostname)</param>
Public Sub New(pLogConfig As LogConfig, pServiceAdress As String, Optional pClientName As String = "")
LogConfig = pLogConfig
Logger = pLogConfig.GetLogger()
_ClientName = If(String.IsNullOrWhiteSpace(pClientName), Environment.MachineName, pClientName)
_ClientId = $"{_ClientName}_{Guid.NewGuid().ToString("N").Substring(0, 8)}" ' ← eindeutig
UpdateTimer.Interval = 60 * 1000 * UPDATE_INTERVAL_IN_MINUTES
' Start erfolgt in Connect()
Try
Dim oConnection = New Connection()
_ServerAddress = oConnection.ParseServiceAddress(pServiceAdress)
ChannelManager = New Channel(pLogConfig, _ServerAddress)
AddHandler ChannelManager.Reconnect, AddressOf Reconnect
Logger.Debug("Ready for connection to Service at: [{0}:{1}] | ClientName=[{2}] | ClientId=[{3}]",
_ServerAddress.Host, _ServerAddress.Port, _ClientName, _ClientId)
Catch ex As Exception
Logger.Error(ex)
End Try
End Sub
''' <summary>
''' Creates a new EDMI Client object
''' </summary>
''' <param name="pLogConfig">LogConfig object</param>
''' <param name="pIPAddress">The IP address to connect to</param>
''' <param name="pPortNumber">The Port number to use for the connection</param>
''' <param name="pClientName">Optional identifier for this client instance (e.g. application name or hostname)</param>
Public Sub New(pLogConfig As LogConfig, pIPAddress As String, pPortNumber As Integer, Optional pClientName As String = "")
LogConfig = pLogConfig
Logger = pLogConfig.GetLogger()
_ClientName = If(String.IsNullOrWhiteSpace(pClientName), Environment.MachineName, pClientName)
_ClientId = $"{_ClientName}_{Guid.NewGuid().ToString("N").Substring(0, 8)}" ' ← eindeutig
UpdateTimer.Interval = 60 * 1000 * UPDATE_INTERVAL_IN_MINUTES
Try
Dim oConnection = New Connection()
_ServerAddress = oConnection.ParseServiceAddress(pIPAddress, pPortNumber)
ChannelManager = New Channel(pLogConfig, _ServerAddress)
AddHandler ChannelManager.Reconnect, AddressOf Reconnect
Logger.Debug("Ready for connection to Service at: [{0}:{1}] | ClientName=[{2}] | ClientId=[{3}]",
_ServerAddress.Host, _ServerAddress.Port, _ClientName, _ClientId)
Catch ex As Exception
Logger.Error(ex)
End Try
End Sub
''' <summary>
''' Connect to the service
''' </summary>
''' <returns>True if connection was successful, false otherwise</returns>
Public Function Connect() As Boolean
Try
_Channel = ChannelManager.GetChannel()
Logger.Debug("Opening channel..")
_Channel.Open()
Dim oResponse = _Channel.GetClientConfig()
If oResponse.OK Then
_ClientConfig = oResponse.ClientConfig
Else
Logger.Warn("⚠Client Configuration could not be loaded: [{0}]", oResponse.ErrorMessage)
End If
Logger.Info("✔✔Connection to AppService [{0}] successfully established! | Client=[{1}] | Id=[{2}]",
ServerAddress, _ClientName, _ClientId)
' CachedTables sofort befüllen – nicht auf den ersten Timer-Tick warten
Try
Dim oTables As String() = _Channel.GetCachedTables()
_CachedTables = oTables.Select(Function(t) t.ToUpper).ToList()
Logger.Debug("✔ CachedTables loaded on connect: [{0}]", _CachedTables.Count)
Catch ex As Exception
Logger.Warn("⚠ CachedTables could not be loaded on connect: {0}", ex.Message)
_CachedTables = New List(Of String)
End Try
_IsOnline = True ' ← vor GetStatus()
UpdateTimer.Start()
' In Registry eintragen – jetzt mit korrektem IsOnline=True
_ActiveClients(_ClientId) = GetStatus()
Logger.Info("Client [{0}] registered with Id=[{1}]. Total active clients: [{2}]", _ClientName, _ClientId, _ActiveClients.Count)
Return True
Catch ex As Exception
_IsOnline = False
Logger.Error(ex)
Return False
End Try
End Function
''' <summary>
''' Aborts the channel and creates a new connection
''' </summary>
Public Async Sub Reconnect(Optional sender As Object = Nothing, Optional e As EventArgs = Nothing)
' Nur ein Reconnect gleichzeitig erlauben – atomar prüfen und setzen
If Interlocked.CompareExchange(_IsReconnecting, 1, 0) = 1 Then
Logger.Debug("Reconnect already in progress – skipping duplicate call.")
Return
End If
Try
Logger.Warn("⚠ Connection faulted. Trying to reconnect..")
Dim oRetryDelays = {2, 5, 15, 30}
For Each oDelay In oRetryDelays
Try
Await Task.Delay(TimeSpan.FromSeconds(oDelay))
Logger.Debug("Reconnect attempt after [{0}]s delay...", oDelay)
Channel.CloseChannel(_Channel)
_Channel = ChannelManager.GetChannel()
_Channel.Open()
_IsOnline = True
If _ActiveClients.ContainsKey(_ClientId) Then
_ActiveClients(_ClientId) = GetStatus()
End If
Logger.Info("✔ Reconnect successful.")
Return
Catch ex As Exception
Logger.Warn("⚠ Reconnect failed after [{0}]s: {1}", oDelay, ex.Message)
_IsOnline = False
End Try
Next
Logger.Error("All reconnect attempts failed. Service unavailable.")
Finally
' Flag immer zurücksetzen – auch bei unerwarteten Fehlern
Interlocked.Exchange(_IsReconnecting, 0)
End Try
End Sub
Private Async Sub UpdateTimer_Elapsed(sender As Object, e As Timers.ElapsedEventArgs) Handles UpdateTimer.Elapsed
Try
If _Channel Is Nothing OrElse _Channel.State <> CommunicationState.Opened Then
Logger.Warn("⚠ UpdateTimer skipped: Channel not in Opened state [{0}]", If(_Channel IsNot Nothing, _Channel.State.ToString(), "null"))
_IsOnline = False
Reconnect(sender, e)
Return
End If
If Not _IsOnline Then
Logger.Debug("UpdateTimer skipped: Client is offline.")
Return
End If
Dim oTables As String() = Await _Channel.GetCachedTablesAsync()
' ◄ NullReferenceException-Schutz: Service kann Nothing zurückgeben
If oTables Is Nothing Then
Logger.Warn("⚠ UpdateTimer: GetCachedTablesAsync returned Nothing.")
_CachedTables = New List(Of String)
Return
End If
_CachedTables = oTables.
Select(Function(table) table.ToUpper).
ToList()
Logger.Info("✔ Channel state: [{0}] | Active connections: [{1}] | CachedTables: [{2}]",
_Channel.State,
Channel.ActiveConnections,
_CachedTables.Count)
If _ActiveClients.ContainsKey(_ClientId) Then
_ActiveClients(_ClientId) = GetStatus()
End If
Catch ex As TimeoutException
' Stale TCP connection (z.B. Firewall/Citrix Idle-Timeout)
Logger.Warn("⚠ UpdateTimer: TCP connection lost (idle timeout / firewall). Triggering reconnect...")
_IsOnline = False
Reconnect()
Catch ex As CommunicationException
Logger.Warn("⚠ UpdateTimer: Channel faulted. Triggering reconnect...")
_IsOnline = False
Reconnect()
Catch ex As Exception
Logger.Warn("⚠ Update of CachedTable was not successful!")
Logger.Error(ex)
_CachedTables = New List(Of String)
End Try
End Sub
''' <summary>
''' Imports a file from a filepath, creating a IDB ObjectId and Filesystem Object
''' </summary>
''' <param name="pObjectStoreName">Type of ObjectStore. Can be WORK or ARCHIVE.</param>
''' <param name="pIDBDoctypeId">Business entity that the new file object should belong to.</param>
''' <param name="pImportOptions">Other file import options</param>
''' <exception cref="FileNotFoundException">When local filepath was not found</exception>
''' <exception cref="ApplicationException">When there was a error in the Service</exception>
''' <returns>The ObjectId of the newly generated filesystem object</returns>
Public Async Function NewFileAsync(pFilePath As String, pObjectStoreName As String, pObjectKind As String, pIDBDoctypeId As Long, Optional pImportOptions As Options.NewFileOptions = Nothing) As Task(Of Long)
Try
Dim oNewFile As New Modules.IDB.NewFile(LogConfig, _Channel)
Return Await oNewFile.RunAsync(pFilePath, pObjectStoreName, pObjectKind, pIDBDoctypeId, pImportOptions)
Catch ex As Exception
Logger.Error(ex)
Return Nothing
End Try
End Function
Public Async Function UpdateFileAsync(pObjectId As Long, pFilePath As String, Optional pImportOptions As Options.UpdateFileOptions = Nothing) As Task(Of Long)
Try
Dim oUpdateFile As New Modules.IDB.UpdateFile(LogConfig, _Channel)
Return Await oUpdateFile.RunAsync(pFilePath, pObjectId, pImportOptions)
Catch ex As Exception
Logger.Error(ex)
Return Nothing
End Try
End Function
Public Async Function SetObjectStateAsync(pObjectId As Long, pState As String, Optional pOptions As Options.SetObjectStateOptions = Nothing) As Task(Of Boolean)
Try
Dim oSetObjectState As New Modules.IDB.SetObjectState(LogConfig, _Channel)
Return Await oSetObjectState.RunAsync(pObjectId, pState, pOptions)
Catch ex As Exception
Logger.Error(ex)
Return False
End Try
End Function
Public Async Function SetAttributeValueAsync(pObjectId As Long, pName As String, pValue As Object, Optional pOptions As Options.SetAttributeValueOptions = Nothing) As Task(Of Boolean)
Try
Dim oSetAttributeValue As New Modules.IDB.SetAttributeValue(LogConfig, _Channel)
Return Await oSetAttributeValue.RunAsync(pObjectId, pName, pValue, pOptions)
Catch ex As Exception
Logger.Error(ex)
Return False
End Try
End Function
Public Async Function CheckOutFile(pObjectId As Long, pComment As String, Optional pOptions As Options.CheckOutInOptions = Nothing) As Task(Of Long)
Try
Dim oCheckOutFile As New Modules.IDB.CheckOutFile(LogConfig, _Channel)
Return Await oCheckOutFile.RunAsync(pObjectId, pComment, pOptions)
Catch ex As Exception
Logger.Error(ex)
Return Nothing
End Try
End Function
Public Async Function CheckOutFile(pObjectId As Long, Optional pOptions As Options.CheckOutInOptions = Nothing) As Task(Of Long)
Try
Dim oCheckOutFile As New Modules.IDB.CheckOutFile(LogConfig, _Channel)
Return Await oCheckOutFile.RunAsync(pObjectId, String.Empty, pOptions)
Catch ex As Exception
Logger.Error(ex)
Return Nothing
End Try
End Function
Public Async Function CheckInFile(pObjectId As Long, Optional pOptions As Options.CheckOutInOptions = Nothing) As Task(Of Long)
Try
Dim oCheckInFile As New Modules.IDB.CheckInFile(LogConfig, _Channel)
Return Await oCheckInFile.RunAsync(pObjectId, pOptions)
Catch ex As Exception
Logger.Error(ex)
Return Nothing
End Try
End Function
Public Async Function ImportFileAsync(
pFilePath As String,
pAttributeValues As List(Of UserAttributeValue),
pObjectStoreName As String,
pObjectKind As String,
pIDBDoctypeId As String,
Optional pImportOptions As Options.ImportFileOptions = Nothing) As Task(Of ImportFileResponse)
Try
Dim oImportFile As New Modules.IDB.ImportFile(LogConfig, _Channel)
Return Await oImportFile.RunAsync(pFilePath, pAttributeValues, pObjectStoreName, pObjectKind, pIDBDoctypeId, pImportOptions)
Catch ex As Exception
Logger.Error(ex)
Return Nothing
End Try
End Function
Public Async Function Globix_ImportFileAsync(
pFilePath As String,
pProfileId As Integer,
pAttributeValues As List(Of UserAttributeValue),
pObjectStoreName As String,
pObjectKind As String,
pIDBDoctypeId As String,
Optional pImportOptions As Options.ImportFileOptions = Nothing) As Task(Of Globix_ImportFileResponse)
Try
Dim oImportFile As New Modules.Globix.ImportFile(LogConfig, _Channel)
Return Await oImportFile.RunAsync(pFilePath, pProfileId, pAttributeValues, pObjectStoreName, pObjectKind, pIDBDoctypeId, pImportOptions)
Catch ex As Exception
Logger.Error(ex)
Return Nothing
End Try
End Function
Public Function Zooflow_GetFileObject(pObjectId As Long, pLoadFileContents As Boolean) As FileObject
Try
Dim oGetFileObject As New Modules.Zooflow.GetFileObject(LogConfig, _Channel)
Dim oFileObject = oGetFileObject.Run(pObjectId, pLoadFileContents)
Return oFileObject
Catch ex As Exception
Logger.Error(ex)
Return Nothing
End Try
End Function
Public Async Function ZooFlow_GetFileObjectAsync(pObjectId As Long, pLoadFileContents As Boolean) As Task(Of FileObject)
Try
Dim oGetFileObject As New Modules.Zooflow.GetFileObject(LogConfig, Me)
Dim oFileObject = Await oGetFileObject.RunAsync(pObjectId, pLoadFileContents)
Return oFileObject
Catch ex As Exception
Logger.Error(ex)
Return Nothing
End Try
End Function
''' <summary>
'''
''' </summary>
''' <param name="pObjectId"></param>
''' <param name="pAttributeName"></param>
''' <param name="pOptions"></param>
''' <returns></returns>
Public Function GetVariableValue(pObjectId As Long, pAttributeName As String, Optional pOptions As Options.GetVariableValueOptions = Nothing) As VariableValue
If pOptions Is Nothing Then
pOptions = New Options.GetVariableValueOptions()
End If
Try
Dim oArgs = New GetAttributeValueRequest With {
.ObjectId = pObjectId,
.AttributeName = pAttributeName,
.User = New UserState() With {
.UserName = pOptions.Username,
.Language = pOptions.Language
}
}
Dim oResponse = _Channel.GetAttributeValue(oArgs)
If oResponse.OK = False Then
Return New VariableValue()
End If
Return New VariableValue(oResponse.Value)
Catch ex As Exception
Logger.Error(ex)
Return New VariableValue()
End Try
End Function
Private Function GetValueByType(pRow As DataRow, pTypeString As String) As Object
Try
Dim oAttributeValue As Object
Select Case pTypeString
Case Constants.AttributeTypeName.BIT
oAttributeValue = pRow.Item("ValueBigInt")
Case Constants.AttributeTypeName.BIG_INTEGER
oAttributeValue = pRow.Item("ValueBigInt")
Case Constants.AttributeTypeName.DATE
oAttributeValue = pRow.Item("ValueDate")
Case Constants.AttributeTypeName.DATETIME
oAttributeValue = pRow.Item("ValueDate")
Case Constants.AttributeTypeName.DECIMAL
oAttributeValue = pRow.Item("ValueDecimal")
Case Constants.AttributeTypeName.FLOAT
oAttributeValue = pRow.Item("ValueDecimal")
Case Constants.AttributeTypeName.VARCHAR
oAttributeValue = pRow.Item("ValueText")
Case Constants.AttributeTypeName.VECTOR_INTEGER
oAttributeValue = pRow.Item("ValueBigInt")
Case Constants.AttributeTypeName.VECTOR_STRING
oAttributeValue = pRow.Item("ValueText")
Case Else
oAttributeValue = Nothing
End Select
Return oAttributeValue
Catch ex As Exception
Logger.Error(ex)
Return Nothing
End Try
End Function
Private Function GetAttributesForObject(pObjectId As Long, pLanguage As String) As List(Of ObjectAttribute)
Dim oAttributes As New List(Of ObjectAttribute)
Try
Dim oResult As TableResult = _Channel.ReturnDatatable_MSSQL_IDB($"EXEC [PRIDB_GET_VALUE_DT] {pObjectId}, '{pLanguage}'")
If oResult.OK = False Then
Throw New ApplicationException(oResult.ErrorMessage)
End If
If oResult.Table Is Nothing OrElse oResult.Table.Rows.Count = 0 Then
Return Nothing
End If
For Each oRow As DataRow In oResult.Table.Rows
Dim oAttribute As New ObjectAttribute With {
.Id = oRow.Item("AttributeId"),
.Title = oRow.Item("AttributeTitle"),
.Type = oRow.Item("AttributeType"),
.ValueBigInt = oRow.ItemEx(Of Object)("ValueBigInt", Nothing),
.ValueDate = oRow.ItemEx(Of Object)("ValueDate", Nothing),
.ValueDecimal = oRow.ItemEx(Of Object)("ValueDecimal", Nothing),
.ValueText = oRow.ItemEx(Of Object)("ValueText", Nothing)
}
oAttributes.Add(oAttribute)
Next
Return oAttributes
Catch ex As Exception
Logger.Error(ex)
Return Nothing
End Try
End Function
Private Function NewObjectData(pObjectId As Long, pAttributeName As String, pValue As Object, pOptions As Options.NewObjectOptions)
If pOptions Is Nothing Then
pOptions = New Options.NewObjectOptions()
End If
Dim oLanguage = GetUserLanguage(pOptions.Language)
Dim oUsername = GetUserName(pOptions.Username)
Dim oSql = $"DECLARE @NEW_OBJ_MD_ID BIGINT
EXEC PRIDB_NEW_OBJ_DATA({pObjectId}, '{pAttributeName}', '{oUsername}', '{pValue}', '{oLanguage}', 0, @OMD_ID = @NEW_OBJ_MD_ID OUTPUT)"
Dim oResult = _Channel.ExecuteNonQuery_MSSQL_IDB(oSql)
If oResult.OK = False Then
Logger.Warn("⚠ Error while IDBNewObjectData")
Logger.Error(oResult.ErrorMessage)
End If
Return oResult.OK
End Function
Private Function DeleteTermObjectFromMetadata(pObjectId As Long, pAttributeName As String, pTerm2Delete As String, Optional pUsername As String = "", Optional pLanguage As String = "") As Boolean
Try
Dim oLanguage = GetUserLanguage(pLanguage)
Dim oUsername = GetUserName(pUsername)
Dim oIdIsForeign As Integer = 1
Dim oDELSQL = $"EXEC PRIDB_DELETE_TERM_OBJECT_METADATA {pObjectId},'{pAttributeName}','{pTerm2Delete}','{oUsername}','{oLanguage}',{oIdIsForeign}"
Dim oResult = _Channel.ExecuteNonQuery_MSSQL_IDB(oDELSQL)
If oResult.OK = False Then
Logger.Warn("⚠ Error while deleting Term object")
Logger.Error(oResult.ErrorMessage)
End If
Return oResult.OK
Catch ex As Exception
Logger.Error(ex)
Return False
End Try
End Function
#Region "GetDatatable"
Public Function GetDatatableFromIDB(pSQL As String) As GetDatatableResponse
EnsureAvailable()
Try
Return _Channel.ReturnDatatable(New GetDatatableRequest() With {
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.IDB
})
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetDatatableFromIDB))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(GetDatatableFromIDB))
Catch ex As Exception
Logger.Error(ex)
Throw
End Try
End Function
Public Function GetDatatableFromECM(pSQL As String) As GetDatatableResponse
EnsureAvailable()
Try
Return _Channel.ReturnDatatable(New GetDatatableRequest() With {
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.ECM
})
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetDatatableFromECM))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(GetDatatableFromECM))
Catch ex As Exception
Logger.Error(ex)
Throw
End Try
End Function
Public Function GetDatatableFromConnection(pSQL As String, pConnectionId As Integer) As GetDatatableResponse
EnsureAvailable()
Try
Return _Channel.ReturnDatatable(New GetDatatableRequest() With {
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.None,
.ConnectionId = pConnectionId
})
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetDatatableFromConnection))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(GetDatatableFromConnection))
Catch ex As Exception
Logger.Error(ex)
Throw
End Try
End Function
Public Async Function GetDatatableFromIDBAsync(pSQL As String) As Task(Of GetDatatableResponse)
EnsureAvailable()
Try
Return Await _Channel.ReturnDatatableAsync(New GetDatatableRequest() With {
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.IDB
})
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetDatatableFromIDBAsync))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(GetDatatableFromIDBAsync))
Catch ex As Exception
Logger.Error(ex)
Throw
End Try
End Function
Public Async Function GetDatatableFromECMAsync(pSQL As String) As Task(Of GetDatatableResponse)
EnsureAvailable()
Try
Logger.Debug("GetDatatableFromECMAsync will be called with SQL [{0}]", pSQL)
Return Await _Channel.ReturnDatatableAsync(New GetDatatableRequest() With {
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.ECM
})
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetDatatableFromECMAsync))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(GetDatatableFromECMAsync))
Catch ex As Exception
Logger.Error(ex)
Throw
End Try
End Function
Public Async Function GetDatatableFromConnectionAsync(pSQL As String, Optional pConnectionId As Integer = 0) As Task(Of GetDatatableResponse)
EnsureAvailable()
Try
Return Await _Channel.ReturnDatatableAsync(New GetDatatableRequest() With {
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.None,
.ConnectionId = pConnectionId
})
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetDatatableFromConnectionAsync))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(GetDatatableFromConnectionAsync))
Catch ex As Exception
Logger.Error(ex)
Throw
End Try
End Function
#End Region
#Region "GetScalarValue"
Public Function GetScalarValueFromIDB(pSQL As String) As GetScalarValueResponse
EnsureAvailable()
Try
Return _Channel.ReturnScalarValue(New GetScalarValueRequest() With {
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.IDB
})
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetScalarValueFromIDB))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(GetScalarValueFromIDB))
Catch ex As Exception
Logger.Error(ex)
Throw
End Try
End Function
Public Function GetScalarValueFromECM(pSQL As String) As GetScalarValueResponse
EnsureAvailable()
Try
Return _Channel.ReturnScalarValue(New GetScalarValueRequest() With {
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.ECM
})
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetScalarValueFromECM))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(GetScalarValueFromECM))
Catch ex As Exception
Logger.Error(ex)
Throw
End Try
End Function
Public Function GetScalarValueFromConnection(pSQL As String, pConnectionId As Integer) As GetScalarValueResponse
EnsureAvailable()
Try
Return _Channel.ReturnScalarValue(New GetScalarValueRequest() With {
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.None,
.ConnectionId = pConnectionId
})
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetScalarValueFromConnection))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(GetScalarValueFromConnection))
Catch ex As Exception
Logger.Error(ex)
Throw
End Try
End Function
Public Async Function GetScalarValueFromIDBAsync(pSQL As String) As Task(Of GetScalarValueResponse)
EnsureAvailable()
Try
Return Await _Channel.ReturnScalarValueAsync(New GetScalarValueRequest() With {
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.IDB
})
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetScalarValueFromIDBAsync))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(GetScalarValueFromIDBAsync))
Catch ex As Exception
Logger.Error(ex)
Throw
End Try
End Function
Public Async Function GetScalarValueFromECMAsync(pSQL As String) As Task(Of GetScalarValueResponse)
EnsureAvailable()
Try
Return Await _Channel.ReturnScalarValueAsync(New GetScalarValueRequest() With {
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.ECM
})
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetScalarValueFromECMAsync))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(GetScalarValueFromECMAsync))
Catch ex As Exception
Logger.Error(ex)
Throw
End Try
End Function
Public Async Function GetScalarValueFromConnectionAsync(pSQL As String, pConnectionId As Integer) As Task(Of GetScalarValueResponse)
EnsureAvailable()
Try
Return Await _Channel.ReturnScalarValueAsync(New GetScalarValueRequest() With {
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.None,
.ConnectionId = pConnectionId
})
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetScalarValueFromConnectionAsync))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(GetScalarValueFromConnectionAsync))
Catch ex As Exception
Logger.Error(ex)
Throw
End Try
End Function
#End Region
#Region "ExecuteNonQuery"
Public Function ExecuteNonQueryFromIDB(pSQL As String) As ExecuteNonQueryResponse
EnsureAvailable()
Try
Return _Channel.ExecuteNonQuery(New ExecuteNonQueryRequest() With {
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.IDB
})
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromIDB))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromIDB))
Catch ex As Exception
Logger.Error(ex)
Throw
End Try
End Function
Public Function ExecuteNonQueryFromECM(pSQL As String) As ExecuteNonQueryResponse
EnsureAvailable()
Try
Return _Channel.ExecuteNonQuery(New ExecuteNonQueryRequest() With {
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.ECM
})
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromECM))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromECM))
Catch ex As Exception
Logger.Error(ex)
Throw
End Try
End Function
Public Function ExecuteNonQueryFromConnection(pSQL As String, pConnectionId As Integer) As ExecuteNonQueryResponse
EnsureAvailable()
Try
Return _Channel.ExecuteNonQuery(New ExecuteNonQueryRequest() With {
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.None,
.ConnectionId = pConnectionId
})
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromConnection))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromConnection))
Catch ex As Exception
Logger.Error(ex)
Throw
End Try
End Function
Public Async Function ExecuteNonQueryFromIDBAsync(pSQL As String) As Task(Of ExecuteNonQueryResponse)
EnsureAvailable()
Try
Return Await _Channel.ExecuteNonQueryAsync(New ExecuteNonQueryRequest() With {
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.IDB
})
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromIDBAsync))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromIDBAsync))
Catch ex As Exception
Logger.Error(ex)
Throw
End Try
End Function
Public Async Function ExecuteNonQueryFromECMAsync(pSQL As String) As Task(Of ExecuteNonQueryResponse)
EnsureAvailable()
Try
Return Await _Channel.ExecuteNonQueryAsync(New ExecuteNonQueryRequest() With {
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.ECM
})
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromECMAsync))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromECMAsync))
Catch ex As Exception
Logger.Error(ex)
Throw
End Try
End Function
Public Async Function ExecuteNonQueryFromConnectionAsync(pSQL As String, pConnectionId As Integer) As Task(Of ExecuteNonQueryResponse)
EnsureAvailable()
Try
Return Await _Channel.ExecuteNonQueryAsync(New ExecuteNonQueryRequest() With {
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.None,
.ConnectionId = pConnectionId
})
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromConnectionAsync))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromConnectionAsync))
Catch ex As Exception
Logger.Error(ex)
Throw
End Try
End Function
#End Region
Public Function GetDatatableByName(DatatableName As String, Optional FilterExpression As String = "", Optional SortByColumn As String = "") As TableResult
EnsureAvailable()
Try
Return _Channel.ReturnDatatableFromCache(DatatableName, FilterExpression, SortByColumn)
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetDatatableByName))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(GetDatatableByName))
Catch ex As Exception
Logger.Error(ex)
Throw
End Try
End Function
Public Async Function GetDatatableByNameAsync(DatatableName As String, Optional FilterExpression As String = "", Optional SortByColumn As String = "") As Task(Of TableResult)
EnsureAvailable()
Try
Return Await _Channel.ReturnDatatableFromCacheAsync(DatatableName, FilterExpression, SortByColumn)
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetDatatableByNameAsync))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(GetDatatableByNameAsync))
Catch ex As Exception
Logger.Error(ex)
Throw
End Try
End Function
''' <summary>
''' Return infos about a file object
''' </summary>
''' <param name="UserId"></param>
''' <param name="ObjectId"></param>
''' <returns></returns>
Public Function GetDocumentInfo(UserId As Long, ObjectId As Long) As DocumentInfo
Try
Dim oResponse As DocumentInfoResponse = _Channel.GetFileInfoByObjectId(New DocumentInfoRequest With {
.ObjectId = ObjectId,
.UserId = UserId
})
Return New DocumentInfo With {
.AccessRight = oResponse.FileRight,
.FullPath = oResponse.FullPath
}
Catch ex As FaultException(Of ObjectDoesNotExistFault)
Logger.Error(ex)
Throw
Catch ex As FaultException
Logger.Error(ex)
Throw
Catch ex As Exception
Logger.Error(ex)
Throw
End Try
End Function
Public Async Function GetDocumentInfoAsync(UserId As Long, ObjectId As Long) As Task(Of DocumentInfo)
Try
Dim oParams = New DocumentInfoRequest With {
.ObjectId = ObjectId,
.UserId = UserId
}
Dim oResponse As DocumentInfoResponse = Await _Channel.GetFileInfoByObjectIdAsync(oParams)
Return New DocumentInfo With {
.AccessRight = oResponse.FileRight,
.FullPath = oResponse.FullPath
}
Catch ex As Exception
Logger.Error(ex)
Throw
End Try
End Function
''' <summary>
''' Returns a live monitoring snapshot from the service (active sessions, users)
''' </summary>
Public Function GetServiceStatus() As ServiceStatusResponse
EnsureAvailable()
Try
Return _Channel.GetServiceStatus()
Catch ex As Exception
Logger.Error(ex)
Return Nothing
End Try
End Function
#Region "Private Functions"
Private Function GetUserLanguage(pOverrideLanguage As String) As String
Return ObjectEx.NotNull(pOverrideLanguage, Threading.Thread.CurrentThread.CurrentUICulture.Name)
End Function
Private Function GetUserName(pOverrideName) As String
Return ObjectEx.NotNull(pOverrideName, Environment.UserName)
End Function
''' <summary>
''' Throws InvalidOperationException if the channel is not ready for calls
''' </summary>
Private Sub EnsureAvailable()
If _Channel Is Nothing Then
Throw New InvalidOperationException("Channel is not initialized. Call Connect() first.")
End If
If Not _IsOnline Then
' Kurze Grace-Period abwarten, damit Reconnect() seinen _IsReconnecting-Flag setzen kann
Dim oGraceLimit = DateTime.Now.AddMilliseconds(600)
While DateTime.Now < oGraceLimit AndAlso _IsReconnecting = 0 AndAlso Not _IsOnline
Task.Delay(100).Wait()
End While
' Jetzt auf aktiven Reconnect warten
Dim oWaitLimit = DateTime.Now.AddSeconds(10)
While Not _IsOnline AndAlso DateTime.Now < oWaitLimit AndAlso _IsReconnecting = 1
Task.Delay(500).Wait()
Logger.Debug("EnsureAvailable: Waiting for reconnect... Channel=[{0}]", _Channel.State.ToString())
End While
If Not _IsOnline Then
Throw New InvalidOperationException("Client is offline.")
End If
End If
If _Channel.State <> CommunicationState.Opened Then
Throw New InvalidOperationException($"Channel is not available. State=[{_Channel.State}]")
End If
End Sub
''' <summary>
''' Logs the communication error, sets offline state, triggers reconnect and rethrows
''' </summary>
Private Sub HandleCommunicationError(ex As Exception, callerName As String)
Select Case True
Case TypeOf ex Is EndpointNotFoundException
Logger.Warn("⚠ Service not reachable (EndpointNotFoundException in [{0}]). Triggering reconnect...", callerName)
Case TypeOf ex Is CommunicationException
Logger.Warn("⚠ CommunicationException in [{0}] during call. Triggering reconnect...", callerName)
Case Else
Logger.Warn("⚠ Unexpected exception in [{0}] during call. Triggering reconnect...", callerName)
Logger.Error(ex)
End Select
_IsOnline = False
Reconnect()
Throw ex
End Sub
#End Region
#Region "Response Classes"
Public Class StreamedFile
Public Stream As MemoryStream
Public FileName As String
End Class
Public Class FileList
Public Datatable As DataTable
End Class
Public Class DocumentInfo
Public Id As Long
Public FullPath As String
Public AccessRight As AccessRight
End Class
Public Class VariableValue
Public ReadOnly Property IsVector As Boolean = False
Public Property Value As Object
Public Property Type As Type
Public Sub New()
MyClass.New(Nothing)
End Sub
Public Sub New(pValue As Object)
' Check if value is a collection
If TypeOf pValue Is IEnumerable Then
IsVector = True
End If
' Try to determine the type
If IsNothing(pValue) Then
Type = Nothing
Else
Type = pValue.GetType
End If
Value = pValue
End Sub
End Class
#End Region
Public Class ObjectAttribute
Public Property Id As Long
Public Property Title As String
Public Property Type As String
Public Property ValueBigInt As Long
Public Property ValueText As String
Public Property ValueDecimal As Decimal
Public Property ValueDate As DateTime
Public ReadOnly Property Value As Object
Get
Return GetValue()
End Get
End Property
Private Function GetValue() As Object
Select Case Type
Case AttributeTypeName.VECTOR_INTEGER
Return ValueBigInt
Case AttributeTypeName.BIG_INTEGER
Return ValueBigInt
Case AttributeTypeName.VECTOR_STRING
Return ValueText
Case AttributeTypeName.VARCHAR
Return ValueText
Case AttributeTypeName.BIT
Return IIf(ValueBigInt = 1, True, False)
Case AttributeTypeName.DATE
Return ValueDate
Case AttributeTypeName.DATETIME
Return ValueDate
Case AttributeTypeName.DECIMAL
Return ValueDecimal
Case AttributeTypeName.FLOAT
Return ValueDecimal
Case Else
Return Nothing
End Select
End Function
End Class ' ← ObjectAttribute endet hier
''' <summary>
''' Returns a detailed status snapshot of the current client state
''' </summary>
Public Function GetStatus() As ClientStatus
Return New ClientStatus() With {
.IsOnline = _IsOnline,
.ClientId = _ClientId, ' ← Neu
.ClientName = _ClientName,
.ServerAddress = Me.ServerAddress,
.ChannelState = If(_Channel IsNot Nothing, _Channel.State.ToString(), "null"),
.ActiveConnections = Channel.ActiveConnections,
.CachedTableCount = _CachedTables.Count,
.CachedTables = _CachedTables.ToList(),
.HasClientConfig = _ClientConfig IsNot Nothing,
.IsDisposed = _Disposed,
.TimerEnabled = UpdateTimer.Enabled,
.TimerIntervalSeconds = CInt(UpdateTimer.Interval / 1000),
.Timestamp = DateTime.Now
}
End Function
Public Class ClientStatus
Public Property IsOnline As Boolean
Public Property ClientId As String
Public Property ClientName As String
Public Property ServerAddress As String
Public Property ChannelState As String
Public Property ActiveConnections As Integer
Public Property CachedTableCount As Integer
Public Property CachedTables As List(Of String)
Public Property HasClientConfig As Boolean
Public Property IsDisposed As Boolean
Public Property TimerEnabled As Boolean
Public Property TimerIntervalSeconds As Integer
Public Property Timestamp As DateTime
Public Overrides Function ToString() As String
Return String.Format(
"[ClientStatus] ClientName=[{0}] Id=[{1}] Online=[{2}] Channel=[{3}]",
ClientName, ClientId, IsOnline, ChannelState)
End Function
Public Function Client_Who_ToString() As String
Return String.Format(
"[ClientStatus] Client=[{0}] Id=[{1}] Online=[{2}] Server=[{3}] Channel=[{4}]",
ClientName, ClientId, IsOnline, ServerAddress, ChannelState)
End Function
End Class ' ← ClientStatus endet hier – kein GetServiceStatus mehr drin
End Class