I am working on a program that needs to create a multiple temporary folders for the application. These will not be seen by the user. The app is written in VB.net. I can think of a few ways to do it such as incremental folder name or random numbered folder names, but I was wondering, how other people solve this problem?
问题:
回答1:
Update: Added File.Exists check per comment (2012-Jun-19)
Here's what I've used in VB.NET. Essentially the same as presented, except I usually didn't want to create the folder immediately.
The advantage to use GetRandomFilename is that it doesn't create a file, so you don't have to clean up if your using the name for something other than a file. Like using it for folder name.
Private Function GetTempFolder() As String
Dim folder As String = Path.Combine(Path.GetTempPath, Path.GetRandomFileName)
Do While Directory.Exists(folder) or File.Exists(folder)
folder = Path.Combine(Path.GetTempPath, Path.GetRandomFileName)
Loop
Return folder
End Function
Random Filename Example:
C:\Documents and Settings\username\Local Settings\Temp\u3z5e0co.tvq
Here's a variation using a Guid to get the temp folder name.
Private Function GetTempFolderGuid() As String
Dim folder As String = Path.Combine(Path.GetTempPath, Guid.NewGuid.ToString)
Do While Directory.Exists(folder) or File.Exists(folder)
folder = Path.Combine(Path.GetTempPath, Guid.NewGuid.ToString)
Loop
Return folder
End Function
guid Example:
C:\Documents and Settings\username\Local Settings\Temp\2dbc6db7-2d45-4b75-b27f-0bd492c60496
回答2:
You have to use System.IO.Path.GetTempFileName()
Creates a uniquely named, zero-byte temporary file on disk and returns the full path of that file.
You can use System.IO.Path.GetDirectoryName(System.IO.Path.GetTempFileName())
to get only the temp folder information, and create your folders in there
They are created in the windows temp folder and that's consider a best practice
回答3:
Just to clarify:
System.IO.Path.GetTempPath()
returns just the folder path to the temp folder.
System.IO.Path.GetTempFileName()
returns the fully qualified file name (including the path) so this:
System.IO.Path.Combine(System.IO.Path.GetTempPath(), System.IO.Path.GetTempFileName())
is redundant.
回答4:
There's a possible race condition when:
- creating a temp file with
GetTempFileName()
, deleting it, and making a folder with the same name, or - using
GetRandomFileName()
orGuid.NewGuid.ToString
to name a folder and creating the folder later
With GetTempFileName()
after the delete occurs, another application could successfully create a temp file with the same name. The CreateDirectory()
would then fail.
Similarly, between calling GetRandomFileName()
and creating the directory another process could create a file or directory with the same name, again resulting in CreateDirectory()
failing.
For most applications it's OK for a temp directory to fail due to a race condition. It's extremely rare after all. For them, these races can often be ignored.
In the Unix shell scripting world, creating temp files and directories in a safe race-free way is a big deal. Many machines have multiple (hostile) users -- think shared web host -- and many scripts and applications need to safely create temp files and directories in the shared /tmp directory. See Safely Creating Temporary Files in Shell Scripts for a discussion on how to safely create temp directories from shell scripts.
回答5:
As @JonathanWright pointed out, race conditions exist for the solutions:
- Create a temporary file with
GetTempFileName()
, delete it, and create a folder with the same name - Use
GetRandomFileName()
orGuid.NewGuid.ToString
to create a random folder name, check whether it exists, and create it if not.
It is possible, however, to create a unique temporary directory atomically by utilizing the Transactional NTFS (TxF) API.
TxF has a CreateDirectoryTransacted()
function that can be invoked via Platform Invoke. To do this, I adapted Mohammad Elsheimy's code for calling CreateFileTransacted()
:
// using System.ComponentModel;
// using System.Runtime.InteropServices;
// using System.Transactions;
[ComImport]
[Guid("79427a2b-f895-40e0-be79-b57dc82ed231")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IKernelTransaction
{
void GetHandle(out IntPtr pHandle);
}
// 2.2 Win32 Error Codes <http://msdn.microsoft.com/en-us/library/cc231199.aspx>
public const int ERROR_PATH_NOT_FOUND = 0x3;
public const int ERROR_ALREADY_EXISTS = 0xb7;
public const int ERROR_EFS_NOT_ALLOWED_IN_TRANSACTION = 0x1aaf;
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool CreateDirectoryTransacted(string lpTemplateDirectory, string lpNewDirectory, IntPtr lpSecurityAttributes, IntPtr hTransaction);
/// <summary>
/// Creates a uniquely-named directory in the directory named by <paramref name="tempPath"/> and returns the path to it.
/// </summary>
/// <param name="tempPath">Path of a directory in which the temporary directory will be created.</param>
/// <returns>The path of the newly-created temporary directory within <paramref name="tempPath"/>.</returns>
public static string GetTempDirectoryName(string tempPath)
{
string retPath;
using (TransactionScope transactionScope = new TransactionScope())
{
IKernelTransaction kernelTransaction = (IKernelTransaction)TransactionInterop.GetDtcTransaction(Transaction.Current);
IntPtr hTransaction;
kernelTransaction.GetHandle(out hTransaction);
while (!CreateDirectoryTransacted(null, retPath = Path.Combine(tempPath, Path.GetRandomFileName()), IntPtr.Zero, hTransaction))
{
int lastWin32Error = Marshal.GetLastWin32Error();
switch (lastWin32Error)
{
case ERROR_ALREADY_EXISTS:
break;
default:
throw new Win32Exception(lastWin32Error);
}
}
transactionScope.Complete();
}
return retPath;
}
/// <summary>
/// Equivalent to <c>GetTempDirectoryName(Path.GetTempPath())</c>.
/// </summary>
/// <seealso cref="GetTempDirectoryName(string)"/>
public static string GetTempDirectoryName()
{
return GetTempDirectoryName(Path.GetTempPath());
}
回答6:
Something like...
using System.IO;
string path = Path.GetTempPath() + Path.GetRandomFileName();
while (Directory.Exists(path))
path = Path.GetTempPath() + Path.GetRandomFileName();
Directory.CreateDirectory(path);
回答7:
You could generate a GUID for your temporary folder names.
回答8:
You can use GetTempFileName to create a temporary file, then delete and re-create this file as a directory instead.
Note: link didn't work, copy/paste from: http://msdn.microsoft.com/en-us/library/aa364991(VS.85).aspx
回答9:
As long as the name of the folder doesn't need to be meaningful, how about using a GUID for them?
回答10:
Combined answers from @adam-wright and pix0r will work the best IMHO:
using System.IO;
string path = Path.GetTempPath() + Path.GetRandomFileName();
while (Directory.Exists(path))
path = Path.GetTempPath() + Path.GetRandomFileName();
File.Delete(path);
Directory.CreateDirectory(path);
回答11:
The advantage to using System.IO.Path.GetTempFileName is that it will be a file in the user's local (i.e., non-roaming) path. This is exactly where you would want it for permissions and security reasons.
回答12:
Dim NewFolder = System.IO.Directory.CreateDirectory(IO.Path.Combine(IO.Path.GetTempPath, Guid.NewGuid.ToString))
回答13:
@JonathanWright suggests CreateDirectory will fail when there is already a folder. If I read Directory.CreateDirectory it says 'This object is returned regardless of whether a directory at the specified path already exists.' Meaning you do not detect a folder created between check exists and actually creating.
I like the CreateDirectoryTransacted() suggested by @DanielTrebbien but this function is deprecated.
The only solution I see that is left is to use the c api and call the 'CreateDirectory' there as it does error if the folder exists if you really need to be sure to cover the whole race condition. That would result in something like this:
Private Function GetTempFolder() As String
Dim folder As String
Dim succes as Boolean = false
Do While not succes
folder = Path.Combine(Path.GetTempPath, Path.GetRandomFileName)
success = c_api_create_directory(folder)
Loop
Return folder
End Function