10

Got an annoying problem here. I've got an NHibernate/Forms application I'm working through SVN. I made some of my own controls, but when I drag and drop those (or view some form editors where I have already dragged and dropped) onto some of my other controls, Visual studio decides it needs to execute some of the code I wrote, including the part that looks for hibernate.cfg.xml.

I have no idea why this is, but (sometimes!) when it executes the code during my form load or drag and drop it switches the current directory to C:\program files\vs 9.0\common7\ide, and then nhibernate throws an exception that it can't find hibernate.cfg.xml, because I'm searching for that in a relative path.

Now, I don't want to hard code the location of hibernate.cfg.xml, or just copy hibernate.cfg.xml to the ide directory (which will work). I want a solution that gets the solutions directory while the current directory is common7\ide. Something that will let someone view my forms in the designer on a fresh checkout to an arbitrary directory on an arbitrary machine. And no, I'm not about to load the controls in code. I have so many controls within controls that it is a nightmare to line everything up without it.

I tried a pre build event that made a file that has the solution directory in it, but of course how can I find that from common7\ide? All the projects files need to be in the solution directory because of svn.

Thanks for your help guys, I've already spent a few hours fiddling with this in vain.

UPDATE: I set hibernate.cfg as an embedded resource. For each configuration I just simply make a new build configuration, debug, release, XYZ. In most cases I'd recommend embedding any files you depend on to run the program. It makes it much simpler to build an installer.

Isaac Bolinger
  • 7,328
  • 11
  • 52
  • 90

6 Answers6

18

This is probably coming a little bit late, but I've found a solution at http://www.tek-tips.com/viewthread.cfm?qid=1226891&page=164. Since I am using Visual Studio 2010, I made a few minor changes. You have to reference the EnvDTE and EnvDTE100 (EnvDTE90 for VS2008),

string solutionDirectory = ((EnvDTE.DTE)System.Runtime
                                              .InteropServices
                                              .Marshal
                                              .GetActiveObject("VisualStudio.DTE.10.0"))
                                   .Solution
                                   .FullName;
solutionDirectory = System.IO.Path.GetDirectoryName(solutionDirectory);

Of course I used VisualStudio.DTE.10.0, you should probably use VisualStudio.DTE.9.0.

Good luck!

Anh-Kiet Ngo
  • 2,151
  • 1
  • 14
  • 11
  • This does find the solution directory alright, but my problem is a little more specific. Sometime during the loading of the form designer, the current directory changes to program files\common7\ide. I need code that finds the original solution directory during this time, and this code doesn't do it. It does work otherwise, though! – Isaac Bolinger Aug 25 '10 at 20:48
  • 2
    Is it possible for you to set this solution directory inside of a static variable before you get into the form designer? App load -> set directory -> form designer -> pull from static variable. Or would this be too much of a hack? – Anh-Kiet Ngo Aug 25 '10 at 22:53
  • Oh I didn't think of this! I"m not sure if it ever does go to the regular solution directory even before common7/ide, but if it does, then this wouldn't be a hack, it would be a solution! I'll check it out. – Isaac Bolinger Aug 27 '10 at 21:41
  • Alas, that didn't work either. I tried to get the solution directory on the first program line, too :( Good idea, though! – Isaac Bolinger Aug 27 '10 at 21:53
  • 1
    Works in VS2013 too, just change the above similar line to. .GetActiveObject("VisualStudio.DTE.12.0"); – Isaac Bolinger Feb 12 '15 at 19:40
  • 1
    One problem I've found is you can't count on it working with multiple visual studio instances open (using different solutions) – Isaac Bolinger Feb 12 '15 at 20:29
10

I've finally figured this one out. This will work for any Visual Studio version, does not rely on EnvDTE, and solves the original problem presented here.

  1. In your project settings, under "Build Events", add the following "Pre-build event command line":

    echo $(SolutionDir) > ..\..\solutionpath.txt
    
  2. Build the project once. The file will be created in your project root.

  3. In solution explorer, click "Show All Files" and "Refresh"

  4. Add solutionpath.txt to your solution

  5. Right click solutionpath.txt, click properties. Change your build action to "Embedded Resource"

  6. Use the following code to get your solution path.

        string assemblyname = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name;
        string path = "";
        using (var stream = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream(assemblyname + ".solutionpath.txt"))
        {
            using (var sr = new StreamReader(stream))
            {
                path = sr.ReadToEnd().Trim();
            }
        }
    

