Deleting a folder on another thread causes webpage to not update? - asp.net

I have a web app which displays a list of emails that need to be sent for the day. The user can select what emails to send, then click a button to generate them. When they click the Send button, a process gets started on another thread which generates the emails, then cleans up after itself by deleting a temp folder. Once the process is finished, the Repeater is rebound to update the User's view and remove the emails that have just been sent so they don't get sent again.
My problem is that when I delete the temp folder from my 2nd thread, the UI doesn't update with the new Repeater data. It updates correctly if I just delete the files in the folder instead of the folder itself, and it also updates correctly if I run the delete the folder on the original thread instead of the 2nd one.
New Thread code
Dim t as Thread = New Thread(New ThreadStart(AddressOf EmailLetters))
t.Start()
Delete folder code
Dim fs = Server.CreateObject("Scripting.FileSystemObject")
fs.DeleteFolder(Server.MapPath(".") + "\tmpEmailFiles")
Why won't the UI update to show the new repeater values when I delete a folder on another thread?
EDIT
Here is some sample code that shows the problem. Sorry if its a bit messy, but I just needed something simple to help me identify the problem.
When you click the button, a thread gets started and a javascript load script starts executing which does a PostBack every 10 seconds. Each postback checks if the thread is complete and updates the Status label showing the result. If I delete a folder from within the background thread, the final update to the status label never occurs. If I remove the DeleteFolder call, it does.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<%# Import Namespace="System.Threading" %>
<%# Page Language="VB" Debug="true" %>
<%# Implements Interface="System.Web.UI.IPostBackEventHandler" %>
<SCRIPT language="vb" runat="server">
Sub Page_Load(Src As Object, e As EventArgs)
End Sub
Public Sub Test(src as Object, e as EventArgs)
Dim t as Thread = New Thread(New ThreadStart(AddressOf TestWorker))
t.Start()
Session("BackgroundThread") = t
End Sub
Public Sub TestWorker()
' 30 Second Delay
System.Threading.Thread.Sleep(30000)
Dim root as String = Server.MapPath(".")
Dim fs = Server.CreateObject("Scripting.FileSystemObject")
If Not fs.FolderExists(root + "\Test") Then fs.CreateFolder(root + "\Test")
fs.DeleteFolder(root + "\Test")
ErrMsg.Text = "Start: " + DateTime.Now.ToString()
End Sub
Public Sub RaisePostBackEvent(ByVal eventArgument As String) _
Implements IPostBackEventHandler.RaisePostBackEvent
If Session("BackgroundThread") is Nothing Then
Exit Sub
End If
Dim t as Thread = CType(Session("BackgroundThread"), Thread)
If t.ThreadState = ThreadState.Stopped Then
ErrMsg.Text = "Done: " + DateTime.Now.ToString()
Session("BackgroundThread") = Nothing
Else
ErrMsg.Text = "Processing: " + DateTime.Now.ToString() + " - " + t.ThreadState.ToString()
End If
End Sub
</SCRIPT>
<HTML>
<HEAD>
<SCRIPT language="javascript">
//<!--
function onLoad()
{
if(<%= IIF(Session("BackgroundThread") is Nothing, "false", "true") %>)
{
toggleLoading();
setTimeout("<%= Page.ClientScript.GetPostBackEventReference(Me, "") %>", 10000);
}
}
function toggleLoading(){
document.getElementById('imgLoading').style.display = 'block';
setTimeout("document.images['imgLoading'].src='images/loading.gif'", 100);
}
// -->
</SCRIPT>
</HEAD>
<BODY OnLoad="onLoad();">
<FORM runat="server">
<ASP:Button runat="server" Text="Test" OnClick="Test" onClientClick="javascript: toggleLoading();" />
<ASP:Label runat="server" Id="ErrMsg" />
<IMG id="imgLoading" src="images/loading.gif" style="display: none;" />
</FORM>
</BODY>
</HTML>

