Odd C# path issue - c#

My C# application writes its full path surrounded by double quotes to a file, with:
streamWriter.WriteLine("\"" + Application.ExecutablePath + "\"");
Normally it works, the written file contains
"D:\Dev\Projects\MyApp\bin\Debug\MyApp.exe"
But, if the executable path of my application contains a #, something weird happens. The output becomes:
"D:\Dev\Projects#/MyApp/bin/Debug/MyApp.exe"
The slashes after the # become forward slashes. This causes issues with the system I am developing.
Why is this happening, and is there a way to prevent it that is more elegant than string.replacing the path before writing?

I just looked into the source code of Application.ExecutablePath, and the implementation is essentially this*:
Assembly asm = Assembly.GetEntryAssembly();
string cb = asm.CodeBase;
var codeBase = new Uri(cb);
if (codeBase.IsFile)
return codeBase.LocalPath + Uri.UnescapeDataString(codeBase.Fragment);
else
return codeBase.ToString();
The property Assembly.CodeBase will return the location as an URI. Something like:
file:///C:/myfolder/myfile.exe
The # is the fragment marker in a URI; it marks the beginning of the fragment. Apparently, the Uri class alters the given uri when it's parsed and converted back to a string again.
Since Assembly.Location contains a 'normal' file path, I guess your best alternative is:
string executablePath = Assembly().GetEntryAssembly().Location;
*) The implementation is more complex than this, because it also deals with situations where there are multiple appdomains and other special situations. I simplified the code for the most common situation.

Odd error/bug. Other than using a replace function or extension method to always return the correct format you could try using
System.Reflection.Assembly.GetExecutingAssembly().Location
instead of ExecutablePath.

Related

How to add a second extension to a file name?

