Path.Combine absolute with relative path strings - c#

I'm trying to join a Windows path with a relative path using Path.Combine.
However, Path.Combine(#"C:\blah",#"..\bling") returns C:\blah\..\bling instead of C:\bling\.
Does anyone know how to accomplish this without writing my own relative path resolver (which shouldn't be too hard)?

What Works:
string relativePath = "..\\bling.txt";
string baseDirectory = "C:\\blah\\";
string absolutePath = Path.GetFullPath(baseDirectory + relativePath);
(result: absolutePath="C:\bling.txt")
What doesn't work
string relativePath = "..\\bling.txt";
Uri baseAbsoluteUri = new Uri("C:\\blah\\");
string absolutePath = new Uri(baseAbsoluteUri, relativePath).AbsolutePath;
(result: absolutePath="C:/blah/bling.txt")

Call Path.GetFullPath on the combined path http://msdn.microsoft.com/en-us/library/system.io.path.getfullpath.aspx
> Path.GetFullPath(Path.Combine(#"C:\blah\",#"..\bling"))
C:\bling
(I agree Path.Combine ought to do this by itself)

Path.GetFullPath(#"c:\windows\temp\..\system32")?

For windows universal apps Path.GetFullPath() is not available, you can use the System.Uri class instead:
Uri uri = new Uri(Path.Combine(#"C:\blah\",#"..\bling"));
Console.WriteLine(uri.LocalPath);

This will give you exactly what you need (path does NOT have to exist for this to work)
DirectoryInfo di = new DirectoryInfo(#"C:\blah\..\bling");
string cleanPath = di.FullName;

Path.GetFullPath() does not work with relative paths.
Here's the solution that works with both relative + absolute paths. It works on both Linux + Windows and it keeps the .. as expected in the beginning of the text (at rest they will be normalized). The solution still relies on Path.GetFullPath to do the fix with a small workaround.
It's an extension method so use it like text.Canonicalize()
/// <summary>
/// Fixes "../.." etc
/// </summary>
public static string Canonicalize(this string path)
{
if (path.IsAbsolutePath())
return Path.GetFullPath(path);
var fakeRoot = Environment.CurrentDirectory; // Gives us a cross platform full path
var combined = Path.Combine(fakeRoot, path);
combined = Path.GetFullPath(combined);
return combined.RelativeTo(fakeRoot);
}
private static bool IsAbsolutePath(this string path)
{
if (path == null) throw new ArgumentNullException(nameof(path));
return
Path.IsPathRooted(path)
&& !Path.GetPathRoot(path).Equals(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)
&& !Path.GetPathRoot(path).Equals(Path.AltDirectorySeparatorChar.ToString(), StringComparison.Ordinal);
}
private static string RelativeTo(this string filespec, string folder)
{
var pathUri = new Uri(filespec);
// Folders must end in a slash
if (!folder.EndsWith(Path.DirectorySeparatorChar.ToString())) folder += Path.DirectorySeparatorChar;
var folderUri = new Uri(folder);
return Uri.UnescapeDataString(folderUri.MakeRelativeUri(pathUri).ToString()
.Replace('/', Path.DirectorySeparatorChar));
}

Be careful with Backslashes, don't forget them (neither use twice:)
string relativePath = "..\\bling.txt";
string baseDirectory = "C:\\blah\\";
//OR:
//string relativePath = "\\..\\bling.txt";
//string baseDirectory = "C:\\blah";
//THEN
string absolutePath = Path.GetFullPath(baseDirectory + relativePath);

Related

GetEnvironmentVariable() and SetEnvironmentVariable() for PATH Variable

I want to extend the current PATH variable with a C# program. Here I have several problems:
Using GetEnvironmentVariable("PATH", EnvironmentVariableTarget.Machine) replaces the placeholders (i.e. '%SystemRoot%\system32' is replaced by the current path 'C:\Windows\system32'). Updating the PATH variable, I dont want to replace the placeholder with the path.
After SetEnvironmentVariable no program can't be opened from the command box anymore (i.e. calc.exe in the command box doesn't work). Im using following code:
String oldPath = Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.Machine);
Environment.SetEnvironmentVariable("PATH", oldPath + ";%MYDIR%", EnvironmentVariableTarget.Machine);
After editing and changing the PATH variable with Windows everything works again. (I thing changes are required, otherwise it is not overwritten)
You can use the registry to read and update:
string keyName = #"SYSTEM\CurrentControlSet\Control\Session Manager\Environment";
//get non-expanded PATH environment variable
string oldPath = (string)Registry.LocalMachine.CreateSubKey(keyName).GetValue("Path", "", RegistryValueOptions.DoNotExpandEnvironmentNames);
//set the path as an an expandable string
Registry.LocalMachine.CreateSubKey(keyName).SetValue("Path", oldPath + ";%MYDIR%", RegistryValueKind.ExpandString);
You can use WMI to retrieve the raw values (not sure about updating them though):
ManagementObjectSearcher searcher = new ManagementObjectSearcher("select * from Win32_Environment WHERE Name = 'PATH'");
foreach (ManagementBaseObject managementBaseObject in searcher.Get())
Console.WriteLine(managementBaseObject["VariableValue"]);
Check WMI Reference on MSDN
You could try this mix. It gets the Path variables from the registry, and adds the "NewPathEntry" to Path, if not already there.
static void Main(string[] args)
{
string NewPathEntry = #"%temp%\data";
string NewPath = "";
bool MustUpdate = true;
string RegKeyName = #"SYSTEM\CurrentControlSet\Control\Session Manager\Environment";
string path = (string)Microsoft.Win32.Registry.LocalMachine.OpenSubKey(RegKeyName).GetValue
("Path", "", Microsoft.Win32.RegistryValueOptions.DoNotExpandEnvironmentNames);
string[] paths = path.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string subPath in paths)
{
NewPath += subPath + ";";
if (subPath.ToLower() == NewPathEntry.ToLower())
{
MustUpdate = false;
}
}
if (MustUpdate == true)
{
Environment.SetEnvironmentVariable("Path", NewPath + NewPathEntry, EnvironmentVariableTarget.Machine);
}
}
You could go through the registry...
string keyName = #"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment";
//get raw PATH environment variable
string path = (string)Registry.GetValue(keyName, "Path", "");
//... Make some changes
//update raw PATH environment variable
Registry.SetValue(keyName, "Path", path);
While working on the application we had to have an option to use Oracle instantclient from user-defined folder. In order to use the instantclient we had to modify the environment path variable and add this folder before calling any Oracle related functionality.
Here is method that we use for that:
/// <summary>
/// Adds an environment path segments (the PATH varialbe).
/// </summary>
/// <param name="pathSegment">The path segment.</param>
public static void AddPathSegments(string pathSegment)
{
LogHelper.Log(LogType.Dbg, "EnvironmentHelper.AddPathSegments", "Adding path segment: {0}", pathSegment);
string allPaths = Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.Process);
if (allPaths != null)
allPaths = pathSegment + "; " + allPaths;
else
allPaths = pathSegment;
Environment.SetEnvironmentVariable("PATH", allPaths, EnvironmentVariableTarget.Process);
}
Note that this has to be called before anything else, possibly as the first line in your Main file (not sure about console applications).
Using Registry.GetValue will expand the placeholders, so I recommend using Registry.LocalMachine.OpenSubKey, then get the value from the sub key with options set to not expand environment variables. Once you've manipulated the path to your liking, use the registry to set the value again. This will prevent Windows "forgetting" your path as you mentioned in the second part of your question.
const string pathKeyName = #"SYSTEM\CurrentControlSet\Control\Session Manager\Environment";
var pathKey = Registry.LocalMachine.OpenSubKey(pathKeyName);
var path = (string)pathKey.GetValue("PATH", "", RegistryValueOptions.DoNotExpandEnvironmentNames);
// Manipulate path here, storing in path
Registry.SetValue(String.Concat(#"HKEY_LOCAL_MACHINE\", pathKeyName), "PATH", path);

RadEditor UploadPaths Absolute File Paths

I am trying to set the uploadpaths for all of the RadEditor File Managers to the same absolute file path, and also share the same path across a couple of Solutions on the same machine.
So, I wrote a method to get the path from the Web.Config and set all the Properties on the FileManagerDialogConfiguration objects (UploadPaths, ViewPaths, etc). The problem is these properties are looking for Virtual Paths, and full paths don't work.
How can I supply the properties with the Virtual Path of a folder that may/may not be in the same Solution?
This didn't work:
private static FileManagerDialogConfiguration fixPaths(FileManagerDialogConfiguration f, String[] path)
{
if (path[0][0] != '~')
{
Uri basePath = new Uri(ConfigurationManager.AppSettings["veMainPath"]);
Uri absPath = new Uri(path[0]);
Uri relPath = basePath.MakeRelativeUri(absPath);
path[0] = relPath.LocalPath;
}
f.ViewPaths = path;
f.UploadPaths = path;
f.DeletePaths = path;
f.MaxUploadFileSize = 10485760;
return f;
}

Converting a URI path to a relative file system path in .NET

How do I convert an absolute or relative URI path (e.g. /foo/bar.txt) to a (segmentwise) corresponding relative file system path (e.g. foo\bar.txt) in .NET?
My program is not an ASP.NET application.
Have you already tried Server.MapPath?
or Uri.LocalPath property? Something like following :
string uriString = "file://server/filename.ext";
// Lesson learnt - always check for a valid URI
if(Uri.IsWellFormedUriString(uriString))
{
Uri uri = new Uri(uriString);
Console.WriteLine(uri.LocalPath);
}
I figured out this way to produce a full absolute file system path from a relative or absolute URI and a base path.
With:
Uri basePathUri = new Uri(#"C:\abc\");
From a relative URI:
string filePath = new Uri(basePathUri, relativeUri).AbsolutePath;
From an absolute URI:
// baseUri is a URI used to derive a relative URI
Uri relativeUri = baseUri.MakeRelativeUri(absoluteUri);
string filePath = new Uri(basePathUri, relativeUri).AbsolutePath;
You can do this:
var localPath = Server.MapPath("/foo/bar.txt");
See MSDN for details
Not all have access to server.MapPath due to backend or framework changes, and there are lot's of way one of them is could be like this
public enum FileLocation
{
NotSet,
Disk,
Resource,
}
private static readonly string[] FileExtenstions = new[] {
".js"
,".ts"
,".vue"
,".css"
,".jpg"
,".png"
,".gif"
,".ico"
,".svg"
,".ttf"
,".eot"
,".ttf"
,".woff"
,".woff2"
,".mp4"
,".mp3"
,".emf"
};
public FileLocation IsMappedTo(Uri uri)
{
if (uri is null)
{
throw new ArgumentNullException(nameof(uri));
}
//make sure we support .net default URI contract
if (uri.IsFile)
return FileLocation.Disk;
//now assume you are looking in a web application
var path = uri.AbsolutePath;
if (path.Length == 0 || path.Equals("/",StringComparison.Ordinal) || path.Length< FileExtenstions.Min(s=>s.Length))
return FileLocation.NotSet;
//get the directory normally one would use IWebHostEnvironment.ContentRootPath different versions .net will have other methods
var dir = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot");
//get all resources names from the assembly hosting this class out side if the loop from this assembly you can also use
//you can also use GetManifestResourceNames() to use the web application's assembly
var resourceNames = new HashSet<string>(this.GetType().Assembly.GetManifestResourceNames());
var entryAssembly = Assembly.GetEntryAssembly();
if (entryAssembly != null && entryAssembly != this.GetType().Assembly)
{
foreach (var entry in entryAssembly.GetManifestResourceNames())
{
if (string.IsNullOrEmpty(entry))
resourceNames.Add(entry);
}
}
for (var i = 0; i < FileExtenstions.Length; i++)
{
if (FileExtenstions[i].Equals(path[FileExtenstions[i].Length..], StringComparison.OrdinalIgnoreCase) || path.Contains(FileExtenstions[i], StringComparison.OrdinalIgnoreCase))
{
//exists on disk
if (File.Exists(Path.Combine(dir, path.Replace("/", #"\", StringComparison.Ordinal))))
return FileLocation.Disk;
//has a file as an embedded resource with the same name (ignores the path) so you might have duplicates names
if (resourceNames.Any(a => a.EndsWith(path.Split('/')[^1], StringComparison.OrdinalIgnoreCase)))
return FileLocation.Resource;
}
}
return FileLocation.NotSet;
}
after this you just do:
switch (IsMappedTo(url))
{
case FileLocation.NotSet:
break;
case FileLocation.Disk:
break;
case FileLocation.Resource:
break;
}

How to convert a relative path to an absolute path in a Windows application?

How do I convert a relative path to an absolute path in a Windows application?
I know we can use server.MapPath() in ASP.NET. But what can we do in a Windows application?
I mean, if there is a .NET built-in function that can handle that...
Have you tried:
string absolute = Path.GetFullPath(relative);
? Note that that will use the current working directory of the process, not the directory containing the executable. If that doesn't help, please clarify your question.
If you want to get the path relative to your .exe then use
string absolute = Path.Combine(Application.ExecutablePath, relative);
This one works for paths on different drives, for drive-relative paths and for actual relative paths. Heck, it even works if the basePath isn't actually absolute; it always uses the current working directory as final fallback.
public static String GetAbsolutePath(String path)
{
return GetAbsolutePath(null, path);
}
public static String GetAbsolutePath(String basePath, String path)
{
if (path == null)
return null;
if (basePath == null)
basePath = Path.GetFullPath("."); // quick way of getting current working directory
else
basePath = GetAbsolutePath(null, basePath); // to be REALLY sure ;)
String finalPath;
// specific for windows paths starting on \ - they need the drive added to them.
// I constructed this piece like this for possible Mono support.
if (!Path.IsPathRooted(path) || "\\".Equals(Path.GetPathRoot(path)))
{
if (path.StartsWith(Path.DirectorySeparatorChar.ToString()))
finalPath = Path.Combine(Path.GetPathRoot(basePath), path.TrimStart(Path.DirectorySeparatorChar));
else
finalPath = Path.Combine(basePath, path);
}
else
finalPath = path;
// resolves any internal "..\" to get the true full path.
return Path.GetFullPath(finalPath);
}
It's a bit older topic, but it might be useful for someone.
I have solved a similar problem, but in my case, the path was not at the beginning of the text.
So here is my solution:
public static class StringExtension
{
private const string parentSymbol = "..\\";
private const string absoluteSymbol = ".\\";
public static String AbsolutePath(this string relativePath)
{
string replacePath = AppDomain.CurrentDomain.BaseDirectory;
int parentStart = relativePath.IndexOf(parentSymbol);
int absoluteStart = relativePath.IndexOf(absoluteSymbol);
if (parentStart >= 0)
{
int parentLength = 0;
while (relativePath.Substring(parentStart + parentLength).Contains(parentSymbol))
{
replacePath = new DirectoryInfo(replacePath).Parent.FullName;
parentLength = parentLength + parentSymbol.Length;
};
relativePath = relativePath.Replace(relativePath.Substring(parentStart, parentLength), string.Format("{0}\\", replacePath));
}
else if (absoluteStart >= 0)
{
relativePath = relativePath.Replace(".\\", replacePath);
}
return relativePath;
}
}
Example:
Data Source=.\Data\Data.sdf;Persist Security Info=False;
Data Source=..\..\bin\Debug\Data\Data.sdf;Persist Security Info=False;

Combine physical path and virtual path outside ASP.NET

I want to convert a virtual file path to a physical file path in a windows service.
I know what the physical path is for the virtual directory, so I have the following function that works, but feels like a fudge:
public static string GetPhysicalPathFromVirtual(string rootPath, string virtualPath)
{
int trailingSlash = virtualPath.IndexOf('/', 1) + 1;
int length = virtualPath.Length - trailingSlash;
string stripped = virtualPath.Substring(trailingSlash, length);
stripped = stripped.Replace(#"/", #"\");
return Path.Combine(rootPath, stripped);
}
The following example:
string test = FileHelper.GetPhysicalPathFromVirtual(#"T:\generateddocuments\output\", #"/virtualroot/folder/myfile.pdf");
Returns: T:\generateddocuments\output\folder\myfile.pdf
Is there a more elegant way to do this?
Uri class may be of help for your task.
Please note that using relative paths in services can expose a huge security hole so you should be very defensive when you code them.
Here's what I came up with:
public static string GetPhysicalPathFromVirtual(string rootPath, string virtualPath)
{
const string mandatoryVirtualPrefix = "/virtualroot/";
if (!virtualPath.StartsWith(mandatoryVirtualPrefix))
throw new ArgumentOutOfRangeException(virtualPath, string.Format("Virtual '{0}' path must start with mandatory prefix '{1}'", virtualPath, mandatoryVirtualPrefix));
var relativePath = virtualPath.Substring(mandatoryVirtualPrefix.Length);
var rootUri = new Uri(rootPath, UriKind.Absolute);
var relativeUri = new Uri(relativePath, UriKind.Relative);
var absoluteUri = new Uri(rootUri, relativeUri);
if (!rootUri.IsBaseOf(absoluteUri))
throw new ArgumentOutOfRangeException(virtualPath, string.Format("Virtual path '{0}' can't be outside of root '{1}'", virtualPath, rootPath));
return absoluteUri.LocalPath;
}

Categories