Having problem opening/writing to a text file in ASP.NET - asp.net

I want to write some stats to a text file every time a person loads a page. But every once in awhile I am getting at 'Could Not Open File, Already in use' type of error. I can not 100% replicate this error it is very erratic. My code is
Public Sub WriteStats(ByVal ad_id As Integer)
Dim ad_date As String = Now.Year & Now.Month
Dim FILENAME As String = Server.MapPath("text/BoxedAds.txt")
Dim objStreamWriter As StreamWriter
objStreamWriter = File.AppendText(FILENAME)
objStreamWriter.WriteLine(ad_id & ";" & ad_date)
objStreamWriter.Close()
End Sub
My question is, how can I lock and unlock the file so I stop getting the erratic errors?
Thanks

If two or more requests hit your web server at roughly the same time, they will all try to open the same file. You will need to create unique file names for each request.

Public Sub WriteStats(ByVal ad_id As Integer)
Dim ad_date As String = Now.Year & Now.Month
Dim FILENAME As String = Server.MapPath("text/BoxedAds.txt")
Dim index As Integer
Using fs As New IO.FileStream(FILENAME, IO.FileMode.Append, IO.FileAccess.Write, IO.FileShare.ReadWrite), _
tl As New TextWriterTraceListener(fs)
index = Trace.Listeners.Add(tl)
Trace.WriteLine(ad_id & ";" & ad_date)
Trace.Listeners(index).Flush()
Trace.Flush()
End Using
Trace.Listeners.RemoveAt(index)
End Sub
Three important things here:
Use of IO.FileShare.ReadWrite to allow multiple writers on the file at once.
The Using statement to make sure the stream is closed immediately, even if an exception occurs. This will minimize collisions
The TextWriterTraceListener will create a temp file for your if it can't open the file you request, to make sure the message isn't lost.

You will have to handle the exception and build some handling to re-try writing to the file after a short random interval.
If you get too much contention then it might make more sense to log it to a table in a database and create a process to export to a file (if its still needed)

I haven't had any trouble with short info using:
File.AppendAllText(path, info);
Regarding the comment on it causing locks, from reflector it uses the same options explained very well by Joel. It does not use the trace writer, so it won't output to a temp file in the case of high load / large content causing trouble.
If the info is large, you really want separate files. For high load, I would go with Joel's suggestion and create a temp file, which can be alternatively done by catching the exception on File.AppendAllText, and using the same File.AppeandAllText with a unique filename.

Related

VB.NET Directory.GetFiles Sort by Date

