WebRequest Async CallBack Code only gets call the first time - asp.net

I am an absolute beginner on ASP.net (VB.) Please pardon me if the question is too obvious for the experienced members.
I tried to make a simple WebRequest in the async mode in case the target URL takes long to provide the data. In my code below, I just want to see if the callback block (RespCallback) is called correctly every time. If all goes well, lblResult should have the string '123' appended to it every time I click the button which calls the 'GetData' sub.
However, the lblResult only shows 123 after the first click. After the subsequent click, the lblResult only gets appended with '12', as if RespCallback is never called. When I tried to debug this in Visual Studio, the execution actually stepped right into the RespCallback part and the lblResult.Text watch actually shows '123123' but the resulting Web page always shows only '12312'
I am sure I am missing something basic here, but I just don't know what. I was even guessing that it has to do with browser cache (hence the result changes for the second time) but I don't know how to fix that either.
Can someone please help? Thanks in advance.
Jim
Dim myWebRequest As WebRequest
Public Shared allDone As New ManualResetEvent(False)
Private Shared BUFFER_SIZE As Integer = 1024
Public Class RequestState
' This class stores the state of the request
Private Shared BUFFER_SIZE As Integer = 1024
Public requestData As StringBuilder
Public bufferRead() As Byte
Public request As WebRequest
Public response As WebResponse
Public responseStream As Stream
Public Sub New()
bufferRead = New Byte(BUFFER_SIZE) {}
requestData = New StringBuilder("")
request = Nothing
responseStream = Nothing
End Sub ' New
End Class ' RequestState
Public Sub GetData(Sender As Object, Args As System.EventArgs)
lblResult.Text += "1"
myWebRequest = WebRequest.Create(dataURL)
Dim myRequestState As New RequestState()
myRequestState.request = myWebRequest
' Start the asynchronous request.
Dim asyncResult As IAsyncResult = CType(myWebRequest.BeginGetResponse(AddressOf RespCallback, myRequestState), IAsyncResult)
lblResult.Text += "2"
allDone.WaitOne()
End Sub
Private Sub RespCallback(asynchronousResult As IAsyncResult)
lblResult.Text += "3"
allDone.Set()
End Sub

I don't know VB so it's hard to read for me but I'm suspecting GetData is your onClick handler.
First thing that is not right is that you have Shared members. Why your reset event is Shared? It makes all requests use the same object.
Basically Your code with ManualResetEvent won't work because after first allDone.Set(), your object remains set (as long as web application lives). To get "123" every time you should add allDone.Reset() after allDone.WaitOne().
In Your situation web request returns to client before RespCallback is called every time except first call (when your reset event is in non-signaled state).
AutoResetEvent resets automatically. That's why it worked.
But! You can't do this this way. Making your ResetEvent Shared you make all request use the same object. When more than one request will be processed by your application at the same time you will get undetermined behavior.
Remove Shared from your code. Than your code will work (but not asynchronously) without allDone.Reset() and without AutoResetEvent. But it will provide known results (not depending on amount of requests).
About asynchronous call (now that we have code "working"). Well. There is no async request to your web page. allDone.WaitOne() waits until your async webRequest finish. So basically you could just as well do synchronous request.
You need a special pattern for asynchronous web pages. You can read how to do this here.
But i'm not sure it's what you wanted. Do you want your request to be called asynchronously so that it will not use server resources or do you want to display some message to the user (like "Data is being downloaded...") while your web page will remain fully responsible?
If it's the second one you should use AJAX functionality (Like UpdatePanel or using JavaScript directly). You can read about it here.

Couple things to check:
If your label is a fixed width, then it's possible the text is being clipped
If you are using an UpdatePanel, you will need to set its mode to 'Conditional' and call Update() on it in the RespCallback callback method so that the UI gets refreshed with the latest label text value.

Related

Send Email without waiting for the action to finish execution after Button click ASP.NET

