Adapted answer from duplicate: How to make an absolute path relative to a particular folder? and updated to match parameter sequence as the .Net standard implementation
This is a more concise implementation for .Net Framework that has thorough guidance in the doc comments and accounts for absolute paths that have relative folder links which is missing from many other solutions.
Tested against Anton Krouglov's test cases in .Net 6 all cases match
System.IO.Path.GetRelativePath
:)
public static partial class PathUtilities{ /// <summary> /// Rebases file with <paramref name="path"/> to the folder specified by <paramref name="relativeTo"/>. /// </summary> /// <param name="path">Full file path (absolute)</param> /// <param name="relativeTo">Full base directory path (absolute) the result should be relative to. This path is always considered to be a directory.</param> /// <returns>Relative path to file with respect to <paramref name="relativeTo"/></returns> /// <remarks>Paths are resolved by calling the <seealso cref="System.IO.Path.GetFullPath(string)"/> method before calculating the difference. This will resolve 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 GetRelativePath(string relativeTo, string path) { String pathSep = "\\"; String itemPath = Path.GetFullPath(path); String baseDirPath = Path.GetFullPath(relativeTo); // If folder contains upper folder references, they get resolved here. "c:\test\..\test2" => "c:\test2" bool isDirectory = path.EndsWith(pathSep); String[] p1 = Regex.Split(itemPath, "[\\\\/]").Where(x => x.Length != 0).ToArray(); String[] p2 = Regex.Split(relativeTo, "[\\\\/]").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))); if (String.IsNullOrEmpty(r)) return "."; else if (isDirectory && p1.Length >= p2.Length) // only append on forward traversal, to match .Net Standard Implementation of System.IO.Path.GetRelativePath r += pathSep; return r; }}
Usage of this method:
string itemPath = @"C:\Program Files\Dummy Folder\MyProgram\Data\datafile1.dat";string baseDirectory = @"C:\Program Files\Dummy Folder\MyProgram";string result = PathUtilities.GetRelativePath(baseDirectory, itemPath);Console.WriteLine(result);
Results in:
Data\datafile1.dat
RE: But if the file is at the root directory or 1 directory below root, then display the full path.
In this case we could modify the method, or simply perform an additional check:string itemPath = @"C:\Program Files\Dummy Folder\datafile1.dat"; string baseDirectory = @"C:\Program Files\Dummy Folder\MyProgram"; string result = PathUtilities.GetRelativePath(baseDirectory, itemPath); Console.WriteLine("Before Check: '{0}'", result); if (result.StartsWith("..\\")) result = itemPath; Console.WriteLine("After Check: '{0}'", result);
Result:
Before Check: '..\datafile1.dat' After Check: 'C:\Program Files\Dummy Folder\datafile1.dat'