As a web developer, I rarely need to deal with physical files on disk. It’s a request/response kind of life while communicating with a database engine. When working on this blog, I find myself doing more file manipulation, as I’m dealing with markdown files, images, and other static assets. It’s a mix of Ruby, .NET Core, and JavaScript. .NET Core is my first choice to script things. In this post, we’ll look at the Path
class found under the System.IO
namespace and some of the methods I regularly use.
The Path
class in .NET is a fantastic helper class that doesn’t get the love it deserves. We should always think about using Path
when dealing with files or the need to create assets on disk. Let’s start by looking at two standard variables we may have in our codebase: a directory path and a file name.
var path = "/users/khalidabuhakmeh/project";
var image = "lolcat.png";
The values aren’t necessarily important, but they are conventional strings we may have to deal with when working with the file system. Let’s explore some methods found on Path
in no particular order.
GetExtension
The GetExtension
method is powerful, as it can correctly find the extension of a file name regardless of the path given to it. In our case, we can determine that the extension on lolcat.png
is .png
.
var extension = Path.GetExtension(image);
Console.WriteLine($"extension: {extension}");
// extension: .png
Note that GetExtension
returns a value with a starting .
in front of the file type.
ChangeExtension
A recent addition to the .NET Framework, the ChangeExtension
method was a pleasant surprise. It’s addition almost negates the need for GetExtension
in many cases, as we might typically use GetExtension
to build a new destination file. To use the ChangeExtension
method, we need only pass in the full path to a file, and the extension we want.
var txt = Path.ChangeExtension(image, "txt");
Console.WriteLine($"txt: {txt}");
// txt: lolcat.txt
The ChangeExtension
implementation can also handle if the extension method value starts with a .
or whether it doesn’t.
var txt = Path.ChangeExtension(image, ".txt");
Console.WriteLine($"txt: {txt}");
// txt: lolcat.txt
The .NET Framework takes care of the edge case where our extension values may be inconsistent.
Combine
The Combine
method is the most used feature of Path
. It can combine multiple paths and files into a useable value. We don’t have to worry about separators at the beginning or end of our values. The Combine
method also has a params
overload, meaning it will handle a large number of path values.
var combine = Path.Combine(path, image);
Console.WriteLine($"combine: {combine}");
// combine: /users/khalidabuhakmeh/project/lolcat.png
This method is useful when transferring files from one directory to another, and we need to create a new target destination using the same filename and target directory.
GetRelativePath
Relative paths are essential to use when we want to store path values. Our code may run in multiple environments, making root folder names unpredictable. We can convert absolute paths to relative paths using the GetRelativePath
method.
var relative = Path.GetRelativePath(path, "/users");
Console.WriteLine($"relative: {relative}");
// relative: ../..
GetFileName
When dealing with a fully qualified path, we may want to “pop” the filename from the end. We could write a complicated statement that splits our path, breaking our string at the separators and takes the last value, or we could use GetFileName
.
var combine = Path.Combine(path, image);
var filename = Path.GetFileName(combine);
Console.WriteLine($"filename: {filename}");
// filename: lolcat.png
This method is useful for retrieving and storing the filename in a data store. I’ve used this personally when moving files from local disk up to a cloud storage provider and needing to retain the original filename.
GetFileNameWithoutExtension
Naming our filenames with descriptive names can give us the benefit of additional metadata. Using GetFileNameWithoutExtension
strips the extension and the path of a file, only returning the file name.
var withoutExtension = Path.GetFileNameWithoutExtension(image);
Console.WriteLine($"without extension: {withoutExtension}");
// without extension: lolcat
This method can be useful when displaying the file to a user who may already understand the types of data they are looking at, i.e., images, text files, or videos.
GetRandomFileName
If we are processing and generating files in bulk, the GetRandomFileName
method is beneficial. The method returns a cryptographically strong random 8.3 string (eight characters, then a dot, then three more characters) that we can use as either a folder or a file name.
var random = Path.GetRandomFileName();
Console.WriteLine($"random: {random}");
// random: 05y5xhb4.wt2
Note that the random value also includes what appears to be a random file extension. Changing the file extension will affect the cryptographic strength of the name.
GetTempFileName
This next method is similar to GetRandomFileName
but behaves much differently. The GetTempFileName
method creates a 0-byte file on disk before returning the full path to the generated asset. That means this method performs an I/O operation.
var temp = Path.GetTempFileName();
Console.WriteLine($"temp: {temp} | exists: {File.Exists(temp)}");
// temp: /var/folders/0h/bb4ssg9s2_d482xfx6dq43l00000gn/T/tmpv8oOvk.tmp | exists: True
Using GetTempFileName
ensures that we either get a file or get an exception early in our code. Nothing could be worse than performing expensive file operations only to find we can’t write our work to disk.
Conclusion
One of the most significant advantages of the .NET Framework is it has practical classes and namespaces that make us productive. Managing files and directories is a mostly solved problem in .NET, and we should be using types like Path
. If we need to do more than deal with paths, there are also the types of File
and Directory
to help round out all the functions we need.
I hope you found this post helpful and feel free to leave a comment below.