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.

To download the source code for this article, you can visit our GitHub repository.

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.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

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.

If you’d like to learn more about copying the entire contents of a directory, check out our article on how to Copy the Entire Contents of a Directory in C#.

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:

ExceptionMeaning
DirectoryNotFoundExceptionIndicates 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.
IOExceptionTriggers 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.
SecurityExceptionThis means the caller does not have the required access to perform a particular operation on a directory.
PathTooLongExceptionThis occurs when the path provided is longer than the system-defined maximum path length.
ArgumentException and ArgumentNullExceptionWe 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.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!