How to make an absolute path relative to a particular folder?

In your example, it's simply absPath.Substring(relativeTo.Length).

More elaborate example would require going back a few levels from the relativeTo, as follows:

"C:\RootFolder\SubFolder\MoreSubFolder\LastFolder\SomeFile.txt"
"C:\RootFolder\SubFolder\Sibling\Child\"

The algorithm to make a relative path would look as follows:

  • Remove the longest common prefix (in this case, it is "C:\RootFolder\SubFolder\")
  • Count the number of folders in relativeTo (in this case, it is 2: "Sibling\Child\")
  • Insert ..\ for each remaining folder
  • Concatenate with the remainder of the absolute path after the suffix removal

The end result looks like this:

"..\..\MoreSubFolder\LastFolder\SomeFile.txt"

After searching for makeRelative in following git repository: https://github.com/tapika/syncProj/blob/8ea41ebc11f538a22ed7cfaf59a8b7e0b4c3da37/syncProj.cs#L1685

We find this solution, which has been enhanced with documentation after a bit of testing ;)

public static partial class PathUtilities
{
    /// <summary>
    /// Rebases file with path <paramref name="fullPath"/> to folder with <paramref name="baseDir"/>.
    /// </summary>
    /// <param name="fullPath">Full file path (absolute)</param>
    /// <param name="baseDir">Full base directory path (absolute)</param>
    /// <returns>Relative path to file with respect to <paramref name="baseDir"/></returns>
    /// <remarks>Paths are resolved by calling the <seealso cref="System.IO.Path.GetFullPath(string)"/> method before calculating the difference. This will flatten relative path fragments:
    /// <code>
    /// "c:\test\..\test2" => "c:\test2"
    /// </code>
    /// These path framents are expected to be created by concatenating a root folder with a relative path such as this:
    /// <code>
    /// var baseFolder = @"c:\test\";
    /// var virtualPath = @"..\test2";
    /// var fullPath = System.IO.Path.Combine(baseFolder, virtualPath);
    /// </code>
    /// The default file path for the current executing environment will be used for the base resolution for this operation, which may not be appropriate if the input paths are fully relative or relative to different
    /// respective base paths. For this reason we should attempt to resolve absolute input paths <i>before</i> passing through as arguments to this method.
    /// </remarks>
    static public string MakeRelative(string fullPath, string baseDir)
    {
        String pathSep = "\\";
        String itemPath = Path.GetFullPath(fullPath);
        String baseDirPath = Path.GetFullPath(baseDir); // If folder contains upper folder references, they get resolved here. "c:\test\..\test2" => "c:\test2"

        String[] p1 = Regex.Split(itemPath, "[\\\\/]").Where(x => x.Length != 0).ToArray();
        String[] p2 = Regex.Split(baseDir, "[\\\\/]").Where(x => x.Length != 0).ToArray();
        int i = 0;

        for (; i < p1.Length && i < p2.Length; i++)
            if (String.Compare(p1[i], p2[i], true) != 0)    // Case insensitive match
                break;

        if (i == 0)     // Cannot make relative path, for example if resides on different drive
            return itemPath;

        String r = String.Join(pathSep, Enumerable.Repeat("..", p2.Length - i).Concat(p1.Skip(i).Take(p1.Length - i)));
        return r;
    }
}

Usage of this method:

string itemPath = @"C:\RootFolder\SubFolder\MoreSubFolder\LastFolder\SomeFile.txt";
string baseDirectory = @"C:\RootFolder\SubFolder\";

string result = PathUtilities.MakeRelative(itemPath, baseDirectory);
Console.WriteLine(result);

Results in:

MoreSubFolder\LastFolder\SomeFile.txt

Yes, you can do that, it's easy, think of your paths as URIs:

Uri fullPath = new Uri(@"C:\RootFolder\SubFolder\MoreSubFolder\LastFolder\SomeFile.txt", UriKind.Absolute);
Uri relRoot = new Uri(@"C:\RootFolder\SubFolder\", UriKind.Absolute);

string relPath = relRoot.MakeRelativeUri(fullPath).ToString();
// relPath == @"MoreSubFolder\LastFolder\SomeFile.txt"

Tags:

C#

.Net

Path