I wrote a simple code which POST credential to a website. I need the return value but I get a null which results to ArgumentException. How can I do this properly
Imports System.Net
Imports System.Text
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim routerUri = "http://127.0.0.1/login.cgi"
Dim routerMethod = "POST"
Dim routerPostData = New Specialized.NameValueCollection From {
{"username", ""},
{"password", ""},
{"foo", "bar"}
}
Console.WriteLine(Encoding.UTF8.GetString(GetResponse(routerUri, routerMethod, routerPostData)))
End Sub
Function GetResponse(hUri As String, hMethod As String, rqParam As Specialized.NameValueCollection)
Dim uriUri As New Uri(hUri)
Dim rByte
Dim tTask = New Task(
Async Sub()
Dim task As Task(Of Byte()) = SendRequest(uriUri, hMethod, rqParam)
rByte = Await task
End Sub)
tTask.Start()
tTask.Wait()
Return rByte
End Function
Async Function SendRequest(hUri As Uri, hMethod As String, rqParam As Specialized.NameValueCollection) As Task(Of Byte())
Dim rByte As Byte()
Using client As New WebClient
rByte = Await client.UploadValuesTaskAsync(hUri, hMethod, rqParam)
End Using
Return rByte
End Function
End Class
Event handlers allow for Async Subs so it would be better to just make the handler async and call the async function directly. Avoid creating new Tasks manually.
Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim routerUri = "http://127.0.0.1/login.cgi"
Dim routerMethod = "POST"
Dim routerPostData = New Specialized.NameValueCollection From {
{"username", ""},
{"password", ""},
{"foo", "bar"}
}
Dim bytes = Await GetResponseAsync(routerUri, routerMethod, routerPostData)
Console.WriteLine(Encoding.UTF8.GetString(bytes))
End Sub
Where GetResponseAsync is
Async Function GetResponseAsync(hUri As String, hMethod As String, rqParam As Specialized.NameValueCollection) As Task(Of Byte())
Dim uriUri As New Uri(hUri)
Return Await SendRequest(uriUri, hMethod, rqParam)
End Function
Reference Async/Await - Best Practices in Asynchronous Programming
Related
The application creates a collection of tasks to obtain folder info from a specific folder path.
This code performs well, but my issue is that the UI is freezing while adding tasks to the list, especially in the while loop, until all tasks are complete.
Here's the code:
Async Function ProcessFolders(ct As CancellationToken, FolderList As IEnumerable(Of
String)) As Task
' Create a collection of tasks
Dim processFoldersQuery As IEnumerable(Of Task(Of String())) =
From folder In FolderList Select GetFolderInfo(folder, ct)
Dim processQuery As List(Of Task(Of String())) = processFoldersQuery.ToList()
' Run through tasks
While processQuery.Count > 0
Dim finishedTask As Task(Of String()) = Await Task.WhenAny(processQuery)
processQuery.Remove(finishedTask)
Dim result = Await finishedTask
folder_dt.Rows.Add(Nothing, result(0), result(1), result(2))
End While
End Function
Async Function GetFolderInfo(folder As String, ct As CancellationToken) As Task(Of
String())
Return Await Task.Run(Async Function()
Dim folder_info = New DirectoryInfo(folder)
Dim result() As String = {folder_info.Name, Await
GetFileMd5(folder, False), Await GetDirectorySize(folder)}
Return result
End Function)
End Function
How to achieve this without UI freezing? I have been looking into parallel and async loops and various different async techniques, but I am not sure how to implement them in this situation.
GetFileMd5() and GetDirectorySize() functions below:
Shared Async Function GetDirectorySize(path As String) As Task(Of Long)
Return Await Task.Run(Function()
Return Directory.GetFiles(path, "*", SearchOption.AllDirectories).Sum(Function(t) (New FileInfo(t).Length))
End Function)
End Function
Private Async Function GetFileMd5(file_name As String, convert_file As Boolean) As Task(Of String)
Return Await Task.Run(Function()
Dim byteHash() As Byte
Dim ByteArrayToString = Function(arrInput() As Byte)
Dim sb As New Text.StringBuilder(arrInput.Length * 2)
For i As Integer = 0 To arrInput.Length - 1
sb.Append(arrInput(i).ToString("X2"))
Next
Return sb.ToString().ToLower
End Function
If convert_file Then
byteHash = Md5CSP.ComputeHash(File.OpenRead(file_name))
Return ByteArrayToString(byteHash)
End If
If convert_file = False Then byteHash = Md5CSP.ComputeHash(System.Text.Encoding.Unicode.GetBytes(file_name)) : Return ByteArrayToString(byteHash)
Return Nothing
End Function)
End Function
A couple of examples that may mitigate a form of lagging you're experiencing when the number of items in the list of folders you're processing increases.
In the current code, these two lines:
processQuery.Remove(finishedTask)
' [...]
folder_dt.Rows.Add(Nothing, result(0), result(1), result(2))
are executed in the UI Thread. In my view, the former is troublesome.
It's also not clear how the DataTable is used. It appears that when ProcessFolders() (which doesn't return that object) returns, a previously existing DataTable is blindly assigned to something.
ProcessFolders() shouldn't know about this, already existing, DataTable.
In the first example, the entry method (renamed ProcessFoldersAsync()), creates a DataTable that is meant to contain the data it generates and returns this DataTable.
You should pass the CancellationToken also to the GetFileMd5() and GetDirectorySize() methods.
Private cts As CancellationTokenSource = Nothing
Private folder_dt As DataTable = Nothing
Private Async Sub SomeButton_Click(sender As Object, e As EventArgs) Handles SomeButton.Click
Dim listOfFolders As New List(Of String) = [Some source of URIs]
cts = New CancellationTokenSource
Using cts
' Await resumes in the UI Thread
folder_dt = Await ProcessFoldersAsync(listOfFolder, cts.Token)
[Some Control].DataSource = folder_dt
End Using
End Sub
Async Function ProcessFoldersAsync(folderList As IEnumerable(Of String), dtSchema As DataTable, token As CancellationToken) As Task(Of DataTable)
Dim processQuery As New List(Of Task(Of Object()))
For Each f In folderList
processQuery.Add(GetFolderInfoAsync(f, token))
Next
If token.IsCancellationRequested Then Return Nothing
Try
' Resumes on a ThreadPool Thread
Await Task.WhenAll(processQuery).ConfigureAwait(False)
' Generate a new DataTable and fills it with the results of all Tasks
' This code executes in a ThreadPool Thread
Dim dt = CreateDataTable()
For Each obj In processQuery
If obj.Result IsNot Nothing Then dt.Rows.Add(obj.Result)
Next
Return dt
Catch ex As TaskCanceledException
Return Nothing
End Try
End Function
Async Function GetFolderInfoAsync(folder As String, token As CancellationToken) As Task(Of Object())
token.ThrowIfCancellationRequested()
Dim folderName = Path.GetFileName(folder)
Return {Nothing, folderName, Await GetFileMd5(folder, False), Await GetDirectorySize(folder)}
End Function
In the second example, an IProgress<T> delegate is used perform the updates it receives from the GetFolderInfoAsync() method.
In this case, the DataTable already exists and could be already assigned to / used by Controls. In this case, the updates are performed in real time, as the async methods return their results, which may happen at different times.
This method may slow down the process, overall.
ProcessFoldersAsync() passes the Progress object delegate to GetFolderInfoAsync(), which calls Report() method with the data object it has elaborated.
The UpdatedDataTable() delegate method adds the new data arrived to the existing DataTable
Note that, in this case, if the Tasks are canceled, the updates already stored are retained (unless you decide otherwise, that is. You can always set the DataTable to null when you catch a TaskCanceledException exception)
' [...]
Private objLock As New Object()
Private updater As IProgress(Of IEnumerable(Of Object))
Private Async Sub SomeButton_Click(sender As Object, e As EventArgs) Handles SomeButton.Click
Dim listOfFolders As New List(Of String) = [Some source of URIs]
folder_dt = CreateDataTable()
[Some Control].DataSource = folder_dt
cts = New CancellationTokenSource
Using cts
updater = New Progress(Of IEnumerable(Of Object))(Sub(data) UpdatedDataTable(data))
Try
Await ProcessFoldersAsync(listOfFolder, updater, cts.Token)
Catch ex As TaskCanceledException
Debug.WriteLine("Tasks were canceled")
End Try
End Using
End Sub
Async Function ProcessFoldersAsync(folderList As IEnumerable(Of String), updater As IProgress(Of IEnumerable(Of Object)), token As CancellationToken) As Task
Dim processQuery As New List(Of Task)
For Each f In folderList
processQuery.Add(GetFolderInfoAsync(f, updater, token))
Next
token.ThrowIfCancellationRequested()
Await Task.WhenAll(processQuery).ConfigureAwait(False)
End Function
Async Function GetFolderInfoAsync(folder As String, progress As IProgress(Of IEnumerable(Of Object)), token As CancellationToken) As Task
token.ThrowIfCancellationRequested()
Dim folderName = Path.GetFileName(folder)
Dim result As Object() = {Nothing, folderName, Await GetFileMd5(folder, False), Await GetDirectorySize(folder)}
progress.Report(result)
End Function
Private Sub UpdatedDataTable(data As IEnumerable(Of Object))
SyncLock objLock
folder_dt.Rows.Add(data.ToArray())
End SyncLock
End Sub
Private Function CreateDataTable() As DataTable
Dim dt As New DataTable()
Dim col As New DataColumn("IDX", GetType(Long)) With {
.AutoIncrement = True,
.AutoIncrementSeed = 1,
.AutoIncrementStep = 1
}
dt.Columns.Add(col)
dt.Columns.Add("FolderName", GetType(String))
dt.Columns.Add("MD5", GetType(String))
dt.Columns.Add("Size", GetType(Long))
Return dt
End Function
I am working on coronavirus statistics dashboard as university project, and I have some problems with asynchronous source data download from sites with statistics.
Well, I failed to understand how to do it myself.
I tried to create my own class with function what will create multiple async web requests
and then wait until they all finished, then return results of all these requests.
Imports System.Net.WebClient
Imports System.Net
Public Class AsyncDownload
Private result As New Collection
Private Sub DownloadCompletedHander(ByVal sender As Object, ByVal e As System.Net.DownloadStringCompletedEventArgs)
If e.Cancelled = False AndAlso e.Error Is Nothing Then
Dim myString As String = CStr(e.Result)
result.Add(myString, sender.Headers.Item("source"))
End If
End Sub
Public Function Load(sources As Array, keys As Array) As Collection
Dim i = 0
Dim WebClients As New Collection
While (i < sources.Length)
Dim newClient As New WebClient
newClient.Headers.Add("source", keys(i))
newClient.Headers.Add("sourceURL", sources(i))
AddHandler newClient.DownloadStringCompleted, AddressOf DownloadCompletedHander
WebClients.Add(newClient)
i = i + 1
End While
i = 1
For Each client As WebClient In WebClients
Dim url As String = client.Headers.Item("sourceURL")
client.DownloadStringAsync(New Uri(url))
Next
While (result.Count < WebClients.Count)
End While
Return result
End Function
End Class
And it is used in:
Dim result As New Collection
Private Sub test() Handles Me.Load
Dim downloader As New CoronaStatisticsGetter.AsyncDownload
result = downloader.Load({"https://opendata.digilugu.ee/covid19/vaccination/v3/opendata_covid19_vaccination_total.json"}, {"Nationalwide Data"})
End Sub
It should work like:
I create a new instance of my class.
Calling function Load of this class
Funciton Load creates instances of System.Net.WebClient for each url and adds as handler DownloadCompletedHander
Function Load goes calls DownloadStringAsync of each client
Function Load waits in While loop until result collection items count is not as big as number of url on input
If item count in result is same as urls number that means what everything is downloaded, so it breaks loop and returns all requested data
The problem is that it doesn't work, it just endlessly remain in while loop, and as I see using debug collection result is not updated (its size is always 0)
Same time, when I try to asynchronously download it without using my class, everything works fine:
Private Sub Download() 'Handles Me.Load
Dim wc As New System.Net.WebClient
wc.Headers.Add("source", "VaccinationByAgeGroup")
AddHandler wc.DownloadStringCompleted, AddressOf DownloadCompletedHander
wc.DownloadStringAsync(New Uri("https://opendata.digilugu.ee/covid19/vaccination/v3/opendata_covid19_vaccination_agegroup.json"))
End Sub
Could somebody tell me please why it is not working and where is the problem?
The following shows how one can use System.Net.WebClient with Task to download a string (ie: data) from a URL.
Add a project reference (System.Net)
VS 2019:
In VS menu, click Project
Select Add reference...
Select Assemblies
Check System.Net
Click OK
Create a class (name: DownloadedData.vb)
Public Class DownloadedData
Public Property Data As String
Public Property Url As String
End Class
Create a class (name: HelperWebClient.vb)
Public Class HelperWebClient
Public Async Function DownloadDataAsync(urls As List(Of String)) As Task(Of List(Of DownloadedData))
Dim allTasks As List(Of Task) = New List(Of Task)
Dim downloadedDataList As List(Of DownloadedData) = New List(Of DownloadedData)
For i As Integer = 0 To urls.Count - 1
'set value
Dim url As String = urls(i)
Debug.WriteLine(String.Format("[{0}]: Adding {1}", i, url))
Dim t = Task.Run(Async Function()
'create new instance
Dim wc As WebClient = New WebClient()
'await download
Dim result = Await wc.DownloadStringTaskAsync(url)
Debug.WriteLine(url & " download complete")
'ToDo: add desired code
'add
downloadedDataList.Add(New DownloadedData() With {.Url = url, .Data = result})
End Function)
'add
allTasks.Add(t)
Next
For i As Integer = 0 To allTasks.Count - 1
'wait for a task to complete
Dim t = Await Task.WhenAny(allTasks)
'remove from List
allTasks.Remove(t)
'write data to file
'Note: The following is only for testing.
'The index in urls won't necessarily correspond to the filename below
Dim filename As String = System.IO.Path.Combine("C:\Temp", String.Format("CoronavirusData_{0:00}.txt", i))
System.IO.File.WriteAllText(filename, downloadedDataList(i).Data)
Debug.WriteLine($"[{i}]: Filename: {filename}")
Next
Debug.WriteLine("all tasks complete")
Return downloadedDataList
End Function
End Class
Usage:
Private Async Sub btnRun_Click(sender As Object, e As EventArgs) Handles btnRun.Click
Dim helper As HelperWebClient = New HelperWebClient()
Dim urls As List(Of String) = New List(Of String)
urls.Add("https://opendata.digilugu.ee/covid19/vaccination/v3/opendata_covid19_vaccination_total.json")
urls.Add("https://api.covidtracking.com/v2/states.json")
urls.Add("https://covidtrackerapi.bsg.ox.ac.uk/api/v2/stringency/date-range/2020-01-01/2022-03-01")
urls.Add("http://covidsurvey.mit.edu:5000/query?age=20-30&gender=all&country=US&signal=locations_would_attend")
Dim downloadedDataList = Await helper.DownloadDataAsync(urls)
Debug.WriteLine("Complete")
End Sub
Resources:
How do I wait for something to finish in C#?
How should Task.Run call an async method in VB.NET?
VB.net ContinueWith
I'm a beginner using async in VB.NET. I read online help but some things aren't clear.
I try to use tweetinvi library
I got this:
Namespace tweet_invi
Class twitter_call
Public Shared Async Function twitter_get_user_info_from_id(id As Long) As Task
Dim userClient = New TwitterClient(ConfigurationManager.AppSettings("consumerKey"), ConfigurationManager.AppSettings("consumerSecret"), ConfigurationManager.AppSettings("accessToken"), ConfigurationManager.AppSettings("accessTokenSecret"))
Dim tweetinviUser = Await userClient.Users.GetUserAsync(id)
Dim description As String = tweetinviUser.Description
End Function
End Class
End Namespace
And the module from where i would launch this async function
Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
Dim toto As Long = 1311275527223812096
Dim result = tweet_invi.twitter_call.twitter_get_user_info_from_id(toto)
End Sub
My issue: result is a task. How do i have to get the value of description?
You can see it in the code you posted. The second line of that method does it. You use the Await operator to await the completion of the Task.
That said, there is no result to get anyway. If you have a synchronous Sub then that becomes an asynchronous Function that returns a Task. In both cases, there is no actual value to get out of the method. As such, awaiting such a method doesn't return anything. If you have a synchronous Function with a return type of T then that becomes an asynchronous Function that returns a Task(Of T). Awaiting that gives you a result of type T.
If you had these methods:
Private Sub DoSomething()
'...
End Sub
Private Function GetSomething() As SomeType
'...
End Function
then you'd call them like this:
DoSomething()
Dim someValue As SomeType = GetSomething()
If you had these methods:
Private Async Function DoSomethingAsync() As Task
'...
End Function
Private Async Function GetSomethingAsync() As Task(Of SomeType)
'...
End Function
then you'd call them like this:
Await DoSomethingAsync()
Dim someValue As SomeType = Await GetSomethingAsync()
VB actually does support Async Sub but the ONLY time you should ever us it is for event handlers, which MUST be declared Sub, i.e. you cannot handle an event with a Function. Also, any method in which you want to use the Await operator must be declared Async. Together, that means that you must declare the Click event handler of your Button as Async Sub and then you can await an asynchronous method in it:
Private Async Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
Dim toto As Long = 1311275527223812096
Await tweet_invi.twitter_call.twitter_get_user_info_from_id(toto)
End Sub
With regards to the code you posted, that twitter_get_user_info_from_id method is useless. It declares and sets some local variables but does nothing with the data it gets. I suspect that that method should be like this:
Namespace tweet_invi
Class twitter_call
Public Shared Async Function twitter_get_user_info_from_id(id As Long) As Task(Of String)
Dim userClient = New TwitterClient(ConfigurationManager.AppSettings("consumerKey"), ConfigurationManager.AppSettings("consumerSecret"), ConfigurationManager.AppSettings("accessToken"), ConfigurationManager.AppSettings("accessTokenSecret"))
Dim tweetinviUser = Await userClient.Users.GetUserAsync(id)
Dim description As String = tweetinviUser.Description
Return description
End Function
End Class
End Namespace
and then you would call it like this:
Private Async Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
Dim toto As Long = 1311275527223812096
Dim userInfo = Await tweet_invi.twitter_call.twitter_get_user_info_from_id(toto)
'...
End Sub
I'm rewriting hash_hmac code I got on PHP to VB.Net.
I need same result generated both in PHP and VB.Net.
This is hash_hmac code on PHP:
$data = urlencode('2019-07-21T15:30:57.465Z');
$data = '_ts='.$data;
$signatureSecretKey = "secrete";
$hash = hash_hmac('sha256',$data,$signatureSecretKey,true);
$signature = base64_encode($hash);
echo $signature;
The result shows:
upLQYFI3pI2m9Pu5fyiobpvCRhTvRmEyxrVDrdJOYG4=
And here is my code on VB:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim _ts, data, signature, secrete, hash
secrete = "secret"
_ts = DateTime.Now.ToString("2019-07-21T15:30:57.465Z")
data = "_ts=" & HttpUtility.UrlEncode(_ts)
signature = Encrypt(data, secrete)
TextBox1.Text = signature
End Sub
Public Function Encrypt(Content As String, Secret As String) As String
Dim kode As New System.Text.ASCIIEncoding()
Dim getkode As Byte() = kode.GetBytes(Secret)
Dim cont As Byte() = kode.GetBytes(Content)
Dim hmcKu As New HMACSHA256(getkode)
Dim HashCode As Byte() = hmcKu.ComputeHash(cont)
Return Convert.ToBase64String(HashCode)
End Function
Result of my code is:
892q1ArPxIqrX48PQegliVql703V2fcipb5A08F053o=
You can see my VB code generates different result from PHP.
I have tried almost every method I got from internet but the result always different. So, what is equivalent hash_hmac of PHP on VB and what is the right way to make this same result?
Please help?
Use this:
dim hmac as HMACSHA256 = new HMACSHA256(key) ' key = Encoding.ASCII.GetBytes("<secret>")
dim hashValue as byte() = hmac.ComputeHash(Encoding.ASCII.GetBytes("<message>"))
dim result as string = BitConverter.ToString(hashValue).Replace("-", "").ToLower()
hmac.dispose()
I found solution from fb community.
This is exact solution for this cases:
Imports System.IO
Imports System.Text
Imports System.Security.Cryptography
Imports System.Text.RegularExpressions
Public Class Form1
Private Shared DES As New TripleDESCryptoServiceProvider
Private Shared MD5 As New MD5CryptoServiceProvider
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim _ts, data, signature, secrete
secrete = "secret"
_ts = DateTime.Now.ToUniversalTime.ToString("yyyy-MM-dd\THH:mm:ss.fff\Z")
data = "_ts=" & HttpUtility.UrlEncode(_ts)
Dim reg = New Regex("%[a-f0-9]{2}")
data = reg.Replace(data, Function(m) m.Value.ToUpperInvariant())
signature = Encrypt(data, secrete)
TextBox1.Text = signature
End Sub
Public Function Encrypt(Content As String, Secret As String) As String
Try
Dim kode As New System.Text.ASCIIEncoding()
Dim getkode As Byte() = kode.GetBytes(Secret)
Dim cont As Byte() = kode.GetBytes(Content)
Dim hmcKu As New HMACSHA256(getkode)
Dim HashCode As Byte() = hmcKu.ComputeHash(cont)
Return Convert.ToBase64String(HashCode)
Catch ex As Exception
MsgBox(ex.Message)
End Try
End Function
End Class
I am using this snippet to validate my NeverBounce account:
Private Sub btnGo_Click(sender As Object, e As RoutedEventArgs) Handles btnGo.Click
Dim nb As NeverBounceSdk = New NeverBounceSdk("secret_.......")
Dim rsp As AccountInfoResponseModel = nb.Account.Info().Result
txtRes.Text = rsp.ToString
End Sub
But I do not get a response. Based on the NeverBounce documentation, this appears to be the correct approach. Do you know why this doesn't work?
Async methods must be used. See the following.
Public Async Function IsValidAsync() As Task(Of Boolean)
Dim val As SingleResponseModel = Await ValidateAddressAsync()
Return If(val.result = "valid", True, False)
End Function
Private Async Function ValidateAddressAsync() As Task(Of SingleResponseModel)
Dim nb As NeverBounceSdk = New NeverBounceSdk("NeverBounce key")
Dim mdl As New SingleRequestModel With {.email = FullAddress, .timeout = 30}
Return Await nb.Single.Check(mdl)
End Function