I want to create a data file, but before writing to the final file I want to drop it in a temporary location to avoid user confussion. As an example, I could begin with test.txt and want to have test.txt.tmp. The names could include a path, but the files may not necesarily exist (so this question is purely about string manipulation).
The closest I have been is to use Path.ChangeExtension:
string original = "test.txt";
string temp = Path.ChangeExtension(original, "tmp");
But that returns test.tmp instead. So my question is if there is a built-in method to achieve that "dual-extension" file name? I could always use brain-dead string concatenation, but I'm looking for a more safe and tested method.
Avoiding pitfalls is a great idea for things like Path.Combine, e.g. because you don't want to be bothered checking if there is no missing \ character.
But there are no pitfalls here.
If your original filename is as you expect it to be, then string concatenation will work.
If your original file name is not as you expect it to be, then the issue lies with whoever supplied you a faulty filename. "Shit goes in, shit comes out" is not really something your internal logic should worry about. An algorithm can only be as correct as the information that it receives.
String concatenation is perfectly acceptable here. There is no premade method here because there is no real pitfall to simply concatenating the strings.
Special shout out to AlessandroD'Andria's suggestion:
Path.ChangeExtension(original, Path.GetExtension(original) + ".tmp");
Technically, it employs Path logic and therefore fits with your criteria. I genuinely like the cleverness of following your expectations.
However, there is simply no merit to doing so. By its very nature an extension is defined as being "the last part of the filename".
Whether you do a direct string concatenation, or instead do this:
chop the string into two pieces (filename, extension)
append something to the last piece (extension + temp extension)
paste everything together again
The end result will always be the same. The chopping of the string is unnecessary work.
Why can't you append that string just like
if(!string.IsNullOrEmpty(Path.GetExtension(original)){
original+= ".tmp";
}
You should use temp file and rename the extension.
string path = Path.GetTempFileName();
// some logic on the file then rename the file and move it when you need it
string fileName = Path.GetFileName(path);
File.Move(path, path.Replace(fileName, "test.txt"));
If you would use temp file, you can use Path.GetTempFileName();
string tempFileName = Path.GetTempFileName();
Or in your case:
string original = "test.txt";
string temp = "test.txt" + ".tmp";

C# combining absolute and relative paths, Path.GetFullPath() inconsistent

I notice that when I use Path.GetFullPath(Path.Combine(#"c:\folder\","\subfolder\file.txt")) it returns a path c:\subfolder\file.txt, instead of the expected combined path c:\folder\subfolder\file.txt. It seems like having a '\' on the second combined input isn't handled by these methods. Has anyone come across this and found a better solution for merging paths?
var test1 = Path.GetFullPath(Path.Combine(#"C:\users\dev\desktop\testfiles\", #".\test1.txt"));
var test2 = Path.GetFullPath(Path.Combine(#"C:\users\dev\desktop\testfiles\", #"\test2.txt"));//
var test3 = Path.GetFullPath(Path.Combine(#"C: \users\dev\desktop\testfiles\", #"test3.txt"));
var test4 = Path.GetFullPath(Path.Combine(#"C:\users\dev\desktop\testfiles\",#".\XXX\test4.txt"));
var test5 = Path.GetFullPath(Path.Combine(#"C:\users\dev\desktop\testfiles\", #"\XXX\test5.txt"));//
var test6 = Path.GetFullPath(Path.Combine(#"C:\users\dev\desktop\testfiles\", #"XXX\test6.txt"));
var test7 = Path.GetFullPath(Path.Combine(#"C:\users\dev\desktop\testfiles\", #"\somefile\that\doesnt\exist.txt"));//
Results in
test1 is "C:\users\dev\desktop\testfiles\test1.txt"
test2 is wrong "\test2.txt"
test3 is "C: \users\dev\desktop\testfiles\test3.txt"
test4 is "C:\users\dev\desktop\testfiles\XXX\test4.txt"
test5 is wrong "c:\XXX\test5.txt"
test6 is "C:\users\dev\desktop\testfiles\XXX\test6.txt"
test7 is wrong "c:\somefile\that\doesnt\exist.txt"
Basically what I'm doing is combining a source path with individual file paths from a text file so that I can copy these files to a destination, and I want to maintain subfolder hierarchy.
e.g. common scenario of the files list being the same, but wanting to reference a specific source folder version.
source path + individual file path
\\myserver\project\1.x + \dll\important.dll
which should be copied to a destination in the same manner
c:\myproject\ + \dll\important.dll
So there would be variables; source, destination, files[array or list]
My current way forward is to use String.TRIM() method to remove special chars . \ / before combining paths with Path.Combine, Path.GetFullPath. But I thought maybe someone has realized a better way for a similar scenario.
It's also a pain creating the subfolders in the destination, basically im using String.LastIndexOf(#'\') to separate the filename from the foldername. Which is also seems a little sketchy, but a better way is beyond my experience.
Similar question:
Path.Combine absolute with relative path strings
Thanks to the feedback I now know that \file.txt is considered a fully qualified (absolute) path, which is why the combine method works that way.
A file name is relative to the current directory if it does not begin with one of the following:
A UNC name of any format, which always start with two backslash characters ("\\"). For more information, see the next section.
A disk designator with a backslash, for example "C:\" or "d:\".
A single backslash, for example, "\directory" or "\file.txt". This is also referred to as an absolute path.
https://msdn.microsoft.com/en-nz/library/windows/desktop/aa365247(v=vs.85).aspx#fully_qualified_vs._relative_paths
Based on the MSDN Documentation, this is expected behavior.
If path2 does not include a root (for example, if path2 does not start
with a separator character or a drive specification), the result is a
concatenation of the two paths, with an intervening separator
character. If path2 includes a root, path2 is returned.
Seperator characters are discussed in this MSDN article.
So let's take a look at your test cases:
var test1 = Path.GetFullPath(Path.Combine(#"C:\users\dev\desktop\testfiles\", #".\test1.txt"));
var test2 = Path.GetFullPath(Path.Combine(#"C:\users\dev\desktop\testfiles\", #"\test2.txt"));
Test1 is behaving correctly because it is taking the . as part of a valid file path and using it in combination with the first parameter to generate the combined path.
Test2 is behaving as expected because the \ is a separator character, so as described it is returned without the first parameter.
There is no functions that you like to use in .Net framework - trimming path or ensuring that relative path starts with ".\" your real options (probably library that does it exists somewhere).
Why code does not behave the way you like (not "wrong", just not meeting your unexpected expectations).
Combine need to decide which part of left path will stay and which will be replaced. It uses regular file system conventions to decide that (".", "..", "\" have special meaning for file paths)
Meaning of path starting with:
#"\" or"/" - starting from the root of current device - this covers most cases that you don't like
#".\" - means from current directory (hence after combining and normalizing it will behave exactly as you want)
`#"..\" (possible multiple "go up") - go up one folder - this is another case you'll likely disagree with.
any character that is not '/' or #'\' - file name or path from current folder
Also note that path ending on anything but "/" or #"\" is treated by Path.Combine as file path and when combined with other path it will lose last segment.

How do I find the parent directory in C#?

I use this code for finding the debug directory
public string str_directory = Environment.CurrentDirectory.ToString();
"C:\\Users\\Masoud\\Documents\\Visual Studio 2008\\Projects\\MyProj\\MyProj\\bin\\Debug"
How can I find the parent folder as shown below?
"C:\\Users\\Masoud\\Documents\\Visual Studio 2008\\Projects\\MyProj\\MyProj"
You can use System.IO.Directory.GetParent() to retrieve the parent directory of a given directory.
string parent = System.IO.Directory.GetParent(str_directory).FullName;
See BOL
If you append ..\.. to your existing path, the operating system will correctly browse the grand-parent folder.
That should do the job:
System.IO.Path.Combine("C:\\Users\\Masoud\\Documents\\Visual Studio 2008\\Projects\\MyProj\\MyProj\\bin\\Debug", #"..\..");
If you browse that path, you will browse the grand-parent directory.
Edit: The normalization covered in this answer only happens when the path is used to access the file system, but not on the string itself. By contrast, this answer achieves the result and normalization purely using path strings, without using the file system at all.
I've found variants of System.IO.Path.Combine(myPath, "..") to be the easiest and most reliable. Even more so if what northben says is true, that GetParent requires an extra call if there is a trailing slash. That, to me, is unreliable.
Path.Combine makes sure you never go wrong with slashes.
.. behaves exactly like it does everywhere else in Windows. You can add any number of \.. to a path in cmd or explorer and it will behave exactly as I describe below.
Some basic .. behavior:
If there is a file name, .. will chop that off:
Path.Combine(#"D:\Grandparent\Parent\Child.txt", "..") => D:\Grandparent\Parent\
If the path is a directory, .. will move up a level:
Path.Combine(#"D:\Grandparent\Parent\", "..") => D:\Grandparent\
..\.. follows the same rules, twice in a row:
Path.Combine(#"D:\Grandparent\Parent\Child.txt", #"..\..") => D:\Grandparent\
Path.Combine(#"D:\Grandparent\Parent\", #"..\..") => D:\
And this has the exact same effect:
Path.Combine(#"D:\Grandparent\Parent\Child.txt", "..", "..") => D:\Grandparent\
Path.Combine(#"D:\Grandparent\Parent\", "..", "..") => D:\
To get a 'grandparent' directory, call Directory.GetParent() twice:
var gparent = Directory.GetParent(Directory.GetParent(str_directory).ToString());
Directory.GetParent is probably a better answer, but for completeness there's a different method that takes string and returns string: Path.GetDirectoryName.
string parent = System.IO.Path.GetDirectoryName(str_directory);
Like this:
System.IO.DirectoryInfo myDirectory = new DirectoryInfo(Environment.CurrentDirectory);
string parentDirectory = myDirectory.Parent.FullName;
Good luck!
No one has provided a solution that would work cross-form. I know it wasn't specifically asked but I am working in a linux environment where most of the solutions (as at the time I post this) would provide an error.
Hardcoding path separators (as well as other things) will give an error in anything but Windows systems.
In my original solution I used:
char filesep = Path.DirectorySeparatorChar;
string datapath = $"..{filesep}..{filesep}";
However after seeing some of the answers here I adjusted it to be:
string datapath = Directory.GetParent(Directory.GetParent(Directory.GetCurrentDirectory()).FullName).FullName;
You might want to look into the DirectoryInfo.Parent property.
IO.Path.GetFullPath(#"..\..")
If you clear the "bin\Debug\" in the Project properties -> Build -> Output path, then you can just use AppDomain.CurrentDomain.BaseDirectory
Since nothing else I have found helps to solve this in a truly normalized way, here is another answer.
Note that some answers to similar questions try to use the Uri type, but that struggles with trailing slashes vs. no trailing slashes too.
My other answer on this page works for operations that put the file system to work, but if we want to have the resolved path right now (such as for comparison reasons), without going through the file system, C:/Temp/.. and C:/ would be considered different. Without going through the file system, navigating in that manner does not provide us with a normalized, properly comparable path.
What can we do?
We will build on the following discovery:
Path.GetDirectoryName(path + "/") ?? "" will reliably give us a directory path without a trailing slash.
Adding a slash (as string, not as char) will treat a null path the same as it treats "".
GetDirectoryName will refrain from discarding the last path component thanks to the added slash.
GetDirectoryName will normalize slashes and navigational dots.
This includes the removal of any trailing slashes.
This includes collapsing .. by navigating up.
GetDirectoryName will return null for an empty path, which we coalesce to "".
How do we use this?
First, normalize the input path:
dirPath = Path.GetDirectoryName(dirPath + "/") ?? ""; // To handle nulls, we append "/" rather than '/'
Then, we can get the parent directory, and we can repeat this operation any number of times to navigate further up:
// This is reliable if path results from this or the previous operation
path = Path.GetDirectoryName(path);
Note that we have never touched the file system. No part of the path needs to exist, as it would if we had used DirectoryInfo.
To avoid issues with trailing \, call it this way:
string ParentFolder = Directory.GetParent( folder.Trim('\\')).FullName;
To get your solution
try this
string directory = System.IO.Directory.GetParent(System.IO.Directory.GetParent(Environment.CurrentDirectory).ToString()).ToString();
This is the most common way -- it really depends on what you are doing exactly:
(To explain, the example below will remove the last 10 characters which is what you asked for, however if there are some business rules that are driving your need to find a specific location you should use those to retrieve the directory location, not find the location of something else and modify it.)
// remove last 10 characters from a string
str_directory = str_directory.Substring(0,str_directory.Length-10);
You shouldn't try to do that. Environment.CurrentDirectory gives you the path of the executable directory. This is consistent regardless of where the .exe file is. You shouldn't try to access a file that is assumed to be in a backwards relative location
I would suggest you move whatever resource you want to access into a local location. Of a system directory (such as AppData)
In my case I am using .NET 6. When I use:
public string str_directory = Environment.CurrentDirectory.ToString();
returns C:\Projects\MyTestProject\bin\Debug\net6.0
In order to reach C:\Projects\MyTestProject I am using the following:
Directory.GetParent(Directory.GetCurrentDirectory()).Parent.Parent
You can chain Parent to Directory.GetParent(Environment.CurrentDirectory) in order to reach the directory you want.
Final version:
public string str_directory = Directory.GetParent(Environment.CurrentDirectory).Parent.Parent.ToString();

How to get files in a relative path in C#

If I have an executable called app.exe which is what I am coding in C#, how would I get files from a folder loaded in the same directory as the app.exe, using relative paths?
This throws an illegal characters in path exception:
string [ ] files = Directory.GetFiles ( "\\Archive\\*.zip" );
How would one do this in C#?
To make sure you have the application's path (and not just the current directory), use this:
http://msdn.microsoft.com/en-us/library/system.diagnostics.process.getcurrentprocess.aspx
Now you have a Process object that represents the process that is running.
Then use Process.MainModule.FileName:
http://msdn.microsoft.com/en-us/library/system.diagnostics.processmodule.filename.aspx
Finally, use Path.GetDirectoryName to get the folder containing the .exe:
http://msdn.microsoft.com/en-us/library/system.io.path.getdirectoryname.aspx
So this is what you want:
string folder = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName) + #"\Archive\";
string filter = "*.zip";
string[] files = Directory.GetFiles(folder, filter);
(Notice that "\Archive\" from your question is now #"\Archive\": you need the # so that the \ backslashes aren't interpreted as the start of an escape sequence)
Hope that helps!
string currentDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
string archiveFolder = Path.Combine(currentDirectory, "archive");
string[] files = Directory.GetFiles(archiveFolder, "*.zip");
The first parameter is the path. The second is the search pattern you want to use.
Write it like this:
string[] files = Directory.GetFiles(#".\Archive", "*.zip");
. is for relative to the folder where you started your exe, and # to allow \ in the name.
When using filters, you pass it as a second parameter. You can also add a third parameter to specify if you want to search recursively for the pattern.
In order to get the folder where your .exe actually resides, use:
var executingPath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
As others have said, you can/should prepend the string with # (though you could also just escape the backslashes), but what they glossed over (that is, didn't bring it up despite making a change related to it) was the fact that, as I recently discovered, using \ at the beginning of a pathname, without . to represent the current directory, refers to the root of the current directory tree.
C:\foo\bar>cd \
C:\>
versus
C:\foo\bar>cd .\
C:\foo\bar>
(Using . by itself has the same effect as using .\ by itself, from my experience. I don't know if there are any specific cases where they somehow would not mean the same thing.)
You could also just leave off the leading .\ , if you want.
C:\foo>cd bar
C:\foo\bar>
In fact, if you really wanted to, you don't even need to use backslashes. Forwardslashes work perfectly well! (Though a single / doesn't alias to the current drive root as \ does.)
C:\>cd foo/bar
C:\foo\bar>
You could even alternate them.
C:\>cd foo/bar\baz
C:\foo\bar\baz>
...I've really gone off-topic here, though, so feel free to ignore all this if you aren't interested.
Pretty straight forward, use relative path
string[] offerFiles = Directory.GetFiles(Server.MapPath("~/offers"), "*.csv");

C# - retrieve file path from config file - # doesn't do it's magic

I'm currently working on a web service that retrieves an XML message, archives it and then processes it further. The archive folder is read from the Web.config. This is what the archive method looks like
private void Archive(System.Xml.XmlDocument xmlDocument)
{
try
{
string directory = System.Configuration.ConfigurationManager.AppSettings.Get("ArchivePath");
ParseMessage(xmlDocument);
directory = string.Format(#"{0}\{1}\{2}", directory, _senderService, DateTime.Now.ToString("MMMyyyy"));
System.IO.Directory.CreateDirectory(directory);
string Id = _messageID;
string senderService = _senderService;
xmlDocument.Save(directory + #"\" + DateTime.Now.ToString("yyyyMMdd_") + Id + "_" + System.Guid.NewGuid().ToString().Substring(0, 13) + ".xml");
}
The path structure I retrieve is C:\Program Files\Subfolder\Subfolder. In the development, QA, UAT and PRD environments everything works fine. But on another machine I now need to install the web service on (which I cannot debug, unfortunately), the directory string is 'C:Files'.
Just to be sure I double checked the .NET version on the different machines (I thought perhaps the usage of # before a string was version-dependent); all machines use 2.0.50727.
Does anyone recognize this problem?
Thanks in advance!
EDIT: I see the # before the directory variable has caused some confusion regarding the question I asked. It was not about that # (in fact, that should not have been there. I have removed it).
My question (rephrased) is:
when you place an # before a quoted string, like #"c:\folder\subfolder", it ensures that the backslashes are not interpreted as escape characters, right? What could be the cause of it working on one machine, but not working on another?
(I do agree with the answers stating to use Path.Combine by the way. I'm just curious what causes this inconsistent behaviour)
You could try using Path.Combine() instead of String.Format(). A good example is here.
When a value is pulled from the configuration file, it is automatically escaped properly. The '#' symbol on your directory variable name is not setting it to be 'explicit' - it's tell the compiler that it is a named parameter. For example:
public void (string[] args)
{
int length = args.Length;
length = #args.Length; // Same thing!
}
The '#' operator on a variable name means to not treat that symbol as a reserved word. It allows you to make variable names with the same name as a keyword:
public static void Foo(object #class)
{
//#class exists here, even though class is a reserved keyword!
}
In addition, if the value it is getting is 'C:Files', then that is invalid, as it is missing a '\'. 'C:\Files' would be valid.
Use Path.Combine(), for instance:
strFilename = CombinePaths(directory, _senderService) + DateTime.Now.ToString("MMMyyyy");
From your question I think that you've treated:
#directory
As if it performed the same function as:
#"c:\myfolder\"
The difference is that the first example allows you to use a reserved word as a variable name, like #class (don't get into the habit of using it) and the second example allows the string to contain unescaped characters such as .

Categories