Simulate Windows Service with ASP.NET - asp.net

I have small web app that generate PDF files as a report. I'm trying to delete those generated PDF files after 10 sec that they are generated. What I want to do is to read a folder with PDF files every 10 sec, and delete all the PDF files inside that folder.
I read this post of Easy Background Tasks in ASP.NET. The following code is the VB version.
Protected Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
AddTask("DoStuff", 10)
End Sub
Private Sub AddTask(ByVal name As String, ByVal seconds As Integer)
OnCacheRemove = New CacheItemRemovedCallback(CacheItemRemoved)
HttpRuntime.Cache.Insert(name, seconds, Nothing, DateTime.Now.AddSeconds(seconds), Cache.NoSlidingExpiration, CacheItemPriority.NotRemovable, _
OnCacheRemove)
End Sub
Public Sub CacheItemRemoved(ByVal k As String, ByVal v As Object, ByVal r As CacheItemRemovedReason)
' do stuff here if it matches our taskname, like WebRequest
DeletePDFilesInFoler()
' re-add our task so it recurs
AddTask(k, Convert.ToInt32(v))
End Sub
But I got this error
Delegate
'System.Web.Caching.CacheItemRemovedCallback'
requires an 'AddressOf' expression or
lambda expression as the only argument
to its constructor.
If this code works, where I should put it. Right, now I'm putting it in the master page. How to get this error out?
Thank you

Rather than deleting them on a schedule, why not look for old PDFs and delete them each time a PDF is generated?
Pseudo-code:
/* Clean up old PDFs */
If Not StaticCleaningUpPDFsNow Then
// No other reports generating simultaneously and trying to delete old PDFs
StaticCleaningUpPDFsNow = True
For each f in (reportfolder\*.pdf)
If f.DateCreated.AddSeconds(10) < Now Then f.Delete
Next
StaticCleaningUpPDFsNow = False
End If
/* Create PDF for the current report */
...
The overhead to look for files in a folder and delete a few is incredibly small, and doing this on demand is a much better use of resources, without the hacks around cache expirations (and the edge cases that can result).

The error is in the error message, you're missing AddressOf. Try this:
OnCacheRemove = New CacheItemRemovedCallback(AddressOf CacheItemRemoved)

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.

How can I store the data in memory and use by the other Button click event to display the data?

