There\'s a part in my apps that displays the file path loaded by the user through OpenFileDialog. It\'s taking up too much space to display the whole path, but I don\'t want
.NET Core 2.0 has Path.GetRelativePath which can be used like so:
var relativePath = Path.GetRelativePath(
@"C:\Program Files\Dummy Folder\MyProgram",
@"C:\Program Files\Dummy Folder\MyProgram\Data\datafile1.dat");
In the above example, the relativePath
variable is equal to Data\datafile1.dat
.
@Dave's solution does not work when the file paths do not end with a forward slash character (/
) which can happen if the path is a directory path. My solution fixes that problem and also makes use of the Uri.UriSchemeFile
constant instead of hard coding "FILE"
.
/// <summary>
/// Creates a relative path from one file or folder to another.
/// </summary>
/// <param name="fromPath">Contains the directory that defines the start of the relative path.</param>
/// <param name="toPath">Contains the path that defines the endpoint of the relative path.</param>
/// <returns>The relative path from the start directory to the end path.</returns>
/// <exception cref="ArgumentNullException"><paramref name="fromPath"/> or <paramref name="toPath"/> is <c>null</c>.</exception>
/// <exception cref="UriFormatException"></exception>
/// <exception cref="InvalidOperationException"></exception>
public static string GetRelativePath(string fromPath, string toPath)
{
if (string.IsNullOrEmpty(fromPath))
{
throw new ArgumentNullException("fromPath");
}
if (string.IsNullOrEmpty(toPath))
{
throw new ArgumentNullException("toPath");
}
Uri fromUri = new Uri(AppendDirectorySeparatorChar(fromPath));
Uri toUri = new Uri(AppendDirectorySeparatorChar(toPath));
if (fromUri.Scheme != toUri.Scheme)
{
return toPath;
}
Uri relativeUri = fromUri.MakeRelativeUri(toUri);
string relativePath = Uri.UnescapeDataString(relativeUri.ToString());
if (string.Equals(toUri.Scheme, Uri.UriSchemeFile, StringComparison.OrdinalIgnoreCase))
{
relativePath = relativePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
}
return relativePath;
}
private static string AppendDirectorySeparatorChar(string path)
{
// Append a slash only if the path is a directory and does not have a slash.
if (!Path.HasExtension(path) &&
!path.EndsWith(Path.DirectorySeparatorChar.ToString()))
{
return path + Path.DirectorySeparatorChar;
}
return path;
}
There is a Windows API called PathRelativePathToA that can be used to find a relative path. Please note that the file or directory paths that you pass to the function must exist for it to work.
var relativePath = PathExtended.GetRelativePath(
@"C:\Program Files\Dummy Folder\MyProgram",
@"C:\Program Files\Dummy Folder\MyProgram\Data\datafile1.dat");
public static class PathExtended
{
private const int FILE_ATTRIBUTE_DIRECTORY = 0x10;
private const int FILE_ATTRIBUTE_NORMAL = 0x80;
private const int MaximumPath = 260;
public static string GetRelativePath(string fromPath, string toPath)
{
var fromAttribute = GetPathAttribute(fromPath);
var toAttribute = GetPathAttribute(toPath);
var stringBuilder = new StringBuilder(MaximumPath);
if (PathRelativePathTo(
stringBuilder,
fromPath,
fromAttribute,
toPath,
toAttribute) == 0)
{
throw new ArgumentException("Paths must have a common prefix.");
}
return stringBuilder.ToString();
}
private static int GetPathAttribute(string path)
{
var directory = new DirectoryInfo(path);
if (directory.Exists)
{
return FILE_ATTRIBUTE_DIRECTORY;
}
var file = new FileInfo(path);
if (file.Exists)
{
return FILE_ATTRIBUTE_NORMAL;
}
throw new FileNotFoundException(
"A file or directory with the specified path was not found.",
path);
}
[DllImport("shlwapi.dll", SetLastError = true)]
private static extern int PathRelativePathTo(
StringBuilder pszPath,
string pszFrom,
int dwAttrFrom,
string pszTo,
int dwAttrTo);
}
Play with something like:
private String GetRelativePath(Int32 level, String directory, out String errorMessage) {
if (level < 0 || level > 5) {
errorMessage = "Find some more smart input data";
return String.Empty;
}
// ==========================
while (level != 0) {
directory = Path.GetDirectoryName(directory);
level -= 1;
}
// ==========================
errorMessage = String.Empty;
return directory;
}
And test it
[Test]
public void RelativeDirectoryPathTest() {
var relativePath =
GetRelativePath(3, AppDomain.CurrentDomain.BaseDirectory, out var errorMessage);
Console.WriteLine(relativePath);
if (String.IsNullOrEmpty(errorMessage) == false) {
Console.WriteLine(errorMessage);
Assert.Fail("Can not find relative path");
}
}
In ASP.NET Core 2
, if you want the relative path to bin\Debug\netcoreapp2.2
you can use the following combination:
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
public class RenderingService : IRenderingService
{
private readonly IHostingEnvironment _hostingEnvironment;
public RenderingService(IHostingEnvironment hostingEnvironment)
{
_hostingEnvironment = hostingEnvironment;
}
public string RelativeAssemblyDirectory()
{
var contentRootPath = _hostingEnvironment.ContentRootPath;
string executingAssemblyDirectoryAbsolutePath = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
string executingAssemblyDirectoryRelativePath = System.IO.Path.GetRelativePath(contentRootPath, executingAssemblyDirectoryAbsolutePath);
return executingAssemblyDirectoryRelativePath;
}
}
A bit late to the question, but I just needed this feature as well. I agree with DavidK that since there is a built-in API function that provides this, you should use it. Here's a managed wrapper for it:
public static string GetRelativePath(string fromPath, string toPath)
{
int fromAttr = GetPathAttribute(fromPath);
int toAttr = GetPathAttribute(toPath);
StringBuilder path = new StringBuilder(260); // MAX_PATH
if(PathRelativePathTo(
path,
fromPath,
fromAttr,
toPath,
toAttr) == 0)
{
throw new ArgumentException("Paths must have a common prefix");
}
return path.ToString();
}
private static int GetPathAttribute(string path)
{
DirectoryInfo di = new DirectoryInfo(path);
if (di.Exists)
{
return FILE_ATTRIBUTE_DIRECTORY;
}
FileInfo fi = new FileInfo(path);
if(fi.Exists)
{
return FILE_ATTRIBUTE_NORMAL;
}
throw new FileNotFoundException();
}
private const int FILE_ATTRIBUTE_DIRECTORY = 0x10;
private const int FILE_ATTRIBUTE_NORMAL = 0x80;
[DllImport("shlwapi.dll", SetLastError = true)]
private static extern int PathRelativePathTo(StringBuilder pszPath,
string pszFrom, int dwAttrFrom, string pszTo, int dwAttrTo);
There is a Win32 (C++) function in shlwapi.dll that does exactly what you want: PathRelativePathTo()
I'm not aware of any way to access this from .NET other than to P/Invoke it, though.
I have used this in the past.
/// <summary>
/// Creates a relative path from one file
/// or folder to another.
/// </summary>
/// <param name="fromDirectory">
/// Contains the directory that defines the
/// start of the relative path.
/// </param>
/// <param name="toPath">
/// Contains the path that defines the
/// endpoint of the relative path.
/// </param>
/// <returns>
/// The relative path from the start
/// directory to the end path.
/// </returns>
/// <exception cref="ArgumentNullException"></exception>
public static string MakeRelative(string fromDirectory, string toPath)
{
if (fromDirectory == null)
throw new ArgumentNullException("fromDirectory");
if (toPath == null)
throw new ArgumentNullException("toPath");
bool isRooted = (Path.IsPathRooted(fromDirectory) && Path.IsPathRooted(toPath));
if (isRooted)
{
bool isDifferentRoot = (string.Compare(Path.GetPathRoot(fromDirectory), Path.GetPathRoot(toPath), true) != 0);
if (isDifferentRoot)
return toPath;
}
List<string> relativePath = new List<string>();
string[] fromDirectories = fromDirectory.Split(Path.DirectorySeparatorChar);
string[] toDirectories = toPath.Split(Path.DirectorySeparatorChar);
int length = Math.Min(fromDirectories.Length, toDirectories.Length);
int lastCommonRoot = -1;
// find common root
for (int x = 0; x < length; x++)
{
if (string.Compare(fromDirectories[x], toDirectories[x], true) != 0)
break;
lastCommonRoot = x;
}
if (lastCommonRoot == -1)
return toPath;
// add relative folders in from path
for (int x = lastCommonRoot + 1; x < fromDirectories.Length; x++)
{
if (fromDirectories[x].Length > 0)
relativePath.Add("..");
}
// add to folders to path
for (int x = lastCommonRoot + 1; x < toDirectories.Length; x++)
{
relativePath.Add(toDirectories[x]);
}
// create relative path
string[] relativeParts = new string[relativePath.Count];
relativePath.CopyTo(relativeParts, 0);
string newPath = string.Join(Path.DirectorySeparatorChar.ToString(), relativeParts);
return newPath;
}