Two functions in our standard ASP.NET app are:
private static void SaveToFileSystem(AttributeFileAttachment attach, int paId)
{
string fileName = GetAttachmentFullName(attach.FileName, paId);
File.WriteAllBytes(fileName, attach.Content);
}
public static string GetAttachmentFullName(string name, int paId)
{
HttpContext ctx = Util.Util.GetHttpContext();
return string.Format("{0}{1}_{2}_{3}",
ctx.Server.MapPath("<some variable to get the path>" + "attributeFileAttachments\\"),
ctx.Session.SessionID,
paId,
name);
}
when File.WriteAllBytes is executed it returns exception:
he process cannot access the file '\\d$\Home\\attributeFileAttachments\' because it is being used by another process.
The essence are two lines:
ctx.Server.MapPath... (Microsoft code)
and File.WriteAllBytes...
that work on the same file.
It turns out that HttpServerUtility.MapPath locks the file and leaves it locked !?
I don't see any comments on that in official documentation nor I see anybody complains on that.
But it can't be anything else, since the two lines are consecutive.
When I modify fileName for File.WriteAllBytes in immediate window just a bit, the writing succeeds, since that new file is not locked.
One more thing I have noticed is that this happens only and always for some of the attachment files.
Thank you for the time and any advice.
Related
everyone!
I do a small project for my company and I use C#. I have a script for my project. But before this day, my colleagues and I had an idea that the script would be used by users one by one. For example, if there are a user A and user B, there can be the order where the user B runs the script and only then the user A can run the script.
Today the decision was made to give the users the possibility to run the script freely without the predetermined order. And now I have some thoughts. Here the part of the script:
if (Directory.Exists(#"H:\" + doc_number + #"\detached") == false)
{
Directory.CreateDirectory(#"H:\" + doc_number + #"\detached");
File.WriteAllBytes(#"H:\" + doc_number + #"\detached\1.cms", signature_bytes);
}
else
{
string[] files = Directory.GetFiles(#"H:\" + doc_number + #"\detached"); int files_number = files.Length;
File.WriteAllBytes(#"H:\" + doc_number + #"\detached\" + Convert.ToString(files_number + 1) + ".cms", signature_bytes);
}
Firstly, there is a check of the existence of a directory. If it doesn't exist, the directory will be created and the first file will be added there. Otherwise, we just count the number of files in the directory and then create a new file with a name which is the number of the files in the folder plus one.
However, I'm thinking about the situation when the user A and the user B were at the beginning of this part of the script at the same time and the condition for both would be positive so it wouldn't be executed correctly. Or if one of them started running this part earlier but his or her PC was less powerful so while creating the directory another user would go through the condition, counting files and start creating a file before the first user which would be also incorrect.
I don't know how likely one of these situations are. if so, how can I solve it?
Indeed, you can run into concurrency issues. And you are correct that you can't rely on the existence of a directory to decide what branch to take in your if statement because you might have operations execute in this order:
User A: Checks for directory. Does not exist.
User B: Checks for directory. Does not exist.
User A: Creates directory, enters if branch.
User B: Creates directory, enters if branch.
If the code was running in one process on one machine but in multiple threads, you could use a lock statement.
If the code was running on different processes on the same machine, you could use a cross-process coordination method such as a Mutex.
The question implies that the code runs on different computers but accesses the same file system. In this case, a lock file is a common mechanism to coordinate access to a shared resource. In this approach, you would attempt to create a file and lock it. If that file already exists and is locked by another process, you know someone else got there first. Depending on your needs, a common scenario is to wait for the lock on the file to go away then acquire the lock yourself and continue.
This strategy also works for the other 2 cases above, though is less efficient.
For information about how to create a file with a lock, see
How to lock a file with C#?
There are some issues with your code. For example, what would happen if a file is deleted? The number of files in the directory would be different than the number of the last file, and you can end up trying to write a file that already exists. Also, please use Path.Combine to create paths, it is safer. You also don't need to check if the directory exists, since Directory.Create will do nothing if it already exists.
Common for all solutions bellow:
string baseDir = Path.Combine("H:",doc_number, "detached");
Directory.Create(baseDir);
If you just want any number of users to create files in the same directory, some solutions that are more safe:
Use a GUID:
var guid = Guid.NewGuid();
var file = Path.Combine(baseDir, $"{guid}.cms");
File.WriteAllBytes(file, signature_bytes);
Iterate, trying to create a new file:
bool created = false;
int index = 1;
while(!created)
{
//Check first if the file exists, and gets the next available index
var file = Path.Combine(baseDir, $"{index}.cms");
while(File.Exists(file))
{
file = Path.Combine(baseDir, $"{++index}.cms");
}
//Handle race conditions, if the file was created after we checked
try
{
//Try create the file, not allowing others to acess it while open
using var stream = File.Open(file,FileMode.CreateNew,FileAccess.Write,FileShare.None);
stream.Write(signature_bytes);
created = true;
}
catch (IOException) //If the file already exists, try the next index
{
++index;
}
}
I am using the JitBit Macro Recorder to create "bots" that save me a lot of time at work. This program can use the mouse and the keyboard and perform tasks by checking different if-options like "if image found on screen".
My newest "bot" is about 900 lines of commands long and I would like to make a log-file to find an error somewhere in there. Sadly, this program doesn't offer such an option, but it let's me use c# as a task. I have NO experience with c# but I thought, that this is easy to do for someone who has some experience.
If I click execute c# code, I get the following input field:
Important: This code MUST contain a class named "Program" with a static method "Main"!
public class Program
{
public static void Main()
{
System.Windows.Forms.MessageBox.Show("test");
}
}
Now I need two code templates:
1. Write a message to a "bot_log.txt" located on my desktop.
[19.05.2016 - 12:21:09] "Checking if item with number 3 exists..."
The number "3" changes with every run and is an exact paste of the clipboard.
2. Add an empty line to the same file
(Everything should be added to a new line at the end of this file.)
If you have no idea how to program in C#, then you should learn it,
if you want to use code provided from answers.
And if you want to generate timestamps and stuff then it's not done within minutes and I don't think someone writes the whole code just for your fitting. Normally questions should have at least a bit of general interest.
Anyway:
This works, if you have a RichTextTbox in your program.
Just do a new event (like clicking a button) and do this inside it.
(This was posted somewhere here too or on another site, with sligh changes)
public static void SaveMyFile(RichTextBox rtb)
{
// Create a SaveFileDialog to request a path and file name to save to.
SaveFileDialog saveLog = new SaveFileDialog();
// Initialize the SaveFileDialog to specify the RTF extention for the file.
saveLog.DefaultExt = "*.rtf";
saveLog.Filter = "RTF Files|*.rtf"; //You can do other extensions here.
// Determine whether the user selected a file name from the saveFileDialog.
if (saveLog.ShowDialog() == System.Windows.Forms.DialogResult.OK &&
saveLog.FileName.Length > 0)
{
// Save the contents of the RichTextBox into the file.
try
{
rtb.SaveFile(saveLog.FileName);
}
catch
{
MessageBox.Show("Error creating the file.\n Is the name correct and is enough free space on your disk\n ?");
}
MessageBox.Show("Logfile was saved successful.");
}
}
I've managed to create my own file extension following this tutorial: http://www.codeproject.com/Articles/17023/System-File-Association
So far, it works perfectly. I've got only one thing that I can't solve.
When I double-click on a file with that extension, my program opens up. Now, I'd want to perform an action in my program. I made my way through some threads here and read that the file path is automatically passed to the startup arguments.
The problem is that no single argument is passed, also Process.GetCurrentProcess().StartInfo.FileName returned an empty string. I think this is consecutively because I don't pass any arguments when double-clicking my file.
This is my code:
var fai = new FileAssociationInfo(".extension");
if (!fai.Exists)
{
try
{
fai.Create("My Extension Program");
var pai = new ProgramAssociationInfo(fai.ProgId);
if (!pai.Exists)
{
pai.Create("My Program File",
new ProgramVerb("Open", Application.ExecutablePath);
pai.DefaultIcon = new ProgramIcon(Application.ExecutablePath);
}
}
}
As you can see I only pass the application's path to open it up. But how can I pass the file path as argument now? I've seen that e.g. the author of the article passes "%1" as argument, I tried that, too, but nothing changed.
Thanks in advance.
ProcessStartInfo.FileName usually gives you the path to your program executable itself, not the file which was clicked in Windows Explorer, so this seems the wrong thing to check in your case.
If you want to get the arguments using the current Process, then Process.GetCurrentProcess().StartInfo.Arguments should give you a string containing all the arguments passed to the program. If there are multiple arguments, you would need to parse these into separate values yourself.
But the standard, simpler way to get the arguments is to make sure the Main() method of your program has signature static void Main(string[] args){}. args is already processed into separate values for you, so it is easier to handle it here, even if you only pass it off to another class or store them in a static variable.
The %1 should ensure the clicked file is passed as the first argument (args[0]) to your program.
Well, I got it. What I had to do was creating a subkey in ClassesRoot: "ProgramName\shell\open\command". Then set a value containing the application's path and attach "%1" to it and you're done.
I have quotas-enabled drive and I want to remove all files created by specifed user (actually a set of applications that runs using special account) from that drive. How can I do this without recursivly checking all files and folders on HDD is it created by specifed user or not? I just need to get "iterator".
Take a look on following example
[Test]
public void Test()
{
string user = #"Domain\UserName";
var files = Directory.EnumerateFiles(#"C:\TestFolder")
.Where(x => IsOwner(x, user));
Parallel.ForEach(files, File.Delete);
}
private static bool IsOwner(string filePath, string user)
{
return string.Equals(File.GetAccessControl(filePath).GetOwner(typeof (NTAccount)).Value, user,
StringComparison.OrdinalIgnoreCase);
}
In term of improving performance, I think you could use Task Parallel Library when using recursive algorithm to search file and folder.
Another way, you could do that Lucence was a useful framework for search and it was already published version for .NET
Actually, you can do that iteratively and very efficiently using USN Change Journal, see http://msdn.microsoft.com/en-us/library/windows/desktop/aa363798.aspx. With proper use of filtering, you can get list of files created by specific user within specific time period.
On the other hand, this technique is quite complicated and is suitable for time-critical applications; if efficiency is not the focal point of your application, I'd choose simpler solution.
I use TextWriterTraceListener (System.Diagnostics) in my application to trace several things like exceptions,...
The application is running on a terminal server and if there are many users using it simultaneously the listener starts to create many tracefiles with random GUIDs in the filename.
Are there possibilities or workarounds to avoid this behaviour ?
I've just taken a look at the documentation for TextWriterTraceListener and there's a note about 1/3 of the way down the page
If an attempt is made to write to a file that is in use or unavailable, the file name is automatically prefixed by a GUID
So, this would appear to be by design. If the file is indeed unavailable then there's nothing that can be done about it with the current implementation. What you could try doing is writing a custom implementation of TextWriterTraceListener that overrides the relevant Write/WriteLine methods so that the output goes to a file, per user, with a name that better suits your needs.
If what you want is for ALL logging from ALL users on the Terminal Server to go to a single file, then you'll almost certainly need to have some kind of "3rd party" process running that "owns" the file and synchronises writes to it, such as a Windows Service that is then called by your custom TextWriterTraceListener
Was the fix calling the Trace.Listeners.Add(xxx listener) multiple times on accident?
Because if you have multiple listeners added they write too all listeners when you call the Trace.writeline();
Also local IIS might be continueing to have the file in use when you shut down the application.
I am currently testing the addition of System.Diagnostics.Trace.Listeners.Clear() in my output method...
// Upon a new day re-create the TextWriterTraceListener to update our file name...
if (_date?.Day != DateTime.Now.Day) { _listener = null; }
if (_listener == null)
{
System.Diagnostics.Trace.Listeners.Clear();
_fileName = $"{DateTime.Now.ToString("yyyy-MM-dd")}_Trace.json";
// Add a writer that appends to the trace.log file:
_listener = new System.Diagnostics.TextWriterTraceListener(_fileName);
_listener.IndentSize = 4;
_listener.TraceOutputOptions = System.Diagnostics.TraceOptions.None; // TraceOptions.DateTime | TraceOptions.ThreadId;
System.Diagnostics.Trace.AutoFlush = true;
System.Diagnostics.Trace.Listeners.Add(_listener);
// Obtain the Console's output stream, then add that as a listener...
System.Diagnostics.Trace.Listeners.Add(new System.Diagnostics.TextWriterTraceListener(Console.Out));
}