Figured out my problem. Turns out deleting any folder in the ASP root folder will restart the application and reset all Session variables.
To get around that I added the following to my Application_OnStart method in Global.asax
Dim p As System.Reflection.PropertyInfo = GetType(HttpRuntime).GetProperty("FileChangesMonitor", Reflection.BindingFlags.Public Or Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Static)
Dim o As Object = p.GetValue(Nothing, Nothing)
Dim f As System.Reflection.FieldInfo = o.GetType.GetField("_dirMonSubdirs", Reflection.BindingFlags.Instance Or Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.IgnoreCase)
Dim monitor As Object = f.GetValue(o)
Dim m As System.Reflection.MethodInfo = monitor.GetType.GetMethod("StopMonitoring", Reflection.BindingFlags.Instance Or Reflection.BindingFlags.NonPublic)
m.Invoke(monitor, New Object() {})

The problems is that this is a background thread on the server that is sending the emails finishes but the response has already been sent to the user. When The user opens the page the background thread is started off, and the server returns with the response on the parent thread, the background thread is still running away on the server for sometime AFTER the response has already been sent
To achieve what you want you would need to have the page refreshing on a Javascript or some ajax to poll the server and keep check if this background thread has completed. ie the background thread is started, and the user is told that emails are being proccessed, 2 seconds later the page is automatically refreshed and the user is still told emails are being processed. Finally When all emails are sent the repeater is updated and automatic refresh is disabled

Related

AjaxFileUpload reloads page on upload ONLY for image files

