In .NET applications, reading text files is essential for tasks like loading configurations, processing data, and parsing logs. In this article, we look at the file’s relative and absolute paths and set up a new project to read a text file using its relative path.

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

Relative and Absolute Paths 

To start it off, let’s remind ourselves of the difference between an absolute and a relative path.

An absolute path specifies a file’s complete location from the system’s root, ensuring a constant reference regardless of the application’s current directory. For example, C:\Users\Username\Documents\file.txt is an absolute path that precisely points to a file’s location on a Windows system. So no matter where our application is running or located, it always tries to read the file at this location.

On the other hand, a relative path defines a file’s location from the current working directory, not from the root of the file system. 

The key to understanding a relative path is recognizing its starting point so we correctly locate the file. This means our application is running from the C:\Users\Username\ directory for everything to run smoothly. If we use our example from the absolute path, C:\Users\Username\Documents\file.txt its relative path would be Documents\file.txt. This path definition implies that the file resides within a Documents folder relative to the current directory. 

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

To learn more about paths, check out our existing article, which goes more in-depth.

Now that we have refreshed ourselves on relative paths, let’s set up a project to see them in action!

Project Setup

First, we create a new console application using the .NET CLI:

dotnet new console

Next, we add a sample CodeMaze.txt file in the solution folder and read its content:

string filePath = @"C:\Code Maze\files-csharp\ReadATextFileWithoutSpecifyingFullLocation\CodeMaze.txt";
try
{
    var fileContent = File.ReadAllText(filePath);
    Console.WriteLine("File content:\n" + fileContent);
}
catch (IOException ex)
{
    Console.WriteLine("An error occurred while reading the file: " + ex.Message);
}

For demonstration purposes, we define a variable filePath to store the absolute path of our text file, CodeMaze.txt. Here, we use a Windows-specific path C:\Code Maze\files-csharp\ReadATextFileWithoutSpecifyingFullLocation\CodeMaze.txt, reflecting its location on our machine. It’s important to note that the actual path may differ on other machines, depending on where the file is stored.

Next, with the filePath set, we use File.ReadAllText(filePath) to grab the content of the file and display it to the console. To cover potential errors that can occur while fetching or reading the file, we wrap this operation in a try-catch block.

One important thing to note is that additional files, such as text files, aren’t automatically copied to the output directory. To include these in the output, we need to set the file’s properties manually. One way of doing it is to open our csproj file and update the CopyToOutputDirectory property for our file:

<ItemGroup>
  <None Update="CodeMaze.txt">
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  </None>
</ItemGroup>

This means that the latest version of the file is always available alongside our application’s executable without unnecessarily copying the file if it hasn’t changed.

For each of our examples below, we will also make use of a static helper class ReadFileMethods. We will define the helper methods as we go along.

Now that we completed our groundwork, let’s explore how to work with the relative path of the CodeMaze.txt file.

Replacing a File’s Absolute Path

In general, defining relative paths over absolute paths is preferred due to the flexibility and portability it offers. Relative paths allow seamless transfer of projects between different environments or systems without necessitating the modification of file or directory references.

With that said, let’s see how we can transform the absolute path to CodeMaze.txt into a relative one.

Using File Name

The first thing we can do is to use the file’s name to read its content:

const string fileName = @"CodeMaze.txt";

try
{
    var fileContent = ReadFileMethods.ReadFile(fileName);
    Console.WriteLine("File content:\n" + fileContent);
}
catch (IOException ex)
{
    Console.WriteLine("An error occurred while reading the file: " + ex.Message);
}

In the Main()  method, we alter the fileName variable to only include "CodeMaze.txt" as its relative path. If the file is located in a subdirectory of the solution the name would be "{SubDirectory}\CodeMaze.txt".

We make use of our first helper method ReadFile() to read the contents of fileName:

public static string ReadFile(string filePath) => File.ReadAllText(filePath);

ReadFile() accepts a filePath parameter and then calls File.ReadAllText(fileName) to fetch and return the file’s content.

File.ReadAllText() accepts a file path, opens a file, reads its content as a string, and finally closes the file, returning the contents. In our case, by providing only the fileName without a full path, we instruct the method to look for the file in the application’s current working directory.

This approach works well in straightforward applications like console apps, where the current working directory usually matches the executable’s location. By setting the file’s output properties in the project setup we ensured our file was copied to the app’s output directory, so File.ReadAllText() can locate the  "CodeMaze.txt" file correctly.

Now, let’s see how we can construct a full/absolute path given a relative path.

Using the Current Directory to Get the File’s Relative Path

For this approach we use Directory.GetCurrentDirectory() method which returns the absolute path of the current working directory:

public static string ReadFileCurrentUsingDirectory(string fileName)
{
    var currentDirectoryPath = Directory.GetCurrentDirectory();
    var filePath = Path.Combine(currentDirectoryPath, fileName);

    return ReadFile(filePath);
}

Here we define our second helper method ReadFileCurrentUsingDirectory(). It accepts a fileName parameter,  which in our case will be "CodeMaze.txt".

First, by using Directory.GetCurrentDirectory() we define the variable currentDirectory which holds the absolute path of the current working directory.

Next, using Path.Combine() we combine the file name and directory path into a complete path and store it in the filePath variable. Finally, using this variable we call ReadFile() and return the file’s contents.

This approach constructs the same path as the first example. But it is good to know how things work under the hood and see how we can be more explicit when constructing relative paths.

Now, let’s see how we can use AppDomain.CurrentDomain.BaseDirectory for the same purpose.

Using the Application Directory to Get the File’s Relative Path

The AppDomain.CurrentDomain.BaseDirectory property gives us the base directory where the application’s executable (.exe) file resides.

Let’s define a new method inside our ReadFileMethods class using BaseDirectory to compute the full path of the CodeMaze.txt file:

public static string ReadFileUsingBaseDirectory(string fileName)
{
    var baseDirectoryPath = AppDomain.CurrentDomain.BaseDirectory;
    var filePath = Path.Combine(baseDirectoryPath, fileName);

    return ReadFile(filePath);
}

The approach here is similar to the previous method, the difference lies in how we get the base directory path and what it refers to. In this case, it will be an absolute path of where the application’s executable (.exe) file resides. In the case of simpler apps this will typically be the same as the current working directory, but this is not always the case.

After we fetch the path, we combine it with the file name, read the file, and return its content to the user.

Which Approach to Use?

Choosing between defining only a file name, or constructing full relative paths by using Directory.GetCurrentDirectory() and AppDomain.CurrentDomain.BaseDirectory depends on the specific needs of our application and its deployment environment.

Using just the file name is the most straightforward approach, ideal for simple applications where files are located in the current working directory. This method is quick and efficient but assumes that the application’s execution context remains constant. On the other hand, explicitly constructing a full path with Directory.GetCurrentDirectory() increases the readability and understanding of the code. 

Both approaches best suit console applications in controlled settings, ideal for development and testing. But, they have limitations. The current directory can change with the application’s launch method, potentially causing issues in complex setups.

Using AppDomain.CurrentDomain.BaseDirectory offers us a reliable path to the executable’s directory. It’s an excellent choice if we want to access files distributed with our application. Also, it ensures consistent file access across different deployment scenarios. This approach is useful for loading resources in the executable’s directory or other application directory deployed relatively to the executable.

In summary, the decision among these methods depends on the application’s specific needs, emphasizing the trade-offs between ease of access, deployment consistency, and resource management efficiency.

Conclusion

In this article, we looked at different approaches for creating a file’s relative path. We learned some of the pros and cons for each one and also gave an overview on when it is suitable to use which approach.

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