11

I'm writing a .Net WinForms on and constantly switching between DEBUG and RELEASE configurations and have a few files I need either configuration to be able to get to.

What I was thinking to do was to put the files in a common directory in the BIN folder so it would look like this:

MyProject/Bin/CommonFiles
MyProject/Bin/Debug
MyProject/Bin/Release

And I was thinking about accessing the files using something along the lines of:

System.IO.Directory.GetParent(System.IO.Directory.GetCurrentDirectory).FullName

My question is if this is dangerous since, from what I've read, System.IO.Directory.GetCurrentDirectory might change due to the user selecting a new current directory in, say, an open file dialogue box.

Should I rather use something along the lines of:

System.IO.Directory.GetParent(Environment.CurrentDirectory).FullName

OR is there an even better way to get to the /Bin folder so I can move from there or a generally accepted way / location to store files the program usually needs to reach and way to reference that more easily (maybe something that's made to work with any kind of app and not just WinForms)?

John Bustos
  • 19,036
  • 17
  • 89
  • 151
  • 1
    The bin directory is an implementation detail of the VS build system. Since it is unlikely you are going to ship your project with source code to the customer and you are not going to be able to save files into c:\program files\bin, this is not a good practice. Use %appdata% for writable files, your EXE directory or %programdata% for read-only files. – Hans Passant Nov 14 '13 at 17:04
  • Thanks @HansPassant... As I told Christian below, I'm slightly hesitant to constantly copy them to the appdata folder on each user's computer since many users will only need to use this program once and it's on a shared drive in my organization at this point... Any thoughts about that?? – John Bustos Nov 14 '13 at 17:19

5 Answers5

14

You can get the .exe location of your app with System.Reflection.Assembly.GetExecutingAssembly().Location.

string exePath = System.Reflection.Assembly.GetExecutingAssembly().Location;
string exeDir = System.IO.Path.GetDirectoryName(exePath);
DirectoryInfo binDir = System.IO.Directory.GetParent(exeDir);
Tim S.
  • 55,448
  • 7
  • 96
  • 122
  • 2
    If an assembly is shadow-copied, Assembly.Location contains the location after being shadow-copied. A better solution is to use AppDomain.BaseDirectory: http://stackoverflow.com/a/6041505/13087 – Joe Nov 14 '13 at 15:42
7

I would do it as follows:

  • Create a subfolder for the files in your project, e.g. "CommonFiles".

  • Put the config files in this folder. Set their properties to:

    • Build Action = Content

    • Copy to output directory = Copy always (or maybe: Copy if newer)

  • The files will then be copied to bin\Debug\CommonFiles or bin\Release\CommonFiles each time your application is built. You can reference them as:

    Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "CommonFiles\MyFile.Dat");
    
Joe
  • 122,218
  • 32
  • 205
  • 338
  • This seems like the smartest idea... Both for all these files we'll need and also for .dlls needed... Now, I know this might seem strange, but there are probably about 50 or so files in 10 or so different directories I would need to do this for (based upon user input, it opens a specific file)... Is there a way to change these properties for that entire directory at once, or would I have to do this file-by-file... OR, given this new bit of data, is there a different way you would recommend?? THANKS!!! – John Bustos Nov 14 '13 at 15:57
  • I think this technique is appropriate for a relatively small number of readonly configuration files, with all files existing at build time. If they are read/write files and/or users can add additional files then I wouldn't do this. Instead put the files in a suitable location outside the bin folder (one that the user can write to if they are read/write files), and add a configuration setting to your app.config with the directory that contains the files. – Joe Nov 14 '13 at 16:35
  • Thanks, Joe - They're not writable, just templates that get saved to the user machine... Just A LOT of different templates and for different purposes, so in sub-folders.... You would still say to do that with the app.config?? How would you suggest that best be done (maybe @Tim's solution with your added suggestion / edit??? - Thanks again for your continued help!! – John Bustos Nov 14 '13 at 16:48
  • Joe, I found a solution to make your work... I'm posting it as a separate solution to show people what I did, but I do think you had the best solution once I found out how to do the necessary tasks... THANKS!!! – John Bustos Nov 14 '13 at 21:14