I have a directory of Xml files. How do I grab only the files that have been created in the last 30 days and sort them in ascending order? I am not bound to using Directory.GetFiles if there is a more efficient solution.
I then create and bind a list of files to a gridView in which I need both the File Name and Path. I display the File Name and use the Path value in the RowDataBound event to build a HyperLink to the file.
Thanks! \m/ \m/
Dim filePaths() As String = Directory.GetFiles("C:\XmlFiles\")
Dim files As List(Of ListItem) = New List(Of ListItem)
For Each filePath As String In filePaths
files.Add(New ListItem(Path.GetFileName(filePath), filePath))
Next
gv.DataSource = files
gv.DataBind()
We can do a little better. Just getting the names and then sorting by time afterwards requires separate trips out the hard drive for both attributes, which is slow. By switching to DirectoryInfo, we can reduce this to one trip to disk per file:
Dim filesByDate = DirectoryInfo.EnumerateFiles("C:\XmlFiles\").
Where(Function(f) f.CreationTime > DateTime.Today.AddDays(-30)).
OrderBy(Function(f) f.CreationTime).
Select(Function(f) f.Name)
gv.DataSource = filesByDate
gv.DataBind()
Not that the difference is likely to drive your program's performance, but it's always nice to reduce disk I/O :)
This is also a case where it might be better to use GetX instead of EnumerateX. Generally, you want to prefer anything that will "enumerate" vs "get", because of the reduced memory use and the ability to start processing as soon as the first item is available, instead of waiting until the last item is available. In this case, though, there's a chance that you can trade memory use for disk I/O. I'm not familiar with the internal implementation of the methods, but it may be possible for GetFileSystemInfos() to get the information for all of the files in one or a few trips to disk, and that would be a big win. But again... I'm not familiar with the details here; you'd have to test for yourself which is better in your situation.
You can use LINQ:
Dim filePathsSortedByDate = From f In Directory.EnumerateFiles("C:\XmlFiles\")
let fileCreationTime = File.GetCreationTime(f)
Where (Date.Today - fileCreationTime.Date).Days <= 30
Order By fileCreationTime
Select New ListItem(Path.GetFileName(f), f)
Dim files As List(Of ListItem) = filePathsSortedByDate.ToList()

How to get PID (Process ID) of Word instance or Hwnd property?

I'm looking for a better way of coding for proper closing of WINWORD.EXE or word process in my application by getting its PID (Process ID) just like getting the PID in Excel.
This is my sample of getting the PID of an Excel using Hwnd property and GetProcessID class.
Imports System.Diagnostics
Private Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hWnd As Integer, ByRef lpdwProcessId As IntPtr) As IntPtr
Public Sub CreateNewFromTemplate_Excel()
' Create Excel Application, Workbook, and WorkSheets
Dim xlExcel As Excel.Application = New Excel.Application
'Getting the process ID of excel application
Dim processId As IntPtr = Nothing
GetWindowThreadProcessId(xlExcel.Hwnd, processId)
Dim excelProcess As Process = Process.GetProcessById(processId.ToInt32())
Dim blError As Boolean = False
Try
[Perform Excel generation here.]
' Make sure all objects are disposed
xlBook.Close()
xlExcel.Quit()
Catch ex As Exception
blError = True
If Not excelProcess.HasExited Then excelProcess.Kill() ' End the specific Excel Process ID if not yet closed.
Throw
Finally
If Not blError Then
If Not excelProcess.HasExited Then excelProcess.Kill() ' End the specific Excel Process ID if not yet closed.
End If
End Try
End Sub
This is my code for closing the Word Process/WINWORD.EXE but unfortunately, it's not working on the server side so I'm looking for a way and code to get the Word Process ID to properly close it.
Marshal.ReleaseComObject(rng)
Dim strFileName As String = comm.GenerateFilename(strUser_Id, strVPN) & ".docx"
' Save the document.
Dim filename As Object = Path.GetFullPath(strNewFilePath & strFileName)
newDoc.SaveAs(FileName:=filename)
' Close.
Dim save_changes As Object = False
newDoc.Close(save_changes)
WordApp.Quit(save_changes)
Marshal.ReleaseComObject(newDoc)
Marshal.ReleaseComObject(WordApp)
rng = Nothing
newDoc = Nothing
WordApp = Nothing
' Let GC know about it
GC.Collect()
GC.WaitForPendingFinalizers()
GC.Collect()
GC.WaitForPendingFinalizers()
' save the file to database
SaveFormat(filename, strUser_Id, strFamilyId.Trim, strVPN.Trim, DateTime.Now.ToString(), "application/vnd.ms-word", br_id, "doc")
If File.Exists(filename) Then
File.Delete(filename)
End If
Can anyone suggest what's the best way or the best practice for coding on how to get the PID or Hwnd property of Word instance. Thanks :)
What you are trying to do is probably not going to work as reliably as expected. I would strongly suggest that you switch to OpenXML or some other library if you need to process documents and spreadsheets from Server side.
I tend to use EPPlus which works very well and is almost the same code as VBA/Interop. http://epplus.codeplex.com/
I would read the following warnings and articles from Microsoft before proceeding:
All current versions of Microsoft Office were designed, tested, and configured to run as end-user products on a client workstation. They assume an interactive desktop and user profile. They do not provide the level of reentrancy or security that is necessary to meet the needs of server-side components that are designed to run unattended.
Microsoft does not currently recommend, and does not support, Automation of Microsoft Office applications from any unattended, non-interactive client application or component (including ASP, ASP.NET, DCOM, and NT Services), because Office may exhibit unstable behavior and/or deadlock when Office is run in this environment.
https://support.microsoft.com/en-us/help/257757/considerations-for-server-side-automation-of-office

DotNetZip download works in one site, not another

EDIT - RESOLVED: the difference was that in the "main" case the download was initiated via a callback cycle, and in the "test" case it was initiated through a server side button click function. My guess is that the download request and the callback cycle interfered with each other, both stopping the download and causing the page to become inactive (as described below). When I rewired the download on the main page to start with a submit instead of a callback, it did initiate the download.
This is in VS2013 Ultimate, Win7Pro, VB.Net, websites (not projects),IISExpress.
I built a test site to develop functionality for creating OpenXML PPTX and XLSX memorystreams and zipping and downloading them using DotNetZip. Got it to work fine. I then merged all that code into my "main" site. Both sites are on the same machine; I can run the test site and the main site at the same time. The main site processing is somewhat more complicated, but only in terms of accessing and downloading more files.
However, the Zip and Download function (below) works fine in the test site, but the exact same code doesn't work in the main site (with or without the test site up and running).
There's an error trap (see below) around the Zip.Save function where the download occurs but no error shows up.
Same overall behavior in Chrome, Firefox and IE11.
One peculiarity that might be a clue is that when the main site download fails, the server side functionality "goes dead". Local JS functions work, but the app doesn't respond to callbacks. When I do an F5 on the browser it works again.
I did a refresh on the DotNetZip package in the main site. The Zip object appears to be working properly, because it generates an error on duplicate file names.
I thought it might be the download function as written, however, it works in the test site. Also, another piece of the main site does a non-zipped download of a memory stream (included as the second code block below) and that works fine.
I thought it might be the data. So I kludged the main site to access, convert to memorystream and download the same file that the is accessed and downloaded in the test site. Still the main site download doesn't work.
When I compare the watch values on the Zip object in the two sites, they look identical. The length of the wrkFS.ContentStream is identical in both cases. The file names are different, however, they are:
Test_2EFVG1THK5.xlsx (main)
6-18_12-46-28_0.xlsx (test)
which are both legal file names.
EDIT: I saved the zip file to disk from the main program, instead of trying to download it, using this:
wrkFilePath = "D:\filepath\test.zip"
wrkZip.Save(wrkFilePath)
And it worked fine. So that possibly isolates the problem to this statement
wrkZip.Save(context.Response.OutputStream)
EDIT: Base on help I received here:
Convert DotNetZip ZipFile to byte array
I used this construct:
Dim ms as New MemoryStream
wrkZip.Save(ms)
wrkBytes = ms.ToArray()
context.Response.BinaryWrite(wrkByteAr)
to get around the ZipFile.Save(to context), and that didn't work either; no download, no error message, and page goes dead. However, at least I can now assume it's not a problem with the ZipFile.Save.
At this point I'm out of ways to diagnose the problem.
Any suggestions would be appreciated.
Here is the code that works in the test site but not in the main site.
Public Sub ZipAndDownloadMemoryStreams(ByVal context As HttpContext) _
Implements IHttpHandler.ProcessRequest
Dim rtn As String = ""
Try
Dim wrkAr As ArrayList
wrkAr = SC.ContentArrayForDownLoad
If wrkAr.Count = 0 Then
Dim wrkStop As Integer = 0
Exit Sub
End If
Dim wrkFS As ZipDownloadContentPair
Using wrkZip As New ZipFile
'----- create zip, add memory stream----------
For n As Integer = 0 To wrkAr.Count - 1
wrkFS = wrkAr(n)
wrkZip.AddEntry(wrkFS.FileName, wrkFS.ContentStream)
Next
context.Response.Clear()
context.Response.ContentType = "application/force-download"
context.Response.AddHeader( _
"content-disposition", _
"attachment; filename=" & "_XYZ_Export.zip")
'---- save context (initiate download)-----
wrkZip.Save(context.Response.OutputStream)
wrkZip.Dispose()
End Using
Catch ex As Exception
Dim exmsg As String = ex.Message
Dim wrkStop As String = ""
End Try
End Sub
Below is the non-zip download function that works fine in the main site.
It might be possible to convert the Zip content to a byte array and try the download that way, however, I'm not sure how that would work.
(SEE EDIT NOTE ABOVE --- I implemented a version of the below, i.e. try to download byte array instead of directly ZipFile.Save(), however, it didn't help; still doesn't download, and still doesn't give any error message)
Public Sub DownloadEncryptedMemoryStream(ByVal context As HttpContext) _
Implements IHttpHandler.ProcessRequest
Dim wrkMemoryStream As New System.IO.MemoryStream()
wrkMemoryStream = SC.ContentForDownload
Dim wrkFileName As String = SC.ExportEncryptedFileName
wrkMemoryStream.Position = 0
Dim wrkBytesInStream As Byte() = New Byte(wrkMemoryStream.Length - 1) {}
wrkMemoryStream.Read(wrkBytesInStream, 0, CInt(wrkMemoryStream.Length))
Dim wrkStr As String = ""
wrkStr = Encoding.UTF8.GetString(wrkMemoryStream.ToArray())
wrkMemoryStream.Close()
context.Response.Clear()
context.Response.ContentType = "application/force-download"
context.Response.AddHeader("content-disposition", "attachment; filename=" & wrkFileName)
context.Response.BinaryWrite(wrkBytesInStream)
wrkBytesInStream = Nothing
context.Response.End()
(Per the note now at the top of the question): The difference was that in the "main" case the download was initiated via a callback cycle, and in the "test" case it was initiated through a server side button click function. My guess is that the download request and the callback cycle interfered with each other, both stopping the download and causing the page to become inactive (as described below). When I rewired the download on the main page to start with a submit instead of a callback, it did initiate the download.

Error when csv data contains comma

I am using ASP.NET VB.NET, CSV File upload and FileHelpers, ASP.NET control is File Upload
Code to read CSV File
Using sr As New StreamReader(FileUpload1.PostedFile.InputStream)
Dim engine As New FileHelperEngine(GetType([MyClass]))
For Each entry As [MyClass] In engine.ReadStream(sr)
Next
End Using
FileHelpers
<DelimitedRecord(",")> _
<IgnoreFirst(1)> _
<IgnoreEmptyLines> _
Class [MyClass]
<FieldTrim(TrimMode.Both)> _
Public Prop1 As String
<FieldTrim(TrimMode.Both)> _
Public Prop2 As String
End Class
Question
There is some comma in data and due to that It is crashing.
VB.net provides exactly what you are looking for in Microsoft.VisualBasic.FileIO. You can use it in C# however, as shown below:
TextFieldParser parser = new TextFieldParser(pFileName);
parser.SetDelimiters(",");
parser.TextFieldType = FieldType.Delimited;
while (!parser.EndOfData)
{
string current = parser.ReadFields();
}
Edit
Just noticed you are using VB - basic premise remains the same, but don't know enough about VB.net to rewrite it. It will work though, as I had the exact same problem and it fixed it.
If your CSV looks like:
value1,value2,valu,e3
it always will be 4 columns not 3
But if CSV looks like:
value1,value2,"valu,e3"
I recomended use regexp split like:
var result = Regex.Split(csvline, ",(?=(?:[^']*'[^']*')*[^']*$)");
In this case,the file has errors and it's pretty much impossible to know what's wrong with an individual line except to say "there are too many fields when split by comma". There is no sane way your code can fix this error, you have to throw it back to the user who is uploading the broken file.
You can set the FileHelper's ErrorMode setting to SaveAndContinue (see here) so that it doesn't throw on error, and after your loop check the Errors property and if it's not empty, report the problem to the user, listing out each ErrorInfo in the Errors collection.

FileUpload control in asp/VB.net cannot access the file because it is being used by another process

Error message:
The process cannot access the file 'C:\SampleProjectName\mytestcsv.csv' because it is being used by another process.
I am trying to read numerous files (CSV, XML, HTML) in asp/VB.net using the fileupload (file upload) control.
I'm saving the file using Server.MapPath so I can process the file in another procedure. It's very odd, but sometimes I can browse and upload the same file over and over with no issues, but sometimes it immediately fails.
I've found that I can ultimately kill the WebDev.WebServer40.exe it releases whatever lock is present. This is annoying, but fine for my debugging... but unacceptable for endusers.
My fileupload code:
If fuImport.HasFile Then
If (System.IO.File.Exists(Server.MapPath("myhtml.html"))) Then
System.IO.File.Delete(Server.MapPath("myhtml.html"))
End If
Dim dtFromHTML As New Data.DataTable
Dim dtFromSQL As New Data.DataTable
Try
fuImport.SaveAs(Server.MapPath("mytestcsv.csv"))
'Process data here
ProcessCSVData(Server.MapPath("mytestcsv.csv"))
Catch ex As Exception
Response.Write("error: " & ex.Message)
Finally
fuImport.PostedFile.InputStream.Flush()
fuImport.PostedFile.InputStream.Close()
fuImport.FileContent.Dispose()
End Try
'Other things happen here
Else
Response.Write("no file...")
End If
Any ideas would be appreciated.
Use FileShare.Read to read a file even if it is opened exclusively by an another process.
You may not be releasing the file in your code for access. You should use the keyword "using".
Read this post:
The process cannot access the file because it is being used by another process - using static class
and this:
http://msdn.microsoft.com/en-us/library/htd05whh.aspx
I am not a VB.NET coder but try something along the lines of:
Using {fuImport.SaveAs(Server.MapPath("mytestcsv.csv")) }
ProcessCSVData(Server.MapPath("mytestcsv.csv"))
End Using
The next procedure that uses file...
Try closing input stream before accessing the file:
UPDATED To use temp filename
Dim sTempName = Path.GetRandomFileName
fuImport.SaveAs(Server.MapPath(sTempName))
'close before accessing saved file
fuImport.PostedFile.InputStream.Close()
'Process data here
ProcessCSVData(Server.MapPath(sTempName)

Resources