In short, remaining in the HTTP context, I would like the user, after clicking on an order completion button, not to wait for the mails to be sent before being sent back to a "thak you page".
I saw that HostingEnvironment.QueueBackgroundWorkItem could help me with this but there is always the risk of it being killed by IIS recycling.
What is the best solution to do this?
I know the best would be to develop a separate console solution but it wouldn't be worth it for 3/4 emails, alternatively I could consider speeding it up by making them asynchronous?
Protected Sub btnConcludiOrdine_Click(sender As Object, e As System.EventArgs) Handles btnConcludiOrdine.Click
If IsValidOrder(Me._cart, msg) Then
If Me._cart.SaveOrder(Me._user, Me._orderCode, Me._lang) then
'Update quantity in db
Dim mail As New EmailBLL
mail.SendOrderNotice(Me._cart, Me._lang) '2 Mails
mail.SendProductNotice() '2 Mails
End If
Else
Response.Redirect("*Error URL*")
End If
End Sub
The way you approach this is as suggested – start a task, or so called new processor thread.
So, what you would do is break out the code – this even works if code behind is for a web form.
So, the first step is to move out the “slow” parts, or the parts we want to run separate.
The main issue is that to start/launch/want/desire/achieve a brand new processor thread?
The sub call CAN ONLY PASS ONE “parameter” and the sub can only accept one parameter!!!!!
I note in your case that routine needs two values.
However, that “one parameter” can be a array of “many” values, or even a collection or whatever. In our case we pass the two values.
So just keep in mind that what you call can NOT update or “use” the values of controls on the form – the instance of that form will go out of scope.
But we can of course PASS the values you need. This will allow that routine to run 100% independent of the web form.
I also VERY strong suggest that if you DO place the sub in the same web page code behind? You should/can mark that sub as shared. Doing so will allow the compiler to get mad at you and spit out errors if that routine say tries to use or update a control value on the form.
However, it is MUCH better is to place this sub in a separate standard code module out side of the web forms code behind.
Regardless of above, we can now re-write the code we have as this:
If Me._cart.SaveOrder(Me._user, Me._orderCode, Me._lang) then
Dim myInfo(1) as object
myInfo(0) = me.cart
myInfo(1) = me_._lng
Call MyUpdateQ(myInfo)
End If
' bla bla lba
Shared Sub MyUPdateQ(p() as object)
'Update quantity in db
Dim mail As New EmailBLL
mail.SendOrderNotice(p(0),p(1)
mail.SendProductNotice() '2 Mails
End Sub
Ok, so far, we not achieved much, but we re-writing to accept the ONE array is KEY here.
So, now now make sure the above runs/works and is all happy.
Now, because we moved out the "work load" to that one routine, it is now a simple matter to start a thread.
Now, Our above code becomes this:
Protected Sub btnConcludiOrdine_Click(sender As Object, e As System.EventArgs) Handles btnConcludiOrdine.Click
If IsValidOrder(Me._cart, msg) Then
If Me._cart.SaveOrder(Me._user, Me._orderCode, Me._lang) then
Dim myInfo(1) as object
myInfo(0) = me.cart
myInfo(1) = me_._lng
Dim MyThread As New Thread(New ParameterizedThreadStart(AddressOf MyUpdateQ))
MyThread.Start(myInfo)
End If
Else
Response.Redirect("*Error URL*")
End If
End Sub
Shared Sub MyUPdateQ(p() as object)
'Update quantity in db
Dim mail As New EmailBLL
mail.SendOrderNotice(p(0),p(1)
mail.SendProductNotice() '2 Mails
End Sub
That is it. Now when you click your button it will wait ZERO time, since the long running routine is now going to run 100% as a separate thread. And this will also mean that when the user clicks the button - the page will respond instant and post back to user will be done. So if that thread takes 6 seconds, or even 25 seconds, the user will not notice this delay.
Just push your sending mail logic in Task and if you are not interested in result don't await it. c# syntax
Task.Run(() => SendEmail());

ASP.NET Refresh page on timer; query database in between timer

'I have a solution in my head, but am unsure if it is possible in ASP.NET. I don't have a lot of experience with timers. My idea is as follows:
'Code Behind
'At the end of the timer(lets say 1 minute)
Dim qry = "SELECT ISNULL(Value, '') FROM Database.Schema.Table WHERE
Column = #Something"
'set up sql connection and execute query
Using sqlcon as new SQLConnection() 'Don't focus on this part
Using sqlcmd = New SQLCommand(sqlcon,qry) 'Don't focus on this part either syntax is probably incorrect
sqlcmd.Parameters.Add("Something", VarChar)
sqlcmd.Parameters("Something").Value = "Row Found"
'Refresh page; assign label control on .aspx page to value of returned query
'SafeToString() is a function that will safely return a string whether the value is some string, DBNULL, Nothing, or ''
lblString.Text = SafeToString(sqlcmd.ExecuteScalar())
End Using
End Using
I want to be able to do this while maintaining scrolls position as well. So I don't interrupt the work/browsing the user is doing.
Thank you all for your input!
Look up Page Methods. JS can call a code-behind method which can return a value to the js method which can then update the page. The code behind method cannot access the page directly, only return a value to the js method.
I had never used WebForms (which I assume this question is about) but regardless I think you're approaching the problem in a wrong way. A page refresh is destructive and distracting, when a page is refreshed the browser has to fetch the page again (this includes all assets and scripts) and re-render the page.
Client-side implementation
So the solution must be implemented at the client side and not require a full page refresh, this can be achieved using Javascript and ajax(from Wikipedia) requests.
In short ajax is a mechanism for issuing additional requests to a web server while the page is loaded and running. That way you can fetch new data in the background without interrupting the user.
When the page loads set up a timeout or an interval:
let intervalId = setInterval(updateFromServer, 1000);
This interval will call updateFromServer every 1 second (1000ms).
Define the function updateFromServer:
function updateFromServer() {
let newData = fetchData();
updateData(newData);
}
This function is call by the interval we've set up and does 2 things: it fetches new data from the server and pushes it to the DOM using updateData.
You can use ajax (for example with jquery) to fetch the data:
function fetchData() {
return $.ajax({
type: "GET",
url: remote_url,
async: false
}).responseText;
}
This is where ajax comes in, the above lines use jquery ajax api to send a GET request to the server at the url remote_url. The browser issues the request and waits for the server to respond.
And finally update the page (again I'm using jquery):
function updateData(newData) {
$('#data-el').text(newData)
}
Again we are using jquery here (you can use pure javascript though) to find an element with the ID data-el and setting its content to newData.

Reading from a MemoryStream using async

Im using iTextSharp (not that this is relative to iTextSharp) to generate PDF bytes using code similar to the following:
Function GetFileBytes() as Byte()
Dim result() As Byte
Using ms As New MemoryStream
Dim reader As New PdfReader(pdfForm)
Dim writer As New PdfStamper(reader, ms)
Dim fields As AcroFields = writer.AcroFields
fields.SetField("Customer Name", custInfo.FirstName + " " + custInfo.LastName)
result = ms.GetBuffer
End Using
End Function
I want to make this an asynchronous task instead, returning Task(Of Byte()) instead, but I'm having problems figuring out how to make my memorystream write in an asynchronous mode, and how to return a Task(Of Byte()) in the process. Any tips, suggestions? Thanks!
EDIT 1
I wanted to clarify the need for using a Task and using this code, as it seems some comments so far are along the lines of "why are you doing it this way" and "no just do this instead, and I want to politely say "thanks, but I want to do it this way" :)
Here's my scenario, imagine a website that collects data on a few pages, then presents a review screen, and then gives the user the ability to view a PDF containing that data. On the review screen I need to generate a 1 MB PDF file, upload it to BLOB storage, and have it available when the user chooses to download it.
I want to generate and upload the PDF while the user is reviewing the review screen and not when they choose the "view PDF" button. That code will be done using my example, which is why I'm curious how to create a Task of type Byte(). Any help is always appreciated, thanks again!
if you really want to make is async a simpler approach would be to create a private thread in the class, that thread on page_init will start the process then on Page_LoadComplete the thread would wait using thread.join() and then you would get the function result, but as mentioned the relative speed gain is minimal, consider that threads(or tasks) are really expensive
Option 2
create an ajax request to a generic handler that returns the pdf file data, this way no thread involved and better user experience
<%# WebHandler Language="VB" Class="GetFile" %>
Imports System
Imports System.Web
Imports System.IO
Public Class GetFile : Implements IHttpHandler
Public Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
context.Response.Clear()
If File.Exists(filePath & docName) Then
context.Response.AddHeader("content-disposition", "attachment; filename=" & HttpContext.Current.Server.UrlEncode(docName))
context.Response.ContentType = "text/pdf"
context.Response.WriteFile(filelocation & docName) 'in your case this writes the bites of the generated file, so instead of the path just pass in the data
End If
context.Response.Flush()
context.Response.End()
End Sub
Public ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
Get
Return False
End Get
End Property
End Class
Responding to your edit: this scenario makes sense. Wrap everything in a Task.Run call. Be sure to be thread-safe (don't use the HttpContext in the task for example). Also log errors because they might be bugs.
Note, though, that with ASP.NET background work can be aborted at any time (e.g. when the worker process recycles). You have to tolerate suddenly disappearing background work. In your case the PDF might be missing when the user tries to access it. Also, what if it is still being generated when the user tries to download it. Must solve those issues.
Here's my scenario, imagine a website that collects data on a few pages, then presents a review screen, and then gives the user the ability to view a PDF containing that data. On the review screen I need to generate a 1 MB PDF file, upload it to BLOB storage, and have it available when the user chooses to download it.
Since you refer to "blob storage", I'll assume you're using Azure.
You need a worker role or service to do this reliably. The worker role should just be reading from an Azure queue and generating PDFs.
So, when your web role generates the review screen, before returning the response, it will also place that data into the Azure queue. When your web role is notified that the user accepts the review, then it should (asynchronously) wait until the PDF file is generated.
(Don't use this code below.. see end of "answer" for reasoning)..
Thanks to everyone for the help. In the end I was approaching this wrong. All I needed was to create an Async wrapper function to return my Task(Of Byte())
So if my initial code was this:
Function GetFileBytes() as Byte()
Dim result() As Byte
Using ms As New MemoryStream
Dim reader As New PdfReader(pdfForm)
Dim writer As New PdfStamper(reader, ms)
Dim fields As AcroFields = writer.AcroFields
fields.SetField("Customer Name", custInfo.FirstName + " " + custInfo.LastName)
result = ms.GetBuffer
End Using
End Function
Now, I created a new method called GetFileBytesAsync to return Task(Of Byte()) calling the same GetFileBytes method described above, it's just called using a Task.Factory.StartNew, like so:
Public Shared Async Function GetFileBytesAsync() As Task(Of Byte())
Dim t As Task(Of Byte()) = Task.Factory.StartNew(Function()
Return GetFileBytes()
End Function)
Return Await t
End Function
And in my calling function, I now just use the Result property of my task object to access the byte array. HTH!
EDIT: OK, so as Stephen Cleary aptly pointed out, I'm doing this all wrong, the above code is not recommended and it isn't doing anything asynchronously.. as a matter of fact its creating new threads to create some blocking code. Sorry...
I came across this TechEd talk: http://channel9.msdn.com/Events/TechEd/NorthAmerica/2013/DEV-B337 - it's How to Build ASP.NET Web Applications Using Async. At the 54 Minute Mark it actually indicates that trying to use a "background thread" on an ASP.NET Web Server is wrong, as there are no background threads for use in IIS, and in essence you are taking up threads that could\should be used to respond to web requests.

asp.net async thread with pooling

I have a long running task that I need to implement on a webpage. What I would like to do is run the task on a separate thread, but have a progress bar on the webpage.
I am struggling to find an easy way of doing this. Here is a very simplified example that I want to do this on. Basically, I want the ResetAll() in a thread, and pool the variable y to update the webpage UI.
Can someone help me?
Protected Sub btnReset_Click(sender As Object, e As System.EventArgs) Handles btnResetLowConductor.Click
ResetAll()
End Sub
Private Sub ResetAll()
Dim y As Integer = 0
While y < x
y += 1
Reset()
lblProgress.Text = y & "/" & x
End While
End Sub
Private Sub Reset()
Threading.Thread.Sleep(200)
End Sub
your lblProgress will not update as long as thread is alive. You will get only final value of y & x i.e when thread is dead. You can store the values of Y & X inside a session variable.
Private Sub ResetAll()
Dim y As Integer = 0
While y < x
y += 1
Reset()
Session("CurrentStatus") = y & "/" & x
End While
End Sub
From Your UI you will fire an asynchronous event using PageMethod i.e
function GetCurrentThreadStatus()
{
PageMethods.GetThreadStatus(function(status){
// success
$("span[id*='lblProgress']").text(status);
});
}
Code Behind : C#
[WebMethod]
public static string GetThreadStatus()
{
return (string)Session["CurrentStatus"];
}
As this is a website, you will probably need to use some sort of ajax method to call back to the server periodically. Here is the basic flow of your program as far as I can see it:
User makes initial call
Spin up new thread to run process and return with a 0% status
Periodically, make a call to the web server via ajax (A GetStatus call that will return the percentage of completion)
If the call is complete, update/refresh the page appropriately
If the call is not complete, then use the returned status
Now, I am not as sure about the details given that this is a web page, but you could try to store the status in the session variable (which may or may not be possible once you have already returned) periodically, and then the GetStatus will just read the most current status. If session does not work, then you will need to persist the status another way (db, file, etc).
Just be careful on how often you store the status. Too often and you slow your process down, and too little and you do not give an accurate representation of the status.
Last, if you can upgrade to .NET 4.0, then this becomes even more trivial using the TPL (Task Parallel Library)
You are confused about how web pages work.
Once the thread is fired and you write the response to the client, you won't be able to continue updating the page as the Thread progresses.
One way to do this sort of thing is using Ajax. Ulhas already showed you a way top do this; all you need to do is have that javascript function he wrote call itself until you get a 100% completion. You can do that using a timer.
Okay, with the help of various websites, I managed to do this.
Here is a sample project. Please let me know if there are any problems with this. I'm no expert, so I would like some feedback!
http://www.mediafire.com/?7vn16vp78rave6a

asp.net global synclock object

is there a way in asp.net to make sure that a certain threaded sub is not run twice concurrently, no matter what?
the code i have now is
Public Class CheckClass
ReadOnly Property CheckSessionsLock As Object
Get
If HttpRuntime.Cache("CheckSessionsLock") Is Nothing Then HttpRuntime.Cache("CheckSessionsLock") = New Object
Return HttpRuntime.Cache("CheckSessionsLock")
End Get
End Property
Sub TryThreads()
Dim thread = New Thread(AddressOf TryLock)
thread.Priority = ThreadPriority.Lowest
thread.Start()
End Sub
Sub TryLock()
SyncLock CheckSessionsLock
DoTrace("entered locker")
For x = 0 To 10000
Next
DoTrace("exiting locker")
End SyncLock
DoTrace("exited locker")
End Sub
End Class
if i run this code on every page then several times the code overlaps. the DoTrace function in the code simply writes the message to a table.
the messages in the table should appear in order (entered,exiting,exited) again and again, but in reality, they don't. i get like entered, exiting,entered,exited,exiting...
this means that the synclock is not complete. is that true?
if so, how can we implement a complete synclock on a block of code, across requests and across sessions?
EDIT: i need this lock, as the real code will be sending emails, according to a list of mailing types in a db. after each mailing type is sent, its marked, then it continues with the next mailing. i cant have in middle of processing, another thread should see this mailing as unprocessed.
please advise
Rather than using the HttpRuntime Cache have you considered using a static variable?
Just as a note (it might be helpful to explain why you want this functionality) your website is not going to be very scalable if this can only be run once at a time.
In C# (sorry, don't know VB syntax) I use this:
private static readonly object Padlock = new object();
It's a field, not a property,
It's static (in VB, that's "shared" if I'm not mistaken) so it's the same throughout the entire application
It's initialised once as soon as you use this class, not when you explicitly use the field.
With your property/cache version, you could have two threads trying to get the lock-object and each creating a different one:
Thread 1 checks the cache and doesn't find the object
Thread 1 is parked
Thread 2 checks the cache, doesn't find the object
Thread 2 creates the object and caches it, retrieves it again and returns from the property
Thread 1 resumes
Thread 1 creates a new object and caches it, retrieves it again and returns a different lock object than thread 2 uses
Any further threads will use the lock object of thread 1

Resources