5

Although most of the answers did actually seem to work, the solution offered by Joe above, after quite a bit of tweaking, proved to be the best solution for my situation.


Firstly, the final solution I went with:

  • Created a Common Files directory (In my case, at the same level as my Bin directory, but that's not necessary, it'll just explain how the code I'll put up later works)
  • Edited my .vbproj (or `,csproj') file to set all the files in that directory (including sub-directories) as extra resource files for my project
  • Made a second edit to my .vbproj file to copy all those resource files to my bin\Debug or bin\Release directory at build time.

Secondly, how I learned to do this:

  • Joes solution above
  • This link shows how to be able to add in wildcards to the .vbproj / .csproj files so you can add in full directories at once.
  • This answer which shows how to copy the files to my \Bin Directory

Finally, my solution:

  • I opened my project.vbproj file
  • Above the line <Import Project="$(MSBuildToolsPath)\Microsoft.VisualBasic.targets" />, I added in one new ItemGroup looking as follows:

<ItemGroup> <CommonFiles Include="Common Files\**" /> </ItemGroup>

This loads in all the files (including all sub-folders) and gives I the Build Action CommonFiles

  • Beneath the line <Import Project="$(MSBuildToolsPath)\Microsoft.VisualBasic.targets" />, I added in the following lines:

    <PropertyGroup> <PrepareForRunDependsOn>$(PrepareForRunDependsOn);CopyCommonFiles</PrepareForRunDependsOn> </PropertyGroup>

<Target Name="CopyCommonFiles"> <Copy SourceFiles="@(CommonFiles)" DestinationFolder="$(OutDir)\%(RecursiveDir)" SkipUnchangedFiles="true"/> </Target>

This will copy the files from the CommonFiles build action into your bin\Debug / bin\Release directories at build time.

And that's how I did it....

ANY comments / thoughts are really greatly appreciated )if you think I should have used another way or anything else)....

this

Community
  • 1
  • 1
John Bustos
  • 19,036
  • 17
  • 89
  • 151
1

If you have configuration data etc. I would store them in the %Appdata% directory. Putting files which might be changed into the installation directory is not a good Idea and is forbidden since Windows Vista. Configuration data should be stored in the %Appdata% folder, which is a special folder. DO NOT hardcode its path into your program - Users from foreign countries or 32/64bit Windowses will have severe problems.

A good idea how to access the Appdata-Folder can you see here: How to create appdata folder with C#

Community
  • 1
  • 1
Christian Sauer
  • 10,351
  • 10
  • 53
  • 85
  • 1
    This isn't really configuration data - This is more files the program needs at runtime... I'm slightly hesitant to constantly copy them to the appdata folder on each oser's computer since many users will only need to use this program once and it's on a shared drive in my organization at this point... Any thoughts about that?? – John Bustos Nov 14 '13 at 15:59
  • 1
    @JohnBustos That is definitely a valid point. I have a very similar Coniguration at work and simply stored a Path in the Proerties of the program. Since it is stored as a setting, it is easy to build a small screen which allows the user to select a new file location. I am hesitant to hardcode the path - it may change in the future. – Christian Sauer Nov 14 '13 at 18:02
0

Following code is from http://msdn.microsoft.com/en-us/library/ms229654(v=vs.90).aspx

    String strAppDir = Path.GetDirectoryName(
        Assembly.GetExecutingAssembly().GetName().CodeBase);
    String strFullPathToMyFile = Path.Combine(strAppDir, "fileName.txt");

    MessageBox.Show(String.Format("Path to the application is: '{0}'." +
        "Full path to the file in the application folder is: '{1}'",
        strAppDir, strFullPathToMyFile));
user2880486
  • 1,068
  • 7
  • 16