Imports System.IO Imports System.Text.RegularExpressions Imports DigitalData.Modules.Logging Imports DigitalData.Modules.Language Imports MultiTool.Common.Exceptions Imports MultiTool.Common.Templates Imports MultiTool.Common.Winline Imports MultiTool.Common.Winline.Entities Imports MultiTool.Common.Constants Imports DigitalData.Modules.Database Namespace Documents Public Class DocumentLoader Inherits BaseClass Private ReadOnly Winline As WinlineData Private ReadOnly MappingConfig As MappingConfig Private ReadOnly TemplateConfig As TemplateConfig Private ReadOnly ApplicationConfig As Config Private ReadOnly Patterns As Patterns Public Property Files As New List(Of Document) Public Property FilesTotal As Integer = 0 Public Property FilesLoaded As Integer = 0 Public Event FileLoadComplete As EventHandler(Of FileLoadInfo) Public Event FileLoadProgress As EventHandler(Of FileLoadProgressInfo) Public Class FileLoadInfo Public FilesLoaded As Integer Public FilesTotal As Integer Public Sub New(pTotal As Integer, pLoaded As Integer) FilesTotal = pTotal FilesLoaded = pLoaded End Sub End Class Public Class FileLoadProgressInfo Inherits FileLoadInfo Public RunningFunction As String Public Sub New(pTotal As Integer, pLoaded As Integer) MyBase.New(pTotal, pLoaded) End Sub End Class Public Sub New(pLogConfig As LogConfig, pWinline As WinlineData, pDatabase As MSSQLServer, pMappingConfig As MappingConfig, pTemplateConfig As TemplateConfig, pGeneralConfig As GeneralConfig, pApplicationConfig As Config) MyBase.New(pLogConfig, pDatabase) Winline = pWinline MappingConfig = pMappingConfig TemplateConfig = pTemplateConfig ApplicationConfig = pApplicationConfig Patterns = New Patterns(pLogConfig, pGeneralConfig) End Sub Public Async Function LoadFiles(pTemplate As Template, pMandator As Mandator) As Task(Of Boolean) Logger.Info("Loading files from directory [{0}]", pTemplate.InputDirectory) Files.Clear() Try Dim oDirectory As New DirectoryInfo(pTemplate.InputDirectory) Dim oFiles = oDirectory.GetFiles() FilesTotal = oFiles.Count Logger.Debug("Found [{0}] files in directory [{1}]", oFiles.Count, oDirectory) For Each oFile In oFiles Logger.Info("Loading file [{0}]", oFile.Name) Dim oDocument = Await LoadFile(oFile, pTemplate, pMandator) Files.Add(oDocument) FilesLoaded = Files.Count Dim oInfo As New FileLoadInfo(FilesTotal, FilesLoaded) RaiseEvent FileLoadComplete(Me, oInfo) Next Return True Catch ex As Exception Logger.Error(ex) Throw ex End Try End Function Public Async Function LoadFile(pFileInfo As FileInfo, pTemplate As Template, pMandator As Mandator) As Task(Of Document) Logger.Debug("Creating new Document object for file [{0}]", pFileInfo.Name) Dim oDocument As Document = New Document With { .File = pFileInfo, .Schema = pTemplate } Try oDocument = LoadDocumentData(oDocument, pTemplate, TemplateConfig) Catch ex As Exception Throw ex Logger.Error(ex) End Try Try oDocument = Await MatchDataFromWinLine(oDocument, Winline.Mandators, pMandator, pTemplate) Catch ex As Exception Throw ex Logger.Error(ex) End Try Try oDocument = MarkRequiredFields(oDocument) Catch ex As Exception Throw ex Logger.Error(ex) End Try Return oDocument End Function Public Function MarkRequiredFields(pDocument As Document) As Document For Each oRow In pDocument.Rows For Each oField In oRow.Fields If oField.Value.Final = String.Empty And oField.Value.IsRequired Then oField.Value.AddFieldError(FieldErrorType.MissingValue, $"Attribut '{oField.Key}' ist ein Pflichtfeld, wurde aber nicht gefüllt.") End If Next Next Return pDocument End Function Public Sub ReplaceDocument(pDocument As Document) Dim oIndex = Files.IndexOf(pDocument) Files.Item(oIndex) = pDocument End Sub Private Function IncludeSchema(pDocument As Document, pTemplate As Template) As Document Logger.Debug("Adding schema to Document object.") pDocument.Schema = pTemplate Return pDocument End Function ''' ''' Loads a single document from the FullName Property in the Document Object ''' ''' ''' A document might look like this: ''' ''' ''' ''' ''' ''' Private Function LoadDocumentData(pDocument As Document, pTemplate As Template, pTemplateConfig As TemplateConfig) As Document Logger.Debug("Loading file contents") Dim oText As String Dim oDoc As XDocument Try Logger.Debug("Loading file from fs..") oText = IO.File.ReadAllText(pDocument.FullName) Logger.Debug("Parsing file..") oDoc = XDocument.Parse(oText) Catch ex As Exception Logger.Error(ex) Throw ex End Try Logger.Debug("File Loaded.") Dim oRootElement As XElement = XmlData.GetElement(oDoc, "MESOWebService") If oRootElement Is Nothing Then pDocument.AddDocumentError(DocumentErrorType.MissingXmlAttribute, "Datei enthält kein MESOWebService-Attribut") End If Dim oTemplateName = XmlData.GetElementAttribute(oRootElement, "Template") If oTemplateName Is Nothing Then pDocument.AddDocumentError(DocumentErrorType.MissingXmlAttribute, "Datei enthält kein Template-Attribut") End If Dim oTemplateType = XmlData.GetElementAttribute(oRootElement, "TemplateType") If oTemplateType Is Nothing Then pDocument.AddDocumentError(DocumentErrorType.MissingXmlAttribute, "Datei enthält kein TemplateType-Attribut") End If Dim oOption = XmlData.GetElementAttribute(oRootElement, "option") If oOption Is Nothing Then pDocument.AddDocumentError(DocumentErrorType.MissingXmlAttribute, "Datei enthält kein option-Attribut") End If Dim oPrintVoucher = XmlData.GetElementAttribute(oRootElement, "printVoucher") If oPrintVoucher Is Nothing Then pDocument.AddDocumentError(DocumentErrorType.MissingXmlAttribute, "Datei enthält kein printVoucher-Attribut") End If ' The first level of Elements are the document Rows Dim oTopLevelElements As List(Of XElement) = oRootElement.Elements.ToList Dim oDocumentRows As New List(Of DocumentRow) Dim oRowSortKey As Integer = 0 ' TODO: Somehow add all fields in the correct order ' ' Right now, the method of ' - first the filled field from xml ' - then the rest from schema ' ' leads to unordered fields. For Each oTopLevelElement As XElement In oTopLevelElements Dim oColumnSortKey = 0 Dim oFields As New Dictionary(Of String, DocumentRow.FieldValue) Dim oSubElements = oTopLevelElement.Descendants().ToList() Dim oTable = pTemplate.Tables. Where(Function(t) t.Name = oTopLevelElement.Name). FirstOrDefault() Logger.Debug("Creating fields from [{0}] columns for Table [{1}]", oTable.Columns.Count, oTable.Name) For Each oColumn In oTable.Columns Dim oSubElement = oSubElements. Where(Function(e) e.Name = oColumn.Name). SingleOrDefault() If oSubElement IsNot Nothing Then Dim oRequired = oColumn.IsRequired Dim oValue = oSubElement.Value.Trim() Logger.Debug("Creating existing field from Element: [{0}]", oSubElement.Name.ToString) ' TODO: Needed when we have time for date times 'If oTemplateField.DataType = Constants.ColumnType.Date Then ' Dim oDate = Date.ParseExact(oValue, "yyyy-MM-dd", CultureInfo.InvariantCulture) ' oValue = oDate.ToString("d") 'End If Dim oFieldValue = GetFieldValueFromColumn(oColumn, oColumnSortKey) oFieldValue.SetOriginalValue(oValue) oFields.Add(oSubElement.Name.ToString, oFieldValue) Else Logger.Debug("Creating new field from Configuration: [{0}]", oColumn.Name) Dim oFieldValue = GetFieldValueFromColumn(oColumn, oColumnSortKey) If oColumn.Config?.IsRequired Then oFieldValue.AddFieldError(FieldErrorType.MissingValue, $"Attribut {oSubElement.Name} wird benötigt, ist aber nicht gefüllt.") End If oFields.Add(oColumn.Name, oFieldValue) End If oColumnSortKey += 1 Next ' Create a DocumentRow object for each Top Level Element Dim oRow = New DocumentRow With { .SortKey = oRowSortKey, .TableName = oTopLevelElement.Name.ToString, .Fields = oFields } oRowSortKey += 1 oDocumentRows.Add(oRow) Next ' Update the document pDocument.TemplateName = oTemplateName pDocument.TemplateType = oTemplateType pDocument.Option = oOption pDocument.PrintVoucher = oPrintVoucher pDocument.Rows = oDocumentRows Return pDocument End Function Public Function GetFieldValueFromColumn(pColumn As Template.Column, pSortKey As Integer) As DocumentRow.FieldValue Return New DocumentRow.FieldValue(LogConfig) With { .DataType = pColumn.DataType, .IsRequired = pColumn.IsRequired, .IsVirtual = pColumn.Config.IsVirtual, .PreferExternalValue = pColumn.Config.PreferExternalValue, .SortKey = pSortKey } End Function Private Async Function MatchDataFromWinLine(pDocument As Document, pMandators As List(Of Mandator), pMandator As Mandator, pTemplate As Template) As Task(Of Document) Dim oMandators As List(Of Mandator) = pMandators. Where(Function(m) m.IsWhitelisted = True). OrderBy(Function(m) m.Order). ToList() Dim oInfo As New FileLoadProgressInfo(FilesTotal, FilesLoaded) With {.RunningFunction = "Mandant finden"} RaiseEvent FileLoadProgress(Me, oInfo) Dim oMandator As Mandator = Nothing If pMandator IsNot Nothing Then oMandator = pMandator Else oMandator = Winline.FindMatchingMandatorFromOrder(pDocument) End If If oMandator Is Nothing Then Logger.Warn("Mandator not found for File [{0}]", pDocument.File.Name) ' Without mandator, we just exit, life is meaningless. pDocument.AddDocumentError(DocumentErrorType.MandatorNotFound, "Mandant nicht gefunden. Verarbeitung wurde abgebrochen.") Else ' Set mandator befor applying any functions that depend on a valid mandator pDocument.Mandator = oMandator RaiseEvent FileLoadProgress(Me, New FileLoadProgressInfo(FilesTotal, FilesLoaded) With {.RunningFunction = "Winline-Funktionen"}) pDocument = Await ApplyDefinedItemFunctionsForImportAsync(pDocument, oMandator, pTemplate) pDocument = ApplyDynamicItemFunctionsForImport(pDocument, oMandator) RaiseEvent FileLoadProgress(Me, New FileLoadProgressInfo(FilesTotal, FilesLoaded) With {.RunningFunction = "Preis-Funktionen"}) If ApplicationConfig.AutomaticPriceCalculation = True Then ' These functions will only be applied if the document does not have errors pDocument = Await MaybeApplyPriceFunctions(pDocument, oMandator, pTemplate) End If RaiseEvent FileLoadProgress(Me, New FileLoadProgressInfo(FilesTotal, FilesLoaded) With {.RunningFunction = "Feld-Funktionen"}) pDocument = ApplySQLFunctionForImport(pDocument, TemplateConfig.SqlItems) ' This function needs to be the last one because ' it can relate to any previously set value pDocument = ApplyFieldFunctionForImport(pDocument, oMandator, pTemplate) End If Return pDocument End Function Private Function ApplySQLFunctionForImport(pDocument As Document, pSQLConfig As List(Of FieldConfig)) As Document For Each oSQLConfigItem In pSQLConfig ' FieldList is a list of fields that will be changed ' Example: Setting SQL for Article StorageLocation will invoke the sql for each row Dim oRowList = pDocument.Rows. Where(Function(row) row.Fields.Any(Function(field) field.Key = oSQLConfigItem.Name)). ToList() For Each oRow As DocumentRow In oRowList Dim oSQL = oSQLConfigItem.Function.Params Dim oField = oRow.Fields. Where(Function(field) field.Key = oSQLConfigItem.Name). SingleOrDefault() oSQL = Patterns.ReplaceForImport(pDocument, oRow, oSQL) Dim oValue = Database.GetScalarValue(oSQL) If oValue IsNot Nothing Then oField.Value.SetExternalValue(oValue) End If Next Next Return pDocument End Function ''' ''' Apply price calculation to the documents products ''' ''' This needs to be strictly seperated from `ApplyDefinedItemFunctionsForImport` ''' because prices can only be calculated if GLN and EAN functions were already (successfully) processed ''' Public Async Function MaybeApplyPriceFunctions(pDocument As Document, pMandator As Mandator, pTemplate As Template) As Task(Of Document) If pDocument.HasErrors Then Return pDocument End If For Each oRow As DocumentRow In pDocument.Rows Dim oTable = pTemplate.Tables.Where(Function(table) table.Name = oRow.TableName).SingleOrDefault() For Each oField In oRow.Fields If oTable Is Nothing Then Exit For End If Dim oColumn = oTable.Columns.Where(Function(c) c.Name = oField.Key).SingleOrDefault() If oColumn Is Nothing Then Continue For End If Dim oFunctionName = oColumn.Config.FunctionName Dim oFunctionParams = oColumn.Config.FunctionParams Logger.Debug("Running Function: [{0}]", oFunctionName) Logger.Debug("With Parameters: [{0}]", oFunctionParams) Dim oParamsDict = ParseFunctionParamsAsDict(oFunctionParams) If oFunctionName = Constants.FUNCTION_PRICE Then Await SetPrice(oRow, oField.Key, oParamsDict, pDocument, pMandator, pTemplate) End If Next Next Return pDocument End Function Private Async Function ApplyDefinedItemFunctionsForImportAsync(pDocument As Document, pMandator As Mandator, pTemplate As Template) As Task(Of Document) For Each oRow As DocumentRow In pDocument.Rows Dim oTable = pTemplate.Tables.Where(Function(table) table.Name = oRow.TableName).SingleOrDefault() For Each oField In oRow.Fields If oTable Is Nothing Then Exit For End If Dim oColumn = oTable.Columns.Where(Function(c) c.Name = oField.Key).SingleOrDefault() If oColumn Is Nothing Then Continue For End If Dim oFunctionName = oColumn.Config.FunctionName Dim oFunctionParams = oColumn.Config.FunctionParams Dim oParamsDict = ParseFunctionParamsAsDict(oFunctionParams) If oFunctionName = FUNCTION_GLN Then SetAccountByGLN(oRow, pMandator, oField.Key, Nothing, oParamsDict) End If If oFunctionName = FUNCTION_EAN Then SetArticleByEAN(oRow, pMandator, oField.Key) End If If oFunctionName = FUNCTION_RUNNINGNUMBER Then Await SetVersionedRunningNumber(pDocument, oRow, pMandator, oField.Key, oParamsDict) End If Next Next Return pDocument End Function Private Function ApplyFieldFunctionForImport(pDocument As Document, pMandator As Mandator, pTemplate As Template) As Document For Each oRow As DocumentRow In pDocument.Rows Dim oTable = pDocument.Schema.Tables.Where(Function(table) table.Name = oRow.TableName).SingleOrDefault() For Each oField In oRow.Fields If oTable Is Nothing Then Logger.Warn("Table [{0}] was not found in the Schema. Exiting.", oRow.TableName) Exit For End If Dim oColumn = oTable.Columns.Where(Function(c) c.Name = oField.Key).SingleOrDefault() If oColumn Is Nothing Then Logger.Warn("Column [{0}] was not found in Table [{0}]. Skipping.", oField.Key, oTable.Name) Continue For End If Dim oFunctionName = oColumn.Config.FunctionName Dim oFunctionParams = oColumn.Config.FunctionParams Dim oParamsDict = ParseFunctionParamsAsDict(oFunctionParams) If oFunctionName = Constants.FUNCTION_FIELD Then Try Logger.Debug("Applying function FIELD to field [{0}]", oField.Key) Dim oParam = oParamsDict.FirstOrDefault() If IsNothing(oParam) Then Logger.Warn("Function FIELD needs exactly one parameter. Skipping") Continue For End If Dim oFieldName = oParam.Key Dim oSubKey = oParam.Value Dim oReferencedField = oRow.Fields. Where(Function(field) field.Key = oFieldName). FirstOrDefault() If IsNothing(oReferencedField) = False Then Dim oRawValue = oReferencedField.Value?.GetValue(oSubKey) Dim oValue As String = Utils.NotNull(oRawValue, String.Empty) If oValue <> String.Empty Then oField.Value.SetExternalValue(oValue) End If Else Logger.Warn("Referenced Field [{0}] was not found. Skipping.", oFieldName) Continue For End If Catch ex As Exception Logger.Warn("Function FIELD could not be applied to field [{0}]. Skipping.", oField.Key) Continue For End Try End If Next Next Return pDocument End Function ''' ''' Execute Mappings defined in TBMT_MAPPING_CONFIG, ''' for example mapping Article Numbers to Winline Mandators ''' Private Function ApplyDynamicItemFunctionsForImport(pDocument As Document, pMandator As Mandator) As Document ' We only want the mapping config for things in the xml file. ' that excludes things like setting the mandator. Dim oFilteredMappingConfig = MappingConfig.Items. Where(Function(item) item.DestinationItem <> String.Empty). ToList() For Each oMapping As MappingConfigItem In oFilteredMappingConfig ' Get Source Value Dim oField As KeyValuePair(Of String, DocumentRow.FieldValue) = pDocument.Rows. SelectMany(Function(row) row.Fields). Where(Function(field) field.Key = oMapping.SourceItem). FirstOrDefault() ' Test on Regex Dim oRegex As New Regex(oMapping.SourceRegex) If oRegex.IsMatch(oField.Value.Final) Then pDocument.Rows. SelectMany(Function(row) row.Fields). Where(Function(field) field.Key = oMapping.DestinationItem). SetValue(Sub(field) field.Value.SetExternalValue(oMapping.DestinationValue)) Else ' don't do anything End If Next Return pDocument End Function Private Async Function SetPrice(pRow As DocumentRow, pPriceField As String, pParamMap As Dictionary(Of String, String), pDocument As Document, pMandator As Mandator, pTemplate As Template) As Task Dim oPriceItem As DocumentRow.FieldValue = pRow.Fields.GetOrDefault(pPriceField) ' These fields are fetched from the current row Const PARAMETER_ARTICLE = "Article" Dim oArticleNumberField As String = pParamMap.GetOrDefault(PARAMETER_ARTICLE, Nothing) If oArticleNumberField Is Nothing Then Logger.Warn("Parameter '{0}' not found for Function PRICE", PARAMETER_ARTICLE) End If If pRow.Fields.ContainsKey(oArticleNumberField) = False Then Logger.Warn("Value '{0}' for Parameter '{1}' not found for Function PRICE", oArticleNumberField, PARAMETER_ARTICLE) End If Dim oArticleNumber As String = pRow.Fields.Item(oArticleNumberField).Final Const PARAMETER_QUANTITY = "Quantity" Dim oQuantityField As String = pParamMap.GetOrDefault(PARAMETER_QUANTITY, Nothing) If oQuantityField Is Nothing Then Logger.Warn("Parameter '{0}' not found for Function PRICE", PARAMETER_QUANTITY) End If Dim oQuantity As Integer = 1 If Integer.TryParse(pRow.Fields.Item(oQuantityField).Final, oQuantity) = False Then Logger.Warn("Value for parameter '{0}' could not be parsed. Setting to 1.", PARAMETER_QUANTITY) End If ' These fields a fetched from the head row, ie. the first row Dim oAccountNumberField As String = pParamMap.GetOrDefault("Account", Nothing) Dim oAccountNumber = pDocument.GetFieldValue(oAccountNumberField) Dim oDocumentDateField As String = pParamMap.GetOrDefault("DocumentDate", Nothing) Dim oDocumentDate = pDocument.GetFieldValue(oDocumentDateField) Dim oDocumentKindField As String = pParamMap.GetOrDefault("DocumentKind", Nothing) Dim oDocumentKind As Integer = 0 If Integer.TryParse(pDocument.GetFieldValue(oDocumentKindField), oDocumentKind) = False Then Logger.Warn("Value for parameter DocumentKind could not be parsed. Setting to 0.") End If ' TODO: Add Field Names as Constants ' TODO: Check for missing values ' TODO: This function should not be hardcoded, but be replaced with virtual field or something.. ' It is related to customer SCHAUM and nothing general Dim oWaitingDays As Integer = Await Winline.TryGetWaitingDaysAsync(oDocumentKind, pMandator) ' END TODO Dim oArticlePrice As Double = Await Winline.TryGetArticlePriceAsync(oArticleNumber, oAccountNumber, oQuantity, oDocumentDate, pMandator, pTemplate, oWaitingDays) If oArticlePrice > 0 Then oPriceItem.SetExternalValue(oArticlePrice) Logger.Info("Price for Item [{0}] set to [{1}]", pPriceField, oArticlePrice) Else Logger.Warn("Price for Item [{0}] could not be found!", pPriceField) End If End Function Private Sub SetArticleByEAN(pRow As DocumentRow, pMandator As Mandator, pArticleField As String) Dim oNumberItem As DocumentRow.FieldValue = pRow.Fields.GetOrDefault(pArticleField) Dim oArticleNumber = Winline.TryGetArticleNumber(oNumberItem.Original, pMandator) If oArticleNumber IsNot Nothing Then oNumberItem.SetExternalValue(oArticleNumber) Logger.Info("EAN [{0}] resolved to ArticleNumber [{1}]", oNumberItem.Original, oArticleNumber) Else oNumberItem.AddFieldError(FieldErrorType.ArticleNotFound, $"EAN in Attribut '{pArticleField}' konnte nicht aufgelöst werden.") Logger.Warn("EAN [{0}] could not be resolved ArticleNumber", oNumberItem.Original) End If End Sub Private Sub SetAccountByGLN(oRow As DocumentRow, pMandator As Mandator, pNumberField As String, pNameField As String, pParams As Dictionary(Of String, String)) Try ' Try to read the Account number (which is a GLN really) and account Name Dim oNumberItem As DocumentRow.FieldValue = oRow.Fields.GetOrDefault(pNumberField) Dim oNameItem As DocumentRow.FieldValue = oRow.Fields.GetOrDefault(pNameField) Dim oContainsAccountName As Boolean = Not IsNothing(oNameItem) If oNumberItem Is Nothing Then Exit Sub End If ' Try to find an account that matches the GLN Dim oAlternateField = pParams.GetOrDefault("AltField", String.Empty) Dim oAccount = Winline.TryGetAccount(oNumberItem.Original, pMandator, "c260", oAlternateField) ' If an account was found, set it for External and Final value If oAccount IsNot Nothing Then oNumberItem.SetExternalValue(oAccount.Id) If oContainsAccountName Then oNameItem.SetExternalValue(oAccount.Name) Else ' TODO: What to to if name field is missing or not set? 'oRow.Fields.Add(pNameField, New DocumentRow.FieldValue() With { ' .External = oAccount.Name, ' .Final = oAccount.Name '}) End If Else ' If no account was found and the field is required, ' mark it as error. Otherwise, do nothing. If oNumberItem.IsRequired Then 'oNumberItem.Error = FieldErrorType.AccountNotFound oNumberItem.AddFieldError(FieldErrorType.AccountNotFound, $"GLN in Attribut '{pNumberField}' konnte nicht aufgelöst werden.") End If End If Catch ex As Exception Logger.Error(ex) Throw ex End Try End Sub Public Async Function SetVersionedRunningNumber(pDocument As Document, pRow As DocumentRow, pMandator As Mandator, pRunningNumberField As String, pParams As Dictionary(Of String, String)) As Task Try Const PARAMETER_ACCOUNT = "Account" Dim oAccountField As String = pParams.GetOrDefault(PARAMETER_ACCOUNT, Nothing) If oAccountField Is Nothing Then Logger.Warn("Parameter '{0}' not found for Function RUNNINGNUMBER", PARAMETER_ACCOUNT) End If Dim oRunningNumberItem As DocumentRow.FieldValue = pRow.Fields.GetOrDefault(pRunningNumberField) Dim oRunningNumber = oRunningNumberItem.Final Dim oAccountNumberItem As DocumentRow.FieldValue = pRow.Fields.GetOrDefault(oAccountField) Dim oAccountNumber = oAccountNumberItem.Final Dim oVersionedNumber = Await Winline.GetVersionedRunningNumberAsync(pDocument, pMandator, oAccountNumber, oRunningNumber) If oVersionedNumber <> oRunningNumber Then oRunningNumberItem.SetExternalValue(oVersionedNumber) End If Catch ex As Exception Logger.Error(ex) Throw ex End Try End Function Private Function ParseFunctionParamsAsDict(pParams As String) As Dictionary(Of String, String) Try Dim oParamsDict As New Dictionary(Of String, String) If pParams <> String.Empty Then Dim oParamList = pParams.Split("|").ToList() For Each oParam In oParamList Dim oParamSplit = oParam.Split("=") If oParamSplit.Count = 2 Then oParamsDict.Add(oParamSplit(0), oParamSplit(1)) End If Next End If Return oParamsDict Catch ex As Exception Logger.Error(ex) Return New Dictionary(Of String, String) End Try End Function End Class End Namespace