542 lines
23 KiB
VB.net
542 lines
23 KiB
VB.net
Imports System.Collections.Specialized
|
|
Imports System.Net.Http
|
|
Imports System.Text
|
|
Imports System.Xml
|
|
Imports DigitalData.Modules.Database
|
|
Imports DigitalData.Modules.Filesystem
|
|
Imports DigitalData.Modules.Logging
|
|
Imports DigitalData.Modules.Language
|
|
Imports MultiTool.Common.Documents
|
|
Imports MultiTool.Common.Exceptions
|
|
Imports MultiTool.Common.Templates
|
|
Imports MultiTool.Common.Templates.GeneralConfig
|
|
Imports MultiTool.Common.Winline.Entities
|
|
|
|
Namespace Winline
|
|
Public Class WebServiceData
|
|
Inherits BaseClass
|
|
|
|
Private ReadOnly Config As WebServiceConfig
|
|
Private ReadOnly Filters As FilterConfig
|
|
Private ReadOnly Serializer As Serializer
|
|
Private ReadOnly Winline As WinlineData
|
|
Private ReadOnly FileEx As File
|
|
Private ReadOnly Patterns As Patterns
|
|
|
|
Public Event WebServiceProgress As EventHandler(Of String)
|
|
|
|
Public Sub New(pLogConfig As LogConfig, pDatabase As MSSQLServer, pWinline As WinlineData, pWebserviceConfig As WebServiceConfig, pGeneralConfig As GeneralConfig, pFilterConfig As FilterConfig)
|
|
MyBase.New(pLogConfig, pDatabase)
|
|
Serializer = New Serializer(pLogConfig)
|
|
Config = pWebserviceConfig
|
|
Filters = pFilterConfig
|
|
Patterns = New Patterns(pLogConfig, pGeneralConfig)
|
|
FileEx = New File(LogConfig)
|
|
Winline = pWinline
|
|
End Sub
|
|
|
|
Public Sub RaiseWebServiceProgress(pMessage As String)
|
|
RaiseEvent WebServiceProgress(Me, pMessage)
|
|
End Sub
|
|
|
|
#Region "Import"
|
|
''' <summary>
|
|
''' Transfers a document to winline via Webservices
|
|
''' </summary>
|
|
''' <param name="pDocument"></param>
|
|
''' <param name="pTemplate"></param>
|
|
''' <param name="pMandator"></param>
|
|
''' <param name="pIsTest"></param>
|
|
''' <exception cref="HttpRequestException"></exception>
|
|
''' <exception cref="WebServiceException"></exception>
|
|
''' <exception cref="TaskCanceledException"></exception>
|
|
''' <returns>True if request was successful.</returns>
|
|
Public Async Function TransferDocumentToWinline(pDocument As Documents.Document, pTemplate As Template, pMandator As Mandator, Optional pIsTest As Boolean = False) As Task(Of Boolean)
|
|
Dim oWS = Config
|
|
|
|
RaiseEvent WebServiceProgress(Me, "Einstellungen laden")
|
|
Logger.Debug("Loading setting and creating directories")
|
|
|
|
' --- Build all teh filenamez and pathz
|
|
Dim oBaseFileName As String = FileEx.GetDateTimeString()
|
|
Dim oFileName = $"{pTemplate.Name}-{oBaseFileName}-Request.xml"
|
|
|
|
' --- Get and create path for request/response files
|
|
Dim oOutputDirectory = FileEx.CreateDateDirectory(pTemplate.OutputWebserviceDirectory)
|
|
Dim oOutputFilePath = IO.Path.Combine(oOutputDirectory, oFileName)
|
|
|
|
' Generate absolute path to copy xml file to
|
|
Dim oAbsolutePath = IO.Path.Combine(oWS.ImportBasePath, oWS.ImportRelativePath)
|
|
oAbsolutePath = FileEx.CreateDateDirectory(oAbsolutePath)
|
|
Dim oImportAbsoluteFilePath = IO.Path.Combine(oAbsolutePath, oFileName)
|
|
|
|
' Generate relative path to supply to winline
|
|
Dim oRelativePath = IO.Path.Combine(oWS.ImportRelativePath)
|
|
oRelativePath = FileEx.GetDateDirectory(oRelativePath)
|
|
Dim oImportRelativeFilePath = IO.Path.Combine(oRelativePath, oFileName)
|
|
|
|
RaiseEvent WebServiceProgress(Me, "Dateien schreiben")
|
|
Logger.Debug("Writing request file to [{oOutputFilePath}]")
|
|
|
|
' If configured, prevent printing the document and instead create it as a draft in the "Nicht gedruckt" section
|
|
If pTemplate.PrintDocument = False Then
|
|
pDocument.PrintVoucher = 0
|
|
End If
|
|
|
|
' --- Serialize Data into XML string
|
|
Dim oBytes As Byte() = GetBytesFromDocument(pDocument)
|
|
IO.File.WriteAllBytes(oOutputFilePath, oBytes)
|
|
|
|
' --- Copy file to Winline Import Directory
|
|
Try
|
|
Logger.Debug("Copying request file to [{oImportAbsoluteFilePath}]")
|
|
IO.File.Copy(oOutputFilePath, oImportAbsoluteFilePath, True)
|
|
Catch ex As Exception
|
|
Logger.Error(ex)
|
|
Throw ex
|
|
End Try
|
|
|
|
' --- Prepare URL and HTTP Client
|
|
Dim oTemplateType = pDocument.TemplateType
|
|
Dim oTemplateName = pDocument.TemplateName
|
|
Logger.Debug("Using Template [{0}/{1}]", oTemplateName, oTemplateType)
|
|
|
|
' ActionCode: Should this be a test or not?
|
|
' 0 = Test call
|
|
' 1 = Real call
|
|
Dim oActionCode = 1
|
|
If pIsTest = True Then
|
|
oActionCode = 0
|
|
End If
|
|
Logger.Debug("Using ActionCode [{0}]", oActionCode)
|
|
|
|
' Byref: Should data be supplied as file or as string?
|
|
' 0 = As String
|
|
' 1 = As File (relative to Winline Server directory)
|
|
Dim oByref = 1
|
|
|
|
Dim oParams As New NameValueCollection() From {
|
|
{"User", oWS.Username},
|
|
{"Password", oWS.Password},
|
|
{"Company", pMandator.Id},
|
|
{"Type", oTemplateType},
|
|
{"Vorlage", oTemplateName},
|
|
{"ActionCode", oActionCode},
|
|
{"Byref", oByref},
|
|
{"Data", oImportRelativeFilePath}
|
|
}
|
|
|
|
Dim oURL As String = $"{oWS.BaseUrl}/ewlservice/import{ToQueryString(oParams)}"
|
|
'Dim oURL As String = $"{oWS.BaseUrl}/ewlservice/import?User={oWS.Username}&Password={oWS.Password}&Company={pMandator.Id}&Type={oTemplateType}&Vorlage={oTemplateName}&ActionCode={oActionCode}&Byref={oByref}&Data={oImportRelativeFilePath}"
|
|
Dim oClient As New HttpClient With {
|
|
.Timeout = TimeSpan.FromSeconds(Constants.HTTP_REQUEST_TIMEOUT_IN_SECONDS)
|
|
}
|
|
|
|
Logger.Info("Creating HTTP Request to [{0}]", oWS.BaseUrl)
|
|
|
|
RaiseEvent WebServiceProgress(Me, "Anfrage absenden")
|
|
|
|
' --- Bring the action!
|
|
Try
|
|
Dim oResponse As HttpResponseMessage = Await oClient.GetAsync(oURL)
|
|
Logger.Debug("HTTP Response recevied!")
|
|
|
|
Await HandleImportResponse(oResponse, pTemplate, oBaseFileName)
|
|
|
|
Return True
|
|
Catch ex As Exception
|
|
Logger.Error(ex)
|
|
Throw ex
|
|
Finally
|
|
oClient.Dispose()
|
|
End Try
|
|
End Function
|
|
|
|
Private Async Function HandleImportResponse(pResponse As HttpResponseMessage, pTemplate As Template, pBaseFileName As String) As Task
|
|
pResponse.EnsureSuccessStatusCode()
|
|
Dim oResponseBody As String = Await pResponse.Content.ReadAsStringAsync()
|
|
Dim oContentType = pResponse.Content.Headers.ContentType.MediaType
|
|
Dim oSerializer = Serializer.GetSerializer(GetType(Templates.Entities.MESOWebServiceResult))
|
|
Dim oOutputDirectory = FileEx.CreateDateDirectory(pTemplate.OutputWebserviceDirectory)
|
|
|
|
RaiseEvent WebServiceProgress(Me, "Antwort verarbeiten")
|
|
|
|
Logger.Debug("Response ContentType: [{0}]", oContentType)
|
|
|
|
Select Case oContentType
|
|
Case "text/xml"
|
|
Logger.Debug("Got XML Response. Checking.")
|
|
|
|
WriteResponseFile(oOutputDirectory, oResponseBody, $"{pTemplate.Name}-{pBaseFileName}-Response.xml")
|
|
|
|
Dim oBytes As Byte() = Encoding.UTF8.GetBytes(oResponseBody)
|
|
Using oStream As New IO.MemoryStream(oBytes)
|
|
Dim oResponseObject As Templates.Entities.MESOWebServiceResult = oSerializer.Deserialize(oStream)
|
|
Dim oErrorStrings As New List(Of String)
|
|
|
|
If oResponseObject.ResultDetails IsNot Nothing Then
|
|
For Each oDetails As Templates.Entities.MESOWebServiceResultResultDetails In oResponseObject.ResultDetails
|
|
|
|
If oDetails.Success = True Then
|
|
Logger.Debug("KeyValue: [{0}]", oDetails.KeyValue)
|
|
Logger.Debug("VoucherNumber: [{0}]", oDetails.VoucherNumber)
|
|
Else
|
|
Logger.Warn("ErrorCode: [{0}]", oDetails.ErrorCode)
|
|
Logger.Warn("ErrorText: [{0}]", oDetails.ErrorText)
|
|
oErrorStrings.Add($"[{oDetails.ErrorCode}] {oDetails.ErrorText}")
|
|
End If
|
|
Next
|
|
End If
|
|
|
|
If oResponseObject.OverallSuccess = False Then
|
|
Dim oMessage = $"Request to Webservice was unsuccessful:{vbNewLine}{vbNewLine}{String.Join(vbNewLine, oErrorStrings.ToArray)}"
|
|
|
|
Logger.Warn("Overall Success was [false]")
|
|
|
|
Throw New WebServiceException(oMessage)
|
|
End If
|
|
End Using
|
|
|
|
Case "text/html"
|
|
Logger.Debug("Got TEXT/HTML Response. Throwing.")
|
|
WriteResponseFile(oOutputDirectory, oResponseBody, $"{pTemplate.Name}-{pBaseFileName}-Response.xml")
|
|
|
|
Throw New WebServiceException(oResponseBody)
|
|
|
|
Case Else
|
|
Logger.Debug("Unknown Response ContentType: [{0}]", oContentType)
|
|
|
|
Throw New WebServiceException(oResponseBody)
|
|
|
|
End Select
|
|
End Function
|
|
|
|
Private Function GetBytesFromDocument(pDocument As Documents.Document) As Byte()
|
|
Using oStream As New IO.MemoryStream()
|
|
Dim w = XmlWriter.Create(oStream)
|
|
|
|
w.WriteStartDocument()
|
|
w.WriteStartElement("MESOWebService")
|
|
w.WriteAttributeString("Template", pDocument.TemplateName)
|
|
w.WriteAttributeString("TemplateType", pDocument.TemplateType)
|
|
w.WriteAttributeString("option", pDocument.Option)
|
|
w.WriteAttributeString("printVoucher", pDocument.PrintVoucher)
|
|
|
|
pDocument.Rows.Sort()
|
|
|
|
For Each oRow In pDocument.Rows
|
|
w.WriteStartElement(oRow.TableName)
|
|
|
|
For Each oField As KeyValuePair(Of String, DocumentRow.FieldValue) In oRow.Fields
|
|
If oField.Value.Final = String.Empty Then
|
|
Continue For
|
|
End If
|
|
|
|
If oField.Value.IsVirtual Then
|
|
Continue For
|
|
End If
|
|
|
|
w.WriteStartElement(oField.Key)
|
|
w.WriteValue(oField.Value.Final)
|
|
w.WriteEndElement() ' Field
|
|
Next
|
|
|
|
w.WriteEndElement() ' Row
|
|
Next
|
|
w.WriteEndElement() ' MESOWebService
|
|
w.WriteEndDocument() ' Document
|
|
w.Close()
|
|
|
|
Return oStream.ToArray()
|
|
End Using
|
|
End Function
|
|
|
|
#End Region
|
|
|
|
#Region "Export"
|
|
Async Function ExportDocumentFromWinline(pDocument As ExportDocument, pTemplate As Template, pMandator As Mandator, Optional pIsTest As Boolean = False) As Task(Of Boolean)
|
|
Dim oWS = Config
|
|
|
|
Logger.Info("Exporting document from Winline")
|
|
|
|
' --- Build all teh filenamez and pathz
|
|
Dim oBaseFileName As String = FileEx.GetDateTimeString()
|
|
Dim oFileName = $"{pTemplate.Name}-{oBaseFileName}.xml"
|
|
'Dim oFileName = FileEx.GetFilenameWithPrefix(oBaseFileName, pTemplate.Name, "xml")
|
|
|
|
Logger.Info("Filename will be [{0}]", oFileName)
|
|
|
|
' Save the filename to the document
|
|
pDocument.FilenameExport = oFileName
|
|
|
|
RaiseEvent WebServiceProgress(Me, "Abfrage vorbereiten")
|
|
|
|
Logger.Debug("Preparing WebService call")
|
|
|
|
' --- Prepare URL and HTTP Client
|
|
Dim oTemplateType = 30
|
|
Dim oTemplateName = pDocument.Schema.Name
|
|
Dim oKey = $"{pDocument.Account.Id}-{pDocument.RunningNumber}"
|
|
|
|
' ActionCode: Should this be a test or not?
|
|
' 0 = Test call
|
|
' 1 = Real call
|
|
Dim oActionCode = 1
|
|
If pIsTest = True Then
|
|
oActionCode = 0
|
|
End If
|
|
|
|
Logger.Debug("This is a test: [{0}]", pIsTest)
|
|
|
|
Dim oURL As String = $"{oWS.BaseUrl}/ewlservice/export?User={oWS.Username}&Password={oWS.Password}&Company={pMandator.Id}&Type={oTemplateType}&Vorlage={oTemplateName}&ActionCode={oActionCode}&Key={oKey}"
|
|
Dim oClient As New HttpClient()
|
|
|
|
Logger.Info("Creating HTTP Request to [{0}]", oWS.BaseUrl)
|
|
|
|
RaiseEvent WebServiceProgress(Me, "Anfrage absenden")
|
|
|
|
' --- Bring the action!
|
|
Try
|
|
Dim oResponse As HttpResponseMessage = Await oClient.GetAsync(oURL)
|
|
Logger.Info("Request ended with code: [{0}]", oResponse.StatusCode)
|
|
|
|
Await HandleExportResponse(oResponse, pDocument, pTemplate, pMandator, oBaseFileName)
|
|
|
|
Return True
|
|
|
|
Catch ex As Exception
|
|
Logger.Error(ex)
|
|
Throw ex
|
|
|
|
Finally
|
|
oClient.Dispose()
|
|
|
|
End Try
|
|
End Function
|
|
|
|
Private Async Function HandleExportResponse(pResponse As HttpResponseMessage, pDocument As Entities.ExportDocument, pTemplate As Template, pMandator As Mandator, pBaseFileName As String) As Task
|
|
pResponse.EnsureSuccessStatusCode()
|
|
|
|
Dim oResponseBody As String = Await pResponse.Content.ReadAsStringAsync()
|
|
Dim oContentType = pResponse.Content.Headers.ContentType.MediaType
|
|
Dim oSerializer = Serializer.GetSerializer(GetType(Templates.Entities.MESOWebServiceResult))
|
|
|
|
RaiseEvent WebServiceProgress(Me, "Antwort verarbeiten")
|
|
|
|
Logger.Debug("Processing response with type '{0}'", oContentType)
|
|
|
|
Select Case oContentType
|
|
Case "text/xml"
|
|
Dim oXmlResponse = oResponseBody
|
|
Dim oDoc = ConvertStringToDocument(oXmlResponse)
|
|
|
|
Logger.Debug("Applying Item Filters")
|
|
oDoc = ApplyItemFiltersForExport(pDocument, pTemplate, pMandator, oDoc)
|
|
|
|
Logger.Debug("Applying Item Functions")
|
|
oDoc = ApplyItemFunctionsForExport(pDocument, pTemplate, pMandator, oDoc)
|
|
|
|
'TODO: Load filters and apply
|
|
|
|
Dim oXml = ConvertDocumentToString(oDoc)
|
|
|
|
' Webservice
|
|
WriteResponseFile(pTemplate.OutputWebserviceDirectory, oXml, $"{pTemplate.Name}-{pBaseFileName}-Response.xml")
|
|
' XML
|
|
WriteResponseFile(pTemplate.OutputXmlFileDirectory, oXml, $"{pTemplate.Name}-{pBaseFileName}.xml")
|
|
' Archive
|
|
WriteResponseFile(FileEx.CreateDateDirectory(pTemplate.ArchiveDirectory), oXml, $"{pTemplate.Name}-{pBaseFileName}.xml")
|
|
|
|
Case "text/html"
|
|
WriteResponseFile(pTemplate.OutputWebserviceDirectory, oResponseBody, $"{pTemplate.Name}-{pBaseFileName}-Response.txt")
|
|
|
|
Throw New ApplicationException(oResponseBody)
|
|
|
|
Case Else
|
|
Throw New ApplicationException(oResponseBody)
|
|
End Select
|
|
End Function
|
|
|
|
Private Function ConvertStringToDocument(pXmlString As String) As XmlDocument
|
|
Dim oDoc As New XmlDocument()
|
|
oDoc.LoadXml(pXmlString)
|
|
Return oDoc
|
|
End Function
|
|
|
|
Private Function ConvertDocumentToString(pXmlDocument As XmlDocument) As String
|
|
Dim oArray As Byte()
|
|
Using oStream As New IO.MemoryStream
|
|
pXmlDocument.Save(oStream)
|
|
oArray = oStream.ToArray()
|
|
End Using
|
|
|
|
Return System.Text.Encoding.UTF8.GetString(oArray)
|
|
End Function
|
|
|
|
Private Function ApplyItemFunctionsForExport(pDocument As Entities.ExportDocument, pTemplate As Template, pMandator As Mandator, oXMLDocument As XmlDocument) As XmlDocument
|
|
For Each oTable In pTemplate.Tables
|
|
Logger.Debug("Processing Table [{0}]", oTable.Name)
|
|
|
|
For Each oItem As Template.Column In oTable.Columns
|
|
Dim oTableName As String = oTable.Name
|
|
Dim oItemName As String = oItem.Name
|
|
|
|
If oItem.Config.Function Is Nothing Then
|
|
Continue For
|
|
End If
|
|
|
|
Logger.Debug("Processing item [{0}]", oItemName)
|
|
|
|
Dim oFunction = oItem.Config.Function.Name
|
|
|
|
Dim oPath = $"//MESOWebService/{oTableName}/{oItemName}"
|
|
Dim oNodes As XmlNodeList = oXMLDocument.SelectNodes(oPath)
|
|
|
|
Logger.Debug("Calling function [{0}] on note [{1}]", oFunction, oPath)
|
|
|
|
For Each oNode As XmlNode In oNodes
|
|
If oItem.Config.Function.Name = "GLN" Then
|
|
Dim oGLN = Winline.TryGetGLN(oNode.InnerText, pMandator)
|
|
|
|
If oGLN Is Nothing Then
|
|
Throw New MissingAttributeException("GLN")
|
|
End If
|
|
|
|
oNode.InnerText = oGLN
|
|
|
|
ElseIf oItem.Config.Function.Name = "EAN" Then
|
|
Dim oEAN = Winline.TryGetEAN(oNode.InnerText, pMandator)
|
|
|
|
If oEAN Is Nothing Then
|
|
' 21.04.2022: Relax the EAN Check
|
|
' Since it is possible to have articles without a proper EAN in export,
|
|
' we dont throw here, but leave the original value in case of a failure.
|
|
' Throw New Exceptions.MissingAttributeException("EAN")
|
|
Logger.Warn("EAN could not be retrieved for Node {0}. Skipping.", oNode.Name)
|
|
End If
|
|
|
|
oNode.InnerText = oEAN
|
|
|
|
ElseIf oItem.Config.Function.Name = "SQL" Then
|
|
Dim oSQL = Patterns.ReplaceForExport(pDocument, pMandator, oItem.Config.Function.Params)
|
|
Dim oValue = Database.GetScalarValue(oSQL)
|
|
|
|
If oValue Is Nothing Then
|
|
Throw New MissingAttributeException("SQL")
|
|
End If
|
|
|
|
oNode.InnerText = oValue
|
|
|
|
End If
|
|
Next
|
|
Next
|
|
Next
|
|
|
|
Return oXMLDocument
|
|
End Function
|
|
|
|
Private Function ApplyItemFiltersForExport(pDocument As ExportDocument, pTemplate As Template, pMandator As Mandator, oXMLDocument As XmlDocument) As XmlDocument
|
|
Dim oTableNames = pDocument.Schema.Tables.
|
|
Select(Function(table) table.Name).
|
|
ToList()
|
|
Dim oFilters = Filters.Items.
|
|
Where(Function(filter) oTableNames.Contains(filter.TableName)).
|
|
ToList()
|
|
|
|
For Each oFilter As FilterConfigItem In oFilters
|
|
Dim oTableName = oFilter.TableName
|
|
|
|
If String.IsNullOrEmpty(oFilter.SQLCommand) Then
|
|
Logger.Warn("SQL Command for filter is empty. Continuing.")
|
|
Continue For
|
|
End If
|
|
|
|
Dim oSQLResult = Database.GetDatatable(oFilter.SQLCommand)
|
|
|
|
If oSQLResult Is Nothing Then
|
|
Logger.Warn("SQL Command for filter returned nothing: {0}. Continuing.", oFilter.SQLCommand)
|
|
Continue For
|
|
End If
|
|
|
|
Dim oResultList = oSQLResult.AsEnumerable.
|
|
Select(Of String)(Function(row) row.Item(0)).
|
|
ToList()
|
|
|
|
Dim oNodes = oXMLDocument.SelectNodes($"//{oTableName}")
|
|
|
|
For Each oNode As XmlNode In oNodes
|
|
Dim oSubNode = oNode.SelectSingleNode($"//{oFilter.ColumnName}")
|
|
|
|
If oSubNode IsNot Nothing AndAlso oResultList.Contains(oSubNode.InnerText) Then
|
|
oNode.ParentNode.RemoveChild(oNode)
|
|
'oDoc.RemoveChild(oNode)
|
|
End If
|
|
Next
|
|
Next
|
|
|
|
Return oXMLDocument
|
|
End Function
|
|
#End Region
|
|
|
|
|
|
Private Function WriteResponseFileWithSuffix(pPath As String, pBaseFileName As String, pResponseBody As String, pExtension As String, pSuffix As String) As Boolean
|
|
Try
|
|
Dim oRequestFileName As String = FileEx.GetFilenameWithSuffix(pBaseFileName, pSuffix, pExtension)
|
|
Dim oFilePath As String = IO.Path.Combine(pPath, oRequestFileName)
|
|
IO.File.WriteAllText(oFilePath, pResponseBody)
|
|
|
|
Return True
|
|
Catch ex As Exception
|
|
Logger.Error(ex)
|
|
Return False
|
|
End Try
|
|
End Function
|
|
|
|
Private Function WriteResponseFileWithPrefix(pPath As String, pBaseFileName As String, pResponseBody As String, pExtension As String, pPrefix As String) As Boolean
|
|
Try
|
|
Dim oRequestFileName As String = FileEx.GetFilenameWithPrefix(pBaseFileName, pPrefix, pExtension)
|
|
Dim oFilePath As String = IO.Path.Combine(pPath, oRequestFileName)
|
|
IO.File.WriteAllText(oFilePath, pResponseBody)
|
|
|
|
Return True
|
|
Catch ex As Exception
|
|
Logger.Error(ex)
|
|
Return False
|
|
End Try
|
|
End Function
|
|
|
|
Private Function WriteResponseFile(pPath As String, pResponseBody As String, pFileName As String) As Boolean
|
|
Try
|
|
Dim oFilePath As String = IO.Path.Combine(pPath, pFileName)
|
|
IO.File.WriteAllText(oFilePath, pResponseBody)
|
|
|
|
Return True
|
|
Catch ex As Exception
|
|
Logger.Error(ex)
|
|
Return False
|
|
End Try
|
|
End Function
|
|
|
|
Private Function ToQueryString(ByVal nvc As NameValueCollection) As String
|
|
Dim sb As StringBuilder = New StringBuilder("?")
|
|
Dim first As Boolean = True
|
|
|
|
For Each key As String In nvc.AllKeys
|
|
|
|
For Each value As String In nvc.GetValues(key)
|
|
|
|
If Not first Then
|
|
sb.Append("&")
|
|
End If
|
|
|
|
sb.AppendFormat("{0}={1}", Uri.EscapeDataString(key), Uri.EscapeDataString(value))
|
|
first = False
|
|
Next
|
|
Next
|
|
|
|
Return sb.ToString()
|
|
End Function
|
|
End Class
|
|
|
|
|
|
End Namespace |