I am making an application for a 'Ski Resort' where you can add members and search members etc. So far I am having difficulty saving and loading these when the application closes. When I add a member, it saves, and then when I try to add another, the first member will not be saved anymore but the first one will have done.
Here is the code used to Save the members:
public void Save(System.IO.TextWriter textOut)
{
textOut.WriteLine(number);
textOut.WriteLine(name);
textOut.WriteLine(address);
textOut.WriteLine(score);
textOut.Close();
}
public bool Save(string filename)
{
System.IO.TextWriter textOut = null;
try
{
textOut = new System.IO.StreamWriter(filename);
Save(textOut);
}
catch
{
return false;
}
finally
{
if (textOut != null)
{
textOut.Close();
}
}
return true;
}
The StreamWriter constructor you are using will overwrite the file each time. Use the constructor that also takes a boolean so you can make it append to the file. And you don't need the TextWriter.
try
{
using(var textOut = new System.IO.StreamWriter(filename, true))
{
textOut.WriteLine("whatever..");
}
}
Related
I am writing a Asp.Net Core application (with RazerPages) that uploads/downloads files. I have a control that uses AJAX to upload files in chunks. The files are uploaded to a subdirectory on the server. The name of the subdirectory is a guid that gets generated when the page loads.
When I remove a file from my control it sends a command to delete the associated file on the server. The issue is that for particularly large files, the delete seems to take a long time but the GUI isn't waiting for a response (so it thinks the file was already deleted). If I then try to upload the same file again I get a "Access Denied" exception because the file is still being used by another request...
I've tried to use a mutex to lock the subdirectory whenever file IO happens, but for some reason different requests don't seem to use the same mutex. If I use a static singleton mutex, it works, but this means that only one file can be uploaded/deleted at a time for the entire server.
How do I create a mutex for the subdirectory I am currently working with, and have it recognized on multiple requests?
public class FileIOService : IFileService
{
public string RootDiectory { get; set; }
public void CreateDirectory(Guid id)
{
// Create the working directory if it doesn't exist
string path = Path.Combine(RootDiectory, id.ToString());
lock (id.ToString())
{
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
}
}
public void AppendToFile(Guid id, string fileName, Stream content)
{
try
{
CreateDirectory(id);
string fullPath = Path.Combine(RootDiectory, id.ToString(), fileName);
lock (id.ToString())
{
bool newFile = !File.Exists(fullPath);
using (FileStream stream = new FileStream(fullPath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite))
{
using (content)
{
content.CopyTo(stream);
}
}
}
}
catch (IOException ex)
{
throw;
}
}
public void DeleteFile(Guid id, string fileName)
{
string path = Path.Combine(RootDiectory, id.ToString(), fileName);
lock (id.ToString())
{
if (File.Exists(path))
{
File.Delete(path);
}
string dirPath = Path.Combine(RootDiectory, id.ToString());
DirectoryInfo dir = new DirectoryInfo(dirPath);
if (dir.Exists && !dir.GetFiles().Any())
{
Directory.Delete(dirPath, false);
}
}
}
public void DeleteDirectory(Guid id)
{
string path = Path.Combine(RootDiectory, id.ToString());
lock (id.ToString())
{
if (Directory.Exists(path))
{
Directory.Delete(path, true);
}
}
}
}
I ended up not using lock and instead used explicitly created global mutexes. I created a private ProtectWithMutex method that would execute an action within a protected block of code:
/// <summary>
/// Performs the specified action. Only one action with the same Guid may be executing
/// at a time to prevent race-conditions.
/// </summary>
private void ProtectWithMutex(Guid id, Action action)
{
// unique id for global mutex - Global prefix means it is global to the machine
string mutexId = string.Format("Global\\{{{0}}}" ,id);
using (var mutex = new Mutex(false, mutexId, out bool isNew))
{
var hasHandle = false;
try
{
try
{
//change the timeout here if desired.
int timeout = Timeout.Infinite;
hasHandle = mutex.WaitOne(timeout, false);
if (!hasHandle)
{
throw new TimeoutException("A timeout occured waiting for file to become available");
}
}
catch (AbandonedMutexException)
{
hasHandle = true;
}
TryAndRetry(action);
}
finally
{
if (hasHandle)
mutex.ReleaseMutex();
}
}
}
This prevented multiple requests from trying to manipulate the same directory at the same time, but I still had issues where other processes (Windows Explorer, Antivirus, I'm not really sure) were grabbing the file in between requests. To get around this I created a TryAndRetry method that would try to perform the same action over and over until successful (or until it failed too many times):
/// <summary>
/// Trys to perform the specified action a number of times before giving up.
/// </summary>
private void TryAndRetry(Action action)
{
int failedAttempts = 0;
while (true)
{
try
{
action();
break;
}
catch (IOException ex)
{
if (++failedAttempts > RetryCount)
{
throw;
}
Thread.Sleep(RetryInterval);
}
}
}
All I had to do at that point was replace all of my lock blocks with calls to my Protected method:
public void AppendToFile(Guid id, string fileName, Stream content)
{
CreateDirectory(id);
string dirPath = Path.Combine(RootDiectory, id.ToString());
string fullPath = Path.Combine(dirPath, fileName);
ProtectWithMutex(id, () =>
{
using (FileStream stream = new FileStream(fullPath, FileMode.Append, FileAccess.Write, FileShare.None))
{
using (content)
{
content.CopyTo(stream);
}
}
});
}
public void DeleteFile(Guid id, string fileName)
{
string path = Path.Combine(RootDiectory, id.ToString(), fileName);
ProtectWithMutex(id, () =>
{
if (File.Exists(path))
{
File.Delete(path);
}
string dirPath = Path.Combine(RootDiectory, id.ToString());
DirectoryInfo dir = new DirectoryInfo(dirPath);
if (dir.Exists && !dir.GetFiles().Any())
{
Directory.Delete(dirPath, false);
}
});
}
I have an object which is used to access a SQL Server table, do insert / updates.
I have two windows services, which use the same object to read/write to the same table.
To preserve the integrity, I was using Mutex lock and release on this object. But of late getting sync errors (Object synchronization method was called from an unsynchronized block of code). Hence I am adding a bool to identify the caller (In the code below bool isThreadOwnedLockAskingToRelease in 'Release' method). Will this resolve the sync issues and is this the correct way?
Class ST
public ST() //constructor
{
Mutex stBusy = utils.GetMutex(#"ST");
bool lockAcquired = false;
lock (ex)
{
//open connection
// build dataset
// close conn
}
}
// ...
public bool Lock(string nameOfLock)
{
if (stBusy != null)
{
if (stBusy.WaitOne(2000))
{
lockAcquired = true;
//...
}
}
return lockAcquired;
}
public void Release(string NameOfUnlocker, bool isThreadOwnedLockAskingToRelease)
{
if (stBusy != null && isThreadOwnedLockAskingToRelease)
{
stBusy.ReleaseMutex();
}
}
Class Service1:
ST st;
bool smlock = st.Lock("STLock");
if (smlock)
{
st = new ST(); // construct object
// do work
st.Release("STLock",smlock); // 2nd param added to identify the locker
}
Class Service2:
ST st1;
bool lockOwner = st1.Lock("QM");
st1= new StatusTable();
//Do work
// Unlock the status table
st1.Release("QM", lockOwner); // Addl. parameter added to fix sync issue
I am checking for Null in the code below, even when passed a bogus path the null test is passed. However when I return the workbook a NullPointerException is thrown. How can I check for a NullPointerReference in this case? When I inspect the workBook variable while debugging it is set to something, I believe this must be why it is passing the Null check.
public static ExcelWorkbook OpenExcelWorkSheet(string file_path)
{
string EXCEL_FILE_EXTENSION = ".xlsx",
full_path = file_path + EXCEL_FILE_EXTENSION;
var excelFile = new FileInfo(full_path);
using (var package = new ExcelPackage(excelFile))
{
ExcelWorkbook workBook = package.Workbook;
if (workBook.Equals(null))
{
throw new Exception("ERROR!!!");
}
else { Console.Write("not null"); }
return workBook;
}
}
Looking at the source code for ExcelPackage, the Workbook property can never return null. Its getter will create a new Workbook if it finds that its internal field is null:
public ExcelWorkbook Workbook
{
get
{
if (_workbook == null)
{
var nsm = CreateDefaultNSM();
_workbook = new ExcelWorkbook(this, nsm);
_workbook.GetExternalReferences();
_workbook.GetDefinedNames();
}
return (_workbook);
}
}
Knowing this, I think you could safely remove the check altogether.
Additionally, as others have noted, you do need to be careful about how you use your using statements. When a ExcelPackage is disposed, its Workbook is in turn disposed (you can see this happening in the source code I linked). This is probably why you're seeing a null workbook returned from your method, but not while you are in the using statement. Very shortly after that closing } of your using it becomes null.
If you have a class that you're using to wrap the functionality of EPPlus, then you should make sure that it:
Only instantiates the ExcelPackage once and holds onto a reference to it; do not use a using statement when you do this
Have your other methods access this ExcelPackage reference when they need to; don't pass it around or re-open the package
Implement IDisposable, then properly dispose your ExcelPackage object when your wrapper is disposing.
Here's an example*:
public class YourWrapper : IDisposable
{
private bool disposed;
public YourWrapper()
{
}
public YourWrapper(string path)
{
OpenExcelPackage(path);
}
public void OpenExcelPackage(string path)
{
Package = new ExcelPackage(path);
}
public ExcelPackage Package { get; private set; }
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
if (Package != null)
{
Package.Dispose();
Package = null;
}
}
}
this.disposed = true;
}
~YourWrapper()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
Now instead, use a using whenever you instantiate your wrapper:
using (var wrapper = new YourWrapper())
{
// Do everything related to manipulating the Excel file here
}
And everything will get cleaned up nicely.
* Please change the names of the classes/methods to something more appropriate; this was just for illustration.
I called three methods on button click in asp.net
The First Method is to save a text file on the application
The Second Method is to create and save PdF file.
The Third Method is to send email in asp.net
I want that , If any of the above method has any error occured, then all the methods that are prevsouly called should be rollbacked.
How this is possible.??
In such simpler procedure, you do not need transaction as simple Try/Catch/Finally should do the job.
FileInfo localFile;
FileInfo pdfFile;
try{
SaveTextFile(localFile);
SavePDFFile(pdfFile);
SendEmail();
}catch{
// something went wrong...
// you can remove extra try catch
// but you might get security related
// exceptions
try{
if(localFile.Exists) localFile.Delete();
if(pdfFile.Exists) pdfFile.Delete();
}catch{}
}
Here is detailed Transaction Implementation.
This is little long process, but here is a simple implementation (single threaded approach with no locking etc). Remember this is simplest form of transaction with no double locking, no multi version concurrency.
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
{
FileInfo localFile = new FileInfo("localFile.txt");
FileInfo pdfFile = new FileInfo("localFile.pdf");
SimpleTransaction.EnlistTransaction(
// prepare
() =>
{
CreateTextFile(localFile);
CreatePDFFile(pdfFile);
// prepare mail should throw an error
// if something is missing as sending email
// is network operation, it cannot be rolled back
// so email should be sent in commit
PrepareMail();
},
// commit
() =>
{
SendEmail();
},
// rollback
() =>
{
try
{
if (localFile.Exists)
localFile.Delete();
if (pdfFile.Exists)
pdfFile.Delete();
}
catch { }
},
// in doubt...
() => { }
);
}
public class SimpleTransaction : IEnlistmentNotification
{
public static void EnlistTransaction(Action prepare, Action commit, Action rollback, Action inDoubt)
{
var st = new SimpleTransaction(prepare, commit, rollback, inDoubt);
Transaction.Current.EnlistVolatile(st, EnlistmentOptions.None);
}
Action CommitAction;
Action PrepareAction;
Action RollbackAction;
Action InDoubtAction;
private SimpleTransaction(Action prepare, Action commit, Action rollback, Action inDoubt)
{
this.CommitAction = commit;
this.PrepareAction = prepare;
this.RollbackAction = rollback;
this.InDoubtAction = inDoubt ?? (Action)(() => {});
}
public void Prepare(PreparingEnlistment preparingEnlistment)
{
try
{
PrepareAction();
preparingEnlistment.Prepared();
}
catch
{
preparingEnlistment.ForceRollback();
}
}
public void Commit(Enlistment enlistment)
{
CommitAction();
enlistment.Done();
}
public void Rollback(Enlistment enlistment)
{
RollbackAction();
enlistment.Done();
}
public void InDoubt(Enlistment enlistment)
{
InDoubtAction();
enlistment.Done();
}
}
The reason this is different from Try Catch is that some other code can rollback transaction instead of raising exception.
Whether or not the operation succeeds, you should always be cleaning up files you create. If you can bypass the file system, and use a MemoryStream to store the data and include it in the email, that would of course both solve your problem and be alot faster.
As mentioned by others, there is no magic method to automatically rollback whatever you created since you clicked that button - you'll have to think of a solution yourself.
Most likely not the best solution, but a simple one, is to create a List<string> containing the files you have successfully written, and in the catch you simply delete all files from that list.
There are tons of other solutions, like a TemporaryFile class that deletes files in its Dispose() method. Give it a go and ask again when you run into issues with your attempt.
Here's another take for achieving what the OP wanted using IEnlistmentNotification.
But instead of writing all the operation (save text, save pdf, and send email) in one implementation class, this one use separate IEnlistmentNotification implementation and support for rollback in case of email sending operation failed.
var textPath = "somefile.txt";
var pdfPath = "somefile.pdf";
try {
using (var scope = new TransactionScope()) {
var textFileSave = new TextFileSave(textPath);
var pdfFileSave = new PDFFileSave(pdfPath);
Transaction.Current.TransactionCompleted += (sender, eventArgs) => {
try {
var sendEmail = new SendEmail();
sendEmail.Send();
}
catch (Exception ex) {
// Console.WriteLine(ex);
textFileSave.CleanUp();
pdfFileSave.CleanUp();
}
};
Transaction.Current.EnlistVolatile(textFileSave, EnlistmentOptions.None);
Transaction.Current.EnlistVolatile(pdfFileSave, EnlistmentOptions.None);
scope.Complete();
}
}
catch (Exception ex) {
// Console.WriteLine(ex);
}
catch {
// Console.WriteLine("Cannot complete transaction");
}
Here's the implementation details:
SendEmail
public class SendEmail {
public void Send() {
// uncomment to simulate error in sending email
// throw new Exception();
// write email sending operation here
// Console.WriteLine("Email Sent");
}
}
TextFileSave
public class TextFileSave : AbstractFileSave {
public TextFileSave(string filePath) : base(filePath) { }
protected override bool OnSaveFile(string filePath) {
// write save text file operation here
File.WriteAllText(filePath, "Some TXT contents");
return File.Exists(filePath);
}
}
PDFFileSave
public class PDFFileSave : AbstractFileSave {
public PDFFileSave(string filePath) : base(filePath) {}
protected override bool OnSaveFile(string filePath) {
// for simulating a long running process
// Thread.Sleep(5000);
// write save pdf file operation here
File.WriteAllText(filePath, "Some PDF contents");
// try returning false instead to simulate an error in saving file
// return false;
return File.Exists(filePath);
}
}
AbstractFileSave
public abstract class AbstractFileSave : IEnlistmentNotification {
protected AbstractFileSave(string filePath) {
FilePath = filePath;
}
public string FilePath { get; private set; }
public void Prepare(PreparingEnlistment preparingEnlistment) {
try {
var success = OnSaveFile(FilePath);
if (success) {
// Console.WriteLine("[Prepared] {0}", FilePath);
preparingEnlistment.Prepared();
}
else {
throw new Exception("Error saving file");
}
}
catch (Exception ex) {
// we vote to rollback, so clean-up must be done manually here
OnDeleteFile(FilePath);
preparingEnlistment.ForceRollback(ex);
}
}
public void Commit(Enlistment enlistment) {
// Console.WriteLine("[Commit] {0}", FilePath);
enlistment.Done();
}
public void Rollback(Enlistment enlistment) {
// Console.WriteLine("[Rollback] {0}", FilePath);
OnDeleteFile(FilePath);
enlistment.Done();
}
public void InDoubt(Enlistment enlistment) {
// in doubt operation here
enlistment.Done();
}
// for manual clean up
public void CleanUp() {
// Console.WriteLine("[Manual CleanUp] {0}", FilePath);
OnDeleteFile(FilePath);
}
protected abstract bool OnSaveFile(string filePath);
protected virtual void OnDeleteFile(string filePath) {
if (File.Exists(FilePath)) {
File.Delete(FilePath);
}
}
}
One thing worth mentioning about IEnlistmentNotification implementation is: if a resource called/ voted a ForceRollback() within the Prepare() method, the Rollback() method for that resource will not be triggered. So any cleanup that should have happen in Rollback() may need to be manually called in Prepare().
My program was practice for me, however, when I try to write all the directories it found, it crashes.
I tried the following:
Having it write to a file stream instead of the file itself
using File.Writealllines using a list<> (this worked, only it did the first five and no more)
FileStream.Write(subdir.ToCharArray())
I cannot see why this wouldn't work, what have I done wrong?
static void Main(string[] args)
{
Method(#"C:\");
}
static void Method(string dir)
{
//crash happens here v
StreamWriter sw = new StreamWriter(#"C:\users\"+Environment.UserName+"\desktop\log.txt",true);
foreach (string subdir in Directory.GetDirectories(dir))
{
try
{
Console.WriteLine(subdir);
sw.Write(subdir);
Method(subdir);
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("Error");
}
}
sw.Close();
}
Its recursive.
Because you're calling Method again here:
Console.WriteLine(subdir);
sw.Write(subdir);
Method(subdir); // BOOM
Your file is already open. You can't open it for writing again.
Open the file in Main once..
static void Main(string[] args) {
using (StreamWriter sw = new StreamWriter(#"C:\users\"+Environment.UserName+"\desktop\log.txt",true)) {
Method(#"C:\", sw);
}
}
Then accept it in your method:
public static void Method(string dir, StreamWriter sw) {
Then when you call it again:
sw.Write(subdir);
Method(subdir, sw); // pass in the streamwriter.
Note though, that you will quickly start chewing up memory. You're recursing through your entire C:\ drive. Maybe test it on a smaller folder?
I am agree with above but in my case solution different a little.
private static object locker = new object();
private static void WriteMessageToFile(string message)
{
string dateStr = DateTime.Now.Date.Day.ToString()+"_"+ DateTime.Now.Date.Month.ToString()+"_"+ DateTime.Now.Date.Year.ToString();
if (!Directory.Exists("Logs"))
{
DirectoryInfo di = Directory.CreateDirectory("Logs");
}
//Guid guidGenerator = Guid.NewGuid();
string filePath = _env.ContentRootPath + "\\Logs\\ProcessLog_" + dateStr + ".txt";
FileInfo fi = new FileInfo(filePath);
lock (locker)
{
using (FileStream file = new FileStream(fi.FullName, FileMode.Append, FileAccess.Write, FileShare.Read))
using (StreamWriter streamWriter = new StreamWriter(file))
{
streamWriter.WriteLine(message);
streamWriter.Close();
}
}
}
Because the following function is called asynchronous and asynchronous in many places in my asp.net core application. In this case, one thread was trying to write a file, another thread wanted to write the same file, and there was an error. As a solution, I tried the above, but it didn't work either because I tried to open a new stream before closing the previous stream. So I decided to write a secure block of code as a solution. In this case, since the other threads could not reach the locked area, they made the write operation by waiting for the previous operation and I was able to write the file without error.
I think; there is another reason code behind, cause i have used Singleton registration on startup. This function's caller classes are isolated from each other. with this reason they didn't know which thread is called the function before. Their lifetime has been finished while. Also FileStream wraps the StreamWriter then it also may work without lock, anyway it is guaranty.
Even Microsoft.Extensions.Logging does not support FileLoger by default, but we can write custom. I share the entire implementation below
public class FileLoger : ILogger
{
public static IHostingEnvironment _env;
private static object locker = new object();
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
var message = string.Format("{0}: {1} - {2}", logLevel.ToString(), eventId.Id, formatter(state, exception));
WriteMessageToFile(message);
}
private static void WriteMessageToFile(string message)
{
string dateStr = DateTime.Now.Date.Day.ToString()+"_"+ DateTime.Now.Date.Month.ToString()+"_"+ DateTime.Now.Date.Year.ToString();
if (!Directory.Exists("Logs"))
{
DirectoryInfo di = Directory.CreateDirectory("Logs");
}
//Guid guidGenerator = Guid.NewGuid();
string filePath = _env.ContentRootPath + "\\Logs\\ProcessLog_" + dateStr + ".txt";
FileInfo fi = new FileInfo(filePath);
lock (locker)
{
using (FileStream file = new FileStream(fi.FullName, FileMode.Append, FileAccess.Write, FileShare.Read))
using (StreamWriter streamWriter = new StreamWriter(file))
{
streamWriter.WriteLine(message);
streamWriter.Close();
}
}
}
public IDisposable BeginScope<TState>(TState state)
{
return null;
}
public bool IsEnabled(LogLevel logLevel)
{
return true;
}
}
public class FileLogProvider : ILoggerProvider
{
public FileLogProvider(IHostingEnvironment env)
{
FileLoger._env = env;
}
public ILogger CreateLogger(string category)
{
return new FileLoger();
}
public void Dispose()
{
}
}
Seems you didn't close your streamwriter before you use it again
public static void Method(string dir)
{
//crash happens here v
StreamWriter sw = new StreamWriter(#"C:\users\"+Environment.UserName+"\desktop\log.txt",true);
foreach (string subdir in Directory.GetDirectories(dir))
{
try
{
Console.WriteLine(subdir);
sw.Write(subdir);
//This line you'll call "Method" again
Method(subdir);
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("Error");
}
}
sw.Close();
}
Also, another suggestion, why don't you use "System.IO.File.AppendAllText(Path,Text)" method? it's easier to use