Managing files and directories is a fundamental aspect of many software development applications. In this article, we will learn C# file system manipulation using the Directory and DirectoryInfo classes.
By the end of this article, we should have a solid understanding of how to efficiently work with files and directories in C#.
Overview of Directory and DirectoryInfo Classes in C#
In C#, the Directory
and DirectoryInfo
classes have distinct purposes, capabilities, and use cases. They expose an extensive collection of methods and properties for executing directory and subdirectory operations using the System.IO
namespace. With them, it is easy for us to work with the file system in a platform-agnostic and efficient manner.
We will explore these classes individually to uncover their strengths, weaknesses, and practical applications. There are several methods shared by both which we are going to look into as well.
Directory Class and Directory Operations
Directory
is a static class that doesn’t require instantiation, as a result, it cannot be inherited.
It excels in handling fundamental activities like creating, removing, enumerating, and inspecting directories. The absence of object instances eliminates performance overhead, making it suitable for quick, one-off operations.
We can create, manage, and interact with directories within the file system with the static methods available.
Create a Directory in C#
To create a directory, we have to call the CreateDirectory()
method. It has two overloads.
The first overload has only one parameter – the directory path:
public static System.IO.DirectoryInfo CreateDirectory (string path);
The second overload takes two – the directory path and a UnixFileMode, an enum representing system file permissions:
[System.Runtime.Versioning.UnsupportedOSPlatform("windows")] public static System.IO.DirectoryInfo CreateDirectory (string path, System.IO.UnixFileMode unixCreateMode);
The Unix file modes are specific to Unix-like operating systems, such as Linux and macOS. They are not directly applicable to other platforms like Windows.
Let’s use the first method signature to create a directory:
var path = @"C:\Users\Public\CM-Demos"; var newDirectory = Directory.CreateDirectory(path);
First, we specify the path for our directory. The name of the directory, CM-Demos
, comes after the last backslash. The method returns our newDirectory
variable value of type DirectoryInfo
.
Retrieve Files in a Directory
We can use the Directory
class to retrieve files in a specified directory path via the GetFiles()
method:
var files = Directory.GetFiles(path);
We pass in the path in our earlier example to GetFiles()
. It retrieves an array of file paths from there if there are any. We can iterate through the array using a foreach
loop and process each file as needed.
If the directory is empty, the method returns an empty array.
GetFiles()
has four overloads, which we won’t cover in this article.
Similarly, we can get directories using GetDirectories()
, which also has four overloads.
Delete Directory
The Delete()
directory method permanently deletes the specified directory.
The first overload of this method takes only the path to the directory:
Directory.Delete(path);
However, for this method to run without an exception, the directory must be empty.
Where we have files/subdirectories in the directory we want to delete, we would need to use the second overload of the Delete()
method:
Directory.Delete(path, true);
The second parameter is a boolean value. It determines whether the deletion should be performed recursively. If set to true
, it means that not only will the specified directory be deleted, but all its subdirectories and files will also be deleted. If set to false
, only the directory itself will be deleted, and an exception will be thrown if the directory is not empty.
DirectoryInfo Class
DirectoryInfo
is not static, but it is a sealed class. This means other classes cannot inherit it as well.
Its constructor takes a single argument, which is a string representing the path to the directory we want to work with:
var directory = new DirectoryInfo(@"C:\Public\CM-Demos");
It is ideal when we have multiple operations that must be performed on a directory. Encapsulating the directory path within an object, not only offers a more object-oriented approach but also mitigates potential inefficiencies associated with repeated path parsing. It is particularly advantageous in scenarios where we anticipate reusing the same object multiple times, making it the ideal choice for such use cases.
The Directory
class performs security checks for all its methods, which can introduce overhead and redundancy when used repeatedly. In contrast, when we work with an instance of DirectoryInfo
and access its non-static methods, we can bypass these redundant security checks, resulting in more efficient operations.
Because the methods in DirectoryInfo
are not static, we access them using an instance of the class.
Let’s check out some other file operations exposed by the DirectoryInfo
class.
Create Subdirectories
We can create subdirectories on a declared path with the CreateSubdirectory()
method:
var directory = new DirectoryInfo(@"C:\Public\CM-Demos"); var subDirectory = "SubFolder"; directory.CreateSubdirectory(subDirectory);
The SubFolder
directory will be a subdirectory of CM-Demos
. If the directory does not exist, the code will also create it.
Move Directories
The MoveTo()
method moves or renames a directory represented by the DirectoryInfo
object to a specified destination directory:
var sourceDirectory = new DirectoryInfo(@"C:\Users\Public\CM-Demos"); var destDirectory = @"C:\Users\Public\CM-Demos\NewDirectory"; sourceDirectory.MoveTo(destDirectory);
Keep in mind that our source and destination directories must be on the same disk volume, but have different names.
When we move a directory, all of its contents move with it. As a result, if we attempt to access any files or subdirectories within the original directory using the sourceDirectory
reference, a System.IO.DirectoryNotFoundException
will be thrown. This is because we have moved the directory and its contents to a new location, hence the old reference no longer points to a valid path.
Enumerate Directories
With EnumerateDirectories()
, we can iterate through and work with the subdirectories without the need to load them all into memory at once. This is especially efficient for large directory structures.
Let’s take a look at this in action:
var directoryInfo = new DirectoryInfo(@"C:\Public\CM-Demos"); foreach (var subdirectory in directoryInfo.EnumerateDirectories()) { Console.WriteLine(subdirectory.FullName); }
Here, we iterate through and print the full paths of all subdirectories within the specified directory.
Also, we can provide search patterns and options to filter the directories we want to enumerate using the right overload.
Now we have learned how to use different directory methods, it’s important to address their security risks. They can potentially allow access to sensitive files and data beyond the authorized directory, a vulnerability known as ‘directory traversal.’
Directory Traversal Vulnerability
A directory traversal vulnerability, otherwise known as path traversal or directory climbing, arises when we do not appropriately validate and filter user input from web browsers.
Attackers exploit this by sending malicious URLs to the web server and manipulating it to access specific files. These attacks do not require high levels of sophistication. They often involve simple trial-and-error, where attackers use ../
commands to navigate directories until they find the desired file.
To mitigate this vulnerability, we can ensure that the path
parameter is not directly constructed from user input. Indirect object references map user requests to resource locations without directly exposing user input to file system APIs. For example, if a user wants to access a specific file, we could have a reference or key associated with that file. Our application then maps this reference to the actual file path securely without exposing the raw file system paths.
Another prevention technique is to use the safe listing approach. With this, we specify what will be allowed as an input. This method ensures that the user input conforms to certain predefined standards.
Directory Security and Permissions in C#
The security settings for directories in C# are controlled through the DirectorySecurity
class, which is part of the System.Security.AccessControl
namespace. Consequently, this class offers methods that enable the management and manipulation of the access control list (ACL) associated with directories.
Furthermore, the DirectoryInfo
class provides two essential methods, GetAccessControl()
and SetAccessControl()
, as a result, we can conveniently work with the DirectorySecurity
object associated with a specific directory.
With these methods, we can retrieve and modify the security settings, effectively controlling access permissions for the directory:
var directoryPath = @"C:\Users\Public\CM-Demos"; var directoryInfo = new DirectoryInfo(directoryPath); DirectorySecurity directorySecurity = directoryInfo.GetAccessControl(); directorySecurity.AddAccessRule(new FileSystemAccessRule( "UserName", FileSystemRights.FullControl, AccessControlType.Allow) ); directoryInfo.SetAccessControl(directorySecurity);
Here, we set up a DirectoryInfo
object to represent a directory. Next, we retrieve its existing security settings using GetAccessControl()
.
As a next step, we add a new access rule to grant a specific username full control over the directory. Finally, we use the SetAccessControl()
method of the directoryInfo
object to apply the modified directorySecurity
back to the directory. This effectively updates the security settings for the directory.
Common Directory Exception-Handling Scenarios in C#
Working with directories in C# exposes us to a variety of exceptions, each attributable to distinct situations. We need to implement proper exception handling, either globally or using try/catch block, to ensure that our application gracefully manages and recovers from these cases.
Let’s explore some common exceptions:
Exception | Meaning |
---|---|
DirectoryNotFoundException | Indicates that we are attempting to access a directory that does not exist. The path we have may be invalid or the directory may have been deleted. |
IOException | Triggers when we try to delete a directory that isn't empty. It can also be a result of an attempt to delete our working directory. |
SecurityException | This means the caller does not have the required access to perform a particular operation on a directory. |
PathTooLongException | This occurs when the path provided is longer than the system-defined maximum path length. |
ArgumentException and ArgumentNullException | We get this if our path is null or empty |
Conclusion
We’ve covered the Directory and DirectoryInfo classes in C#, detailing their respective purposes, capabilities, and practical applications. We also addressed directory traversal vulnerabilities, and how to mitigate them. Furthermore, we discussed managing directory security and permissions through the DirectorySecurity class. Lastly, we emphasized the importance of proper exception handling in common directory-related scenarios, ensuring robust and error-resilient applications.
Mastering the provisions of these classes empowers us to handle files and directories in our applications. By understanding their strengths, handling security considerations, and implementing robust exception handling, we can create reliable and secure file system operations.