Here is the code, but the datatable is NULL in ButtonExport click event, how can i pass the DataTable to Sub ButtonExport_Click ? I dont want to store in Session as the data is too big
Here is the class clsGlobalVarriable
Public Class clsGlobalVariable
Private _gdt As DataTable
Public Property globalDataTable As DataTable
Get
Return _gdt
End Get
Set(ByVal value As DataTable)
_gdt = value
End Set
End Property
End Class
Here is the From frmTest code:
Public Class frmTest
Inherits System.Web.UI.Page
Private gdt As New clsGlobalVariable
Protected Sub ButtonInactivePC_Click(sender As Object, e As EventArgs) Handles ButtonInactivePC.Click
Try
Dim func As New clsFunction
Dim command As String = "Get-ADComputer -Filter { OperatingSystem -NotLike '*Windows Server*'} -Property * | select Name, CanonicalName, operatingSystem, LastLogonDate, Description, whenChanged | Where {($_.LastLogonDate -lt (Get-Date).AddDays(-90)) -and ($_.LastLogonDate -ne $NULL)}"
Dim arr As New ArrayList
arr.Add("Name")
arr.Add("CanonicalName")
arr.Add("operatingSystem")
arr.Add("LastLogonDate")
arr.Add("whenChanged")
arr.Add("Description")
gdt.globalDataTable = func.PSObjectToDataTable(command, arr)
Me.GridView1.DataSource = gdt.globalDataTable
Me.GridView1.DataBind()
Catch ex As Exception
Me.LabelDebug.Text = "Button Click" + ex.Message
End Try
End Sub
Protected Sub ButtonExport_Click(sender As Object, e As EventArgs) Handles ButtonExport.Click
Dim func As New clsFunction
Dim dt As New DataTable
dt = (DirectCast(Me.GridView1.DataSource, DataTable))
Me.LabelDebug.Text = "Global Data Table Count = " & dt.Rows.Count
End Sub
When working with webpages that show data to the user, and the user takes some action on that data you either need to store the data somewhere in their computer, your computer (the server) or rely on the fact that it's still stored in the computer you got it from. As a process you have undertaken:
You generate a grid from querying AD
You send the grid to the customer's computer - so it's stored there as a visual representation (and maybe also ViewState)
It's still stored in AD, where you got it
You could also store it locally on the server somehow - Session, DB, text file, whatever
Decide on which of these to use when the user clicks Export:
Dig it out of the viewstate or other data that was sent to the user - for this you'll have to code things up so it comes back from the user
Get it out of AD again - simple to do; you did it once and sent it to the user in HTML. Getting it again and sending it to the user again this time as a CSV isn't really any different from the first time you did it
Restore it from wherever you kept it on the server
Choose the first if your user is going to modify the data or choose to export only some of it - the data he sends back to you should indicate which bits he wants exporting.
Choose the second option if you want an easy life, and it's just a straight export, no editing or subset of data. Write one method that gets the data out of AD and then use it in either place, one to form HTML/fill a grid, in the other to send a file to the user. Don't get hung up on "well I already got this data once, it's a waste to get it again" - no-one writes a Login Page and thinks "i'll only ever look up a user from the DB once, then get the server to remember the login data forever more and use it next time there is a login request" - they store the data in the db, and look it up every time there is a login. DBs store data and perform the same queries over and over again. This is no different
You probably wouldn't choose the third option, for reasons already mentioned
I decided to use alternative for the Excel Export, i am not going to pass the DataTable, instead i pass the GridView to the Export to Excel function
Add the following sub right after Page_load, this is to avoid the GridView error
Public Overrides Sub VerifyRenderingInServerForm(ByVal control As Control)
End Sub
Here is the Code:
Public Sub ExportFromGridview(ByVal gv As GridView, ByVal response As HttpResponse
response.Clear()
response.Write("<meta http-equiv=Content-Type content=text/html;charset=utf-8>")
response.AddHeader("content-disposition", "attachment;filename=" & Now & ".xls")
response.ContentType = "application/vnd.xls"
Dim stringWrite As System.IO.StringWriter = New System.IO.StringWriter()
Dim htmlWrite As System.Web.UI.HtmlTextWriter = New HtmlTextWriter(stringWrite)
gv.RenderControl(htmlWrite)
response.Write(stringWrite.ToString())
response.End()
End Sub

Why Dev express ReportService Saves classes but loads byte array?

When creating a report designer if a ReportStorageWebExtension is created, it has 2 set data methods and 1 method to get data.
Public Overrides Function GetData(ByVal url As String) As Byte()
' Returns report layout data stored in a Report Storage using the specified URL.
' This method is called only for valid URLs after the IsValidUrl method is called.
Return MyBase.GetData(url)
End Function
Public Overrides Sub SetData(ByVal report As XtraReport, ByVal url As String)
' Stores the specified report to a Report Storage using the specified URL.
' This method is called only after the IsValidUrl and CanSetData methods are called.
MyBase.SetData(report, url)
End Sub
Public Overrides Function SetNewData(ByVal report As XtraReport, ByVal defaultUrl As String) As String
' Stores the specified report using a new URL.
' The IsValidUrl and CanSetData methods are never called before this method.
' You can validate and correct the specified URL directly in the SetNewData method implementation
' and return the resulting URL used to save a report in your storage.
Return MyBase.SetNewData(report, defaultUrl)
End Function
Why does set data get a XtraReport but getting the data as byte()
As explained by Devexpress team in this ticket it is done like that so you can access to the report properties before you save it. and the proper way to save them is as a byte array like this(shown here):
Public Overrides Sub SetData(ByVal report As XtraReport, ByVal url As String)
' Write a report to the storage under the specified URL.
Dim row As DataRow = reportsTable.Rows.Find(Integer.Parse(url))
If row IsNot Nothing Then
Using ms As New MemoryStream()
report.SaveLayoutToXml(ms)
row("LayoutData") = ms.GetBuffer()
End Using
reportsTableAdapter.Update(catalogDataSet)
catalogDataSet.AcceptChanges()
End If
End Sub

Write to a log file asynchronously from asp.net task

Using VB.Net (Framework version 4.5.1)
I have a program that sets up a list of (System.Threading.Tasks.Task) tasks that are executed as follows (only relevant code is shown):
po = New System.Threading.Tasks.ParallelOptions()
po.MaxDegreeOfParallelism = 5
Parallel.ForEach( task_list, po, AddressOf do_work )
The program works fine, but I want to add a log file rather than using just Console.WriteLine()
In the do_work() Sub, I want to write to a log file, for example:
Sub do_work( param As String )
Dim thread_id as String = "Thread ID " & System.Threading.Thread.CurrentThread.ManagedThreadId.ToString() & ": "
joblog_append( thread_id & "Beg" )
joblog_append( thread_id & "param = " & param )
joblog_append( thread_id & "End" )
End Sub
Ideally, I would like to create three functions such as:
joblog_open() to open a log file at the start o the program.
joblog_append() to append to the log file; callable from within any task
joblog_close() to close the log file
What is the correct way to implement such logging when the joblog_append() will be called from multiple tasks being executed on separate threads?
All attempts I have tried so far seem to be hit and miss; sometimes the data is written to the output file, sometimes it is not.
Any advice (or better yet, a code example) would be most appreciated.
Thanks in advance.
I think that the issue you have is due to the fact multiple threads are accessing the file at the same time.
A better approach would be to make sure only one thread access the file at a time. You could use a lock (see this SO) or append the messages to be written to a concurrentQueue of string, then process that queue on another thread.
The joblog_append calls _Logger.Log, which in turns enqueue the message and start a new thread to process the queue.
private _Logger as new Logger
Private Sub joblog_append(Message As String)
_Logger.Log(Message)
End Sub
The logger class performs the following.
Append message to to a concurrent queue
Create and start a task (if no one already running) to write queue content to the file.
Set the task to nothing when completed
In the event the task is already created, the message is enqueued and the While condition in the task itself should take care of any messages added while it's running.
'Missing: Idisposable, Fileaccess.Shared,
'TODO: remove debugger.break
Public class Logger
Public Property Messages As new ConcurrentQueue(Of string)
private _WorkerTask as Task
private event WorkerTaskCompleted
private _Stream as FileStream
private _Writer as StreamWriter
Public sub New()
_Stream = io.file.OpenWrite("Mylog.txt")
_Writer = New StreamWriter(_Stream)
End sub
Public sub Log(Message as string)
Messages.Enqueue(Message)
if _WorkerTask Is Nothing
_WorkerTask = New Task(sub()
While Messages.Any
Dim CurrentMessage as string = ""
if Messages.TryDequeue(CurrentMessage)
_Writer.WriteLine(CurrentMessage)
else
debugger.Break
End If
End While
_Writer.Flush
_Stream.Flush
RaiseEvent WorkerTaskCompleted
End Sub)
_WorkerTask.Start
End If
End sub
Private Sub Logger_WorkerTaskCompleted() Handles Me.WorkerTaskCompleted
_WorkerTask = Nothing
End Sub
End Class
Please note. This is my approach to this problem but I do not have anything similar implemented and tested. Therefore, you will have to make your tests to confirm it is working properly.

Downloading simple text/csv file in Chrome fails

I'm trying to figure out a problem that we're facing with the download and the problem seems to exist only in Chrome and only for txt and csv files. Downloading zip files works just as expected. IE and Firefox works for all scenarios.
For some reason Chrome downloads both txt and csv files without properly setting the name. Files inherit the name of the page which they were downloaded from, and don't have an extension ().
I have a simple Webform with just 3 linkbuttons a fairly straightforward code behind, which I simplified for the sake of this question.
Imports System.IO
Public Class _Default
Inherits Page
Private Const FILE_PATH_CSV As String = "D:\Temp\Download\testFile.csv"
Private Const FILE_PATH_ZIP As String = "D:\Temp\Download\testFile.zip"
Private Const FILE_PATH_TXT As String = "D:\Temp\Download\testFile.txt"
Private Sub btnDownloadCSV_Click(sender As Object, e As EventArgs) Handles btnDownloadCSV.Click
Dim file As New FileInfo(FILE_PATH_CSV)
If Not file.Exists() Then
Throw New Exception("No file")
End If
Try
DownloadFile(file)
Catch ex As Exception
'TODO: Log!
End Try
End Sub
Private Sub btnDownloadTXT_Click(sender As Object, e As EventArgs) Handles btnDownloadTXT.Click
Dim file As New FileInfo(FILE_PATH_TXT)
If Not file.Exists() Then
Throw New Exception("No file")
End If
Try
DownloadFile(file)
Catch ex As Exception
'TODO: Log!
End Try
End Sub
Private Sub btnDownloadZIP_Click(sender As Object, e As EventArgs) Handles btnDownloadZIP.Click
Dim file As New FileInfo(FILE_PATH_ZIP)
If Not file.Exists() Then
Throw New Exception("No file")
End If
Try
DownloadFile(file)
Catch ex As Exception
'TODO: Log!
Finally
With Response
.Flush()
.End()
End With
End Try
End Sub
Private Sub DownloadFile(ByVal file As FileInfo)
With Response
.Clear()
.ClearHeaders()
.ClearContent()
.Buffer = True
Response.ContentType = GetContentType(file)
Response.AddHeader("Content-Disposition:", String.Format("attachment;filename=""{0}""", file.Name.ToString()))
Response.AddHeader("Content-Length", file.Length.ToString())
Response.TransmitFile(file.FullName)
End With
End Sub
Private Function GetContentType(ByVal file As FileInfo)
Select Case file.Extension.ToLower()
Case ".txt", ".csv"
Return "application/octet-stream"
Case ".zip"
Return "application/x-zip-compressed"
Case Else
Return "application/octet-stream"
End Select
End Function
End Class
I've tried switching the content type to 'text/plain' or other options as I searched the web for the answer but had no luck. I feel like I'm missing something, but I can't find anything that answers my question online.
Does anyone have any ideas why this might be happening?
Any help is appreciated.
You need to change your MIME types to the correct values.
Case ".csv"
Return "text/csv"
Case ".txt"
Return "text/plain"
You may also need to change the method of sending the file from
Response.TransmitFile(file.FullName)
to
Response.WriteFile(file.FullName, False)
The documentation on MSDN isn't clear about what the differences are between the two methods. I have never used TransmitFile, but have used WriteFile successfully.
Problem solved. I guess I should be more careful with copy/paste as that caused me 2 days of very frustrated debugging.
Response.AddHeader("Content-Disposition:", String.Format("attachment;filename=""{0}""", file.Name.ToString()))
Should be
Response.AddHeader("Content-Disposition", String.Format("attachment;filename=""{0}""", file.Name.ToString()))
Extra colon in there was not necessary and when I removed it - everything worked!
Thanks to all who helped!

Resources