I have been working to debug an intermittent issue I've been experiencing with AJAXFileUpload. I'm moving our code away from another uploader and have worked this into several pages already. I have realized that the uploader is reloading the page after completing the UploadComplete code in the code-behind ONLY for images. I can upload .pdf, .docx with absolutely no problem but as soon as I try .png, .jpeg, .gif, etc the page reloads immediately after the upload.
I can't find anything that would suggest the behavior between these two types of files should be different and I'm assuming I'm missing something.
Here is my upload control:
<ajax:AjaxFileUpload AutoStartUpload="true" OnClientUploadCompleteAll="() => $('#btnSaveThumbnails').toggle()" ClientIDMode="Static" style="max-width:800px;display:none" runat="server" ID="thumbUploader" />
And here is the code-behind for the UploadComplete function:
If Not System.IO.Directory.Exists(tempFilePath) Then
System.IO.Directory.CreateDirectory(tempFilePath)
End If
sender.SaveAs(tempFilePath & e.FileName)
Session("docUploaderFiles") = If(Session("docUploaderFiles"), New List(Of String))
CType(Session("docUploaderFiles"), List(Of String)).Add(e.FileName)
Like I mentioned, these work exactly as expected for non-image file types in my experience.
UPDATE: I have narrowed it down to being caused by the .SaveAs call in the code-behind. Removing just that line causes no page reload.
UPDATE: I have found this issue is specific to my machine. The uploader works on other local environments as well as on our production application. Possibly something to do with my IIS.
I can't see why this would not work.
However, I have OFTEN found that if you don't specify and wire up the CLIENT side events, then the up-loader can barf on you.
So, try this for your test:
<ajaxToolkit:AjaxFileUpload ID="AjaxFileUpload1" runat="server"
OnClientUploadComplete="MyComplete"
OnClientUploadCompleteAll="MyCompleteAll"
OnClientUploadStart="MyStart" AllowedFileTypes="txt,zip,png,jpeg"
OnClientUploadError="MyError" ChunkSize="16384" />
</div>
<br />
<br />
</div>
<script>
function MyStart() {
}
function MyComplete() {
}
function MyCompleteAll() {
}
function MyError(e) {
}
</script>
Note how I added the client side events. I have found in some cases that without the client side events, then problems arise, so I drop in the 4 blank client side events.
EVEN if you not using the client side events, try putting them in as per above. So, I dropped in 4 client side events - without any code in those stubs.
My server side code for this test is this:
Protected Sub AjaxFileUpload1_UploadCompleteAll(sender As Object, e As AjaxControlToolkit.AjaxFileUploadCompleteAllEventArgs) Handles AjaxFileUpload1.UploadCompleteAll
Debug.Print("all done")
End Sub
Protected Sub AjaxFileUpload1_UploadStart(sender As Object, e As AjaxControlToolkit.AjaxFileUploadStartEventArgs) Handles AjaxFileUpload1.UploadStart
Debug.Print("start upload")
End Sub
Protected Sub AjaxFileUpload1_UploadComplete(sender As Object, e As AjaxControlToolkit.AjaxFileUploadEventArgs) Handles AjaxFileUpload1.UploadComplete
Debug.Print("file done")
Debug.Print(e.FileName)
End Sub
Protected Sub AsyncFileUpload1_UploadedFileError(sender As Object, e As AjaxControlToolkit.AsyncFileUploadEventArgs)
Debug.Print(e.StatusMessage)
End Sub
I would also check + clear out non ASCII chars in the file name. I seen funny files - and they blow out a error.
So, I do this:
Dim strCleanFile As String = TrimNonAscii(e.FileName)
and
Public Function TrimNonAscii(ByVal value As String) As String
Dim pattern As String = "[^ -~]+"
Dim reg_exp As Regex = New Regex(pattern)
Return reg_exp.Replace(value, "")
End Function
So I trim out spaces, and also rip out other funny values. (from a Mac or other devices, they actually allow non legal windows chars in the file name, but windows does not.

Event won't fire to dynamically added control

I'm dynamically adding htmlvideo controls to my web form based on the number of videos present on the server folder. This part works fine. All the videos show up and can be played. I add the 'onended' event as an attribute and the function in my code behind, but this event won't fire. I'm aware that since these controls are added after the fact I have to add a listener, but just don't know how.
This is the code that adds the controls
Dim SavePath As String = "e:\ftproot\images\TechNet\"
Dim Directory As New DirectoryInfo(SavePath)
Dim allFiles As IO.FileInfo() = Directory.GetFiles("*.mov")
Dim VidCtr As Integer = 1
For Each singlefile In allFiles
Dim myVid As New HtmlVideo
myVid.Src = "https://www.rawauto.com/images/TechNet/" & singlefile.Name
myVid.Attributes.Add("height", 140)
myVid.Attributes.Add("runat", "server")
myVid.Attributes.Add("type", "video/mp4")
myVid.Attributes.Add("controls", "controls")
myVid.Attributes.Add("onended", "VidPlayed")
myVid.Attributes.Add("id", "Vid" & VidCtr)
Panel1.Controls.Add(myVid)
Dim myLbl As New Label
myLbl.Text = Replace(UCase(singlefile.Name), ".MOV", "")
myLbl.Width = 250
myLbl.CssClass = "VidStyle"
myLbl.Font.Name = "calabri"
myLbl.Font.Bold = True
LPanel.Controls.Add(myLbl)
Next
This is the function I'm trying to fire once the user has finished watching the video:
Protected Sub VidPlayed(sender As Object, e As EventArgs)
Dim Tech As New SqlConnection("server=RAW-OTT; Initial Catalog=TechNet; Integrated Security=True;")
Dim vid As HtmlVideo = sender
Dim vidurl As String = vid.Src
VidName = Replace(vidurl, "https://www.rawauto.com/images/TechNet/", "")
If Len(VidName) > 50 Then
VidName = Mid(VidName, 1, 50)
End If
Dim SqlStr As String = "Select * From TechTube Where Video = '" & VidName & "'"
Dim ttA As New SqlDataAdapter(SqlStr, Tech)
Dim ttT As New DataTable
ttA.Fill(ttT)
If ttT.Rows.Count = 0 Then
SqlStr = "Insert Into TechTube Values ('" & VidName & "', 1, 0)"
Dim tCmd As New SqlCommand(SqlStr, Tech)
Tech.Open()
tCmd.ExecuteNonQuery()
Tech.Close()
Else
SqlStr = "Update TechTube Set Hits = Hits + 1 Where Video = '" & VidName & "'"
Dim tCmd As New SqlCommand(SqlStr, Tech)
Tech.Open()
tCmd.ExecuteNonQuery()
Tech.Close()
End If
RateLabel.Visible = True
RatingBox.Visible = True
End Sub
This is an old ViewState problem for any dynamic control, and has nothing specific to do with video.
Remember, every PostBack rebuilds the entire page from scratch, including your dynamic controls. If you still want to see these controls after a postback (which includes all server events), you must re-add them to the page. Additionally, you need a control's ViewState restored if you want an event fired for the control during this PostBack, and for the ViewState to restore the control must be added back to the reconstructed page before the Page_Load event runs. Page_Init or Page_PreInit can work well for this.
Finally, consider the performance implications here. Do you really want to rebuild the entire page on every user interaction, or is it perhaps time to learn to use javascript to process these things, with maybe a web api that only has to receive a javascript request without causing an entire page cycle both on your server and in the user's browser?
Just a few of the many other times this has been asked and answered:
Dynamic Event Handler not Firing
dynamically added buttons not firing click event c#
dynamically created button click event not firing
Dynamically Added DropDownlists Are Not Firing SelectedIndexChanged Event
ASP.NET: Viewstate and programmatically adding user controls
Click events on Array of buttons
VB ASP dynamic button click event not hitting handler event

Changes to Site.Master page do not persist for a session

Good Afternoon.
I'm working with the following web pages using ms vwd 2010 express:
Site.Master/Site.Master.vb, Login.aspx/Login.aspx.vb
The Site.Master has the following:
<div class="loginDisplay">
<asp:Label ID="WelcomeLabel" runat="server" Text=""></asp:Label>
<asp:HyperLink ID="LogHyperlink" navigateurl="~/Account/Login.aspx" runat="server">Log In</asp:HyperLink>
</div>
I have the following code in the Login.aspx.vb program:
Dim WelcomeLabel As New Label
WelcomeLabel = CType(Master.FindControl("WelcomeLabel"), Label)
WelcomeLabel.Text = "Welcome " & OLEdr.Item("ho1FirstName")
Dim LogHyperlink As New HyperLink
LogHyperlink = CType(Master.FindControl("LogHyperlink"), HyperLink)
LogHyperlink.Text = "Log Out"
LogHyperlink.NavigateUrl = "Exit.aspx"
When a user logs in successfully the LogHyperlink is changed from Log In to
Log Out and the WelcomeLabel contains the text "Welcome " and person's first name. This all works fine.
However, the code only works for the Login.asp page. When I navigate to another
page, say About.aspx (which also uses the Site.Master), the Site.Master
page is back to the orginal and I have lost the changes the code made.
How can I make the changes persist for the session across all the
web pages? All the web pages use the Site.Master.
Thank you.
tfj
Have a look at the LoginView control. It seems you're trying to implement exactly what that control is for. It allows you to display different information depending on whether a user is logged in or not.
It is doable ( although I don't recommend it). In Login.aspx.vb add a line to save the user name in session:
Session("LoggedInUser") = OLEdr.Item("ho1FirstName").ToString()
Dim WelcomeLabel As New Label
WelcomeLabel = CType(Master.FindControl("WelcomeLabel"), Label)
WelcomeLabel.Text = "Welcome " & OLEdr.Item("ho1FirstName")
Dim LogHyperlink As New HyperLink
LogHyperlink = CType(Master.FindControl("LogHyperlink"), HyperLink)
LogHyperlink.Text = "Log Out"
LogHyperlink.NavigateUrl = "Exit.aspx"
In Site.Master.vb load the username from Session in Page_Load:
Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Load
If Not Session("LoggedInUser") Is Nothing Then
WelcomeLabel.Text = Session("LoggedInUser").ToString()
LogHyperlink.Text = "Log Out"
LogHyperlink.NavigateUrl = "Exit.aspx"
Else
LogHyperlink.Text = "Log In"
LogHyperlink.NavigateUrl = "~/Account/Login.aspx"
WelcomeLabel.Text = ""
End If
End Sub

How to call a javascript function from asp.net Sub functions?

So after extensive search, I found that in order to trigger a javasript function, we could do this:
<script>
function foobar()
{
alert("foobar");
}
</script>
Dim strScript As String = "<script language='javascript' id='myClientScript'>foobar();</script>"
Page.RegisterStartupScript(“callTest”,strScript)
However, the Page.RegisterStartupScript seem to be working only under the Page_load function....
When I put it in a Sub, like this:
Sub Test
Dim strScript As String = "<script language='javascript' id='myClientScript'>foobar();</script>"
Page.RegisterStartupScript(“callTest”,strScript)
End Sub
This won't work. As I link the above function to an asp button. I triggered the button, but nothing happens. So is there anyway to trigger javaScript function conditionally from asp.net? From a Sub function instead of on every page_load?
Thanks!!
You should represent this line
Dim strScript As String = "<script language='javascript' id='myClientScript'>foobar();</script>"
as
Dim strScript As String
strScript = "<script language='javascript' id='myClientScript'>foobar();</script>"
If you want to call javascript for asp.net button click, you can use OnClientClick attribute with a client side function OnClientClick="buttonClick()":-
<asp:button id="Button1"
runat="server"
OnClientClick="buttonClick()"
Text="Click!" />
The javascript injected using the RegisterStartUpscript method is executed when the page is first loaded. Try using the RegisterClientScriptBlock method when you need to inject and execute javascript after the
Type t = this.GetType();
if (!ClientScript.IsClientScriptBlockRegistered(t, "myClientScript"))
{
ClientScript.RegisterClientScriptBlock(t,"myClientScript", sb.ToString());
}
Not sure if it will work inside an updatepanel though
Below is the JavaScript function I am using and have used on numerous web applications where I work. On this specific example, I am comparing the clients IP address to a predetermined IP address on our network, and if the clients IP doesn't match, I display a message box that then informs the user that their specific computer terminal is not permitted to make these specific requests.
Dim ipAdd as string = nothing
ipAdd = Request.ServerVariables.Item("REMOTE_ADDR")
If ipAdd <> "###.###.###.###" then
ClientScript.RegisterClientScriptBlock(Page.GetType, "Script", "<script language='javascript'>alert('This terminal is not permitted to submit requests.');</script>")
End If

How can I make a variable static (or "global") in Classic ASP?

I want to make my variable static or "global" - so the same effect as static in .NET; every session that accesses it gets the same result, and if one session modifies it it affects everyone else too.
How can I achieve this in Classic ASP?
If you want to have a variable that is accessible application wide, you can use the application object. Be sure to use Application.Lock/Unlock to prevent any problems.
Application.Lock
Application("MyVariable") = "SomeValue"
Application.Unlock
using a session variable
Session("myVariableName") = "my new value"
the scope will be the user...
if you want to wide the scope to ALL users that are in the website, then you use an Application variable
Application("myVariableName") = "my new value"
you can reset or handle this in the global.asa file as well
This is a common thing to do:
global.asa file:
<script language="vbscript" runat="server">
Sub Application_OnStart
Application("visitors") = 0
End Sub
Sub Session_OnStart
Application.Lock
Application("visitors") = Application("visitors") + 1
Application.UnLock
End Sub
Sub Session_OnEnd
Application.Lock
Application("visitors") = Application("visitors") - 1
Application.UnLock
End Sub
</script>
default.asp file:
<html>
<head>
</head>
<body>
<p>There are <%response.write(Application("visitors"))%> online now!</p>
</body>
</html>

Resources