Because this is a pre-build event, the file does not need to exist before the build is started, so it is compatible with source-control and doesn't have any obvious issues.

marknuzz
  • 2,847
  • 1
  • 26
  • 29
  • 2
    IT works: just my 50 coins: `echo $(SolutionDir) > $(ProjectDir)\solutionpath.txt` `string assemblyName = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name` – Fantastory Apr 22 '15 at 12:09
  • Good trick. I actually used it to output a partial class with a constant holding this path. This was to run IIS express in a test suite, where you need to know the path to the applicationHost.config file, which is rooted in the solution directory. – naasking Jan 11 '21 at 20:03
  • @naasking Thanks, I still use this trick myself on occasion. – marknuzz Jan 11 '21 at 21:25
3

Update: Unfortunately, i don't know how of a way to get your solution folder at design time. So, technically, I am not answering your question, just offering a potential workaround.

You can check if your control is in DesignMode and if it is, you can use Assembly.GetExecutingAssembly() to get the Assembly for your control and determine the location it was loaded from.

Note that there are some caveats with the DesignTime property value, namely if your designing your control or if you are designing a Form that contains your control, it'll return properly true, but if you are designing a form that contains a control that contains your control, it'll retun false.

You might want to skip the whole DesignTime check because of this and always look for the NHibernate config in the base path of your assembly, if your standard way to find that config file fails.

Franci Penov
  • 74,861
  • 18
  • 132
  • 169
  • 'C:\Users\Administrator\AppData\Local\Microsoft\VisualStudio\9.0\ProjectAssemblies\raa4j4oa01\dllname.dll' This is what I'm getting from Assembly.Location Unless there's an assembly method that gets the original dll location? Do you know of one? – Isaac Bolinger Jun 17 '10 at 00:14
  • Argh. So VS creates a temporary assembly with your control. :-( Unfortunately, I am not sure how to work around this particular problem. – Franci Penov Jun 17 '10 at 00:27
  • Thanks for your help anyhow! I appreciate you spending the time. – Isaac Bolinger Jun 17 '10 at 00:44
2

It sounds like you just need to code a better path to your configuration file.

If you do something like this:

configPath =  Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "\\PathToCFG");

you should not be messed up when Windows changes the current directory on you.

Edit: You may be running into an issue with the Visual Studio hosting process. Can you disable this? There is a check box under project properties \ debug.

Scott P
  • 3,775
  • 1
  • 24
  • 30
  • This does not work, it just gives me C:\program files\vs9\common7\ide\PathToCFG I tried this from the NHibernate faq too. – Isaac Bolinger Jun 17 '10 at 00:00
  • I added a tip to disable the hosting process. Maybe this will provide you a workaround? – Scott P Jun 17 '10 at 01:13
  • 1
    this answer is great for me cause I needed to get path to directory which is in the project base directory! just combining the relative path with base directory! and it works perfectly!!! thanks! – Aleksei Chepovoi Feb 02 '13 at 11:17
2

I'm way late, and I have no idea if this works in VS2008, but for VS2015, the simplest thing I could think to do was create a t4 template. Here's the contents:

<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ output extension=".generated.cs" #>
namespace MyNamespace
{
    public static class BuildVariables
    {
        public static readonly string SolutionDir = <#= QuotedString(Host.ResolveAssemblyReference(@"$(SolutionDir)")) #>;
    }
}
<#+
public string QuotedString(string value)
{
    if (value == null)
    {
        return "null";
    }

    return "@\"" + value.Replace("\"", "\"\"") + "\"";
}
#>

This generates a class that's has exactly what's needed. The two warnings I have for this are to be sure to ignore it from version control, and watch out if you copy your solution somewhere else. Unfortunately, building t4 templates as part of an automated build process is still an complex problem in 2018.

Timothy
  • 469
  • 5
  • 8
  • This worked best for me because I needed the solutiondir changed to its correct value when our CI server built the unit tests rather than building locally. – Ed Bayiates Jul 10 '19 at 22:36
  • This worked for me in VS2019. I needed it because my unit test runners have different directories. Very cool technique. – aboy021 Oct 28 '21 at 01:51
2

I ended up embedding the config file. Since the program auto updates independently of the config file I could give up exposing it to the users.

Isaac Bolinger
  • 7,328
  • 11
  • 52
  • 90
  • +1 because in this specific case (but not mine), embedding the config file would be a simpler solution – marknuzz Dec 20 '13 at 10:40