当创建自定义MSBuild任务时,如何从C#代码中获取当前项目目录?

233

我希望获取当前项目目录而不是在代码中直接写入外部程序路径。我通过自定义任务中的进程来调用外部程序。

如何实现呢?使用AppDomain.CurrentDomain.BaseDirectory仅会返回VS 2008的位置。


嘿 Sean,我觉得在问题标题中提到 MSBuild 是一个转移注意力的话题。这个问题似乎是关于如何通过编程获取项目目录,也就是源代码目录的问题(所有答案都理解为此)。我想编辑一下问题以反映这一点,但我想先确认一下你的意见。 - Mike Nakis
嗨,Mike。提到ProjectDir应该就足够了。但幸运的是,我提到了“自定义构建任务”,因为我已经找到了这个项目,并且看起来需要获取/Scripts文件夹以压缩js文件的任务。奇怪的是,路径仍然是硬编码的。 - sean
在这种情况下,我认为“自定义构建任务”甚至可以在问题结尾处的“为了更好的理解,这是我想要做的事情:”注释中提到。但是,由于现在我不清楚你想要什么,所以我不会干涉。 - Mike Nakis
28个回答

434
using System;
using System.IO;

// This will get the current WORKING directory (i.e. \bin\Debug)
string workingDirectory = Environment.CurrentDirectory;
// or: Directory.GetCurrentDirectory() gives the same result

// This will get the current PROJECT bin directory (ie ../bin/)
string projectDirectory = Directory.GetParent(workingDirectory).Parent.FullName;

// This will get the current PROJECT directory
string projectDirectory = Directory.GetParent(workingDirectory).Parent.Parent.FullName;

39
对 Directory.GetParent() 加上 +1,以避免获取到 \bin\Debug 目录 :) - Eystein Bye
12
如果我们使用自定义目标CPU会怎样?例如,如果我将构建设置为目标x64,则会在它们之间创建另一个文件夹。 - Samir Aguiar
9
这是正确的答案。接受的答案返回到bin目录的路径,这不是项目目录。 - pookie
5
这个答案是错误的,因为它对项目配置做了一些假设,所以它可能适用于某些项目,但不适用于其他项目。此外,所有那些评论说“这是正确的答案”的人都是错的;他们最好能说的是“它对我有用”,而且它只对他们有用是因为月亮处于正确的相位,行星排列得当。更糟糕的是,如果您更改项目配置,这个答案将停止为您工作,您将没有任何警告或指示原因。 - Mike Nakis
3
为了获取项目文件路径,我们应该使用Directory.GetParent(AppDomain.CurrentDomain.BaseDirectory).Parent.Parent.Parent.FullName。 - FreePhoenix888
显示剩余5条评论

151

您可以尝试以下两种方法之一。

string startupPath = System.IO.Directory.GetCurrentDirectory();

string startupPath = Environment.CurrentDirectory;
告诉我,你觉得哪个更好。

113
上述两点指向了bin目录,因此如果您的整个解决方案只有一个bin目录,它将指向那里,而不是指向您的项目目录(或者在您的项目目录下两级)。 - matcheek
21
当使用测试资源管理器时,这两种解决方案都无法按预期工作。 - Gucu112
2
我想补充一下,这实际上是当前的答案。我尝试了另一个答案,但它会将我的文件放在 .NET 5.0 中当前工作目录的两个级别之上。我不得不回到这个答案中,才能将文件放置在我的当前项目目录中。因此,在我的控制台 .NET 5 应用程序中,这确实是正确的答案。 - Jay
两者都给了我这个位置: c:\windows\system32\inetsrv - Khalid Bin Sarower

91
如果项目在IIS Express上运行,Environment.CurrentDirectory可能指向IIS Express的位置(默认路径为C:\Program Files (x86)\IIS Express),而不是项目所在的位置。
这很可能是各种类型项目最适合使用的目录路径。
AppDomain.CurrentDomain.BaseDirectory

这是MSDN的定义。

获取程序集解析程序用于探测程序集的基目录。


40
9年后,有人真正找到了答案。 - Jeff Davis
.NET Core 中没有 AppDomain。你需要像这样做:System.Runtime.Loader.AssemblyLoadContext.Default.Unloading += context => InvokeBatchProcessors(); - Latency
2
在一个 .net core 3 WPF 项目中存在的延迟。 - Alexander
这在调试模式下不起作用,因为它将/bin/debug删除了。 - Anubix
1
此答案未提供项目目录。项目目录是您的项目文件所在的位置。此答案提供了您项目二进制文件所在的目录,这绝对不是您的项目文件所在的目录。 - Mike Nakis
显示剩余4条评论

51

获取C#项目根目录的正确1方法是利用[CallerFilePath]属性获取源文件的完整路径名,然后从中减去文件名加扩展名,留下项目路径。

以下是实际上的做法:

在您的项目根目录中,添加名为ProjectSourcePath.cs的文件,并将以下内容添加到其中:

internal static class ProjectSourcePath
{
    private const  string  myRelativePath = nameof(ProjectSourcePath) + ".cs";
    private static string? lazyValue;
    public  static string  Value => lazyValue ??= calculatePath();

    private static string calculatePath()
    {
        string pathName = GetSourceFilePathName();
        Assert( pathName.EndsWith( myRelativePath, StringComparison.Ordinal ) );
        return pathName.Substring( 0, pathName.Length - myRelativePath.Length );
    }
}

如果您的C#版本不够新,没有#nullable enable功能,那么字符串string?就需要移除?

Assert()函数是我自己写的,你可以用你自己的函数替换它,或者如果你喜欢冒险的话,也可以省略它。

函数GetSourceFilePathName()的定义如下:

using System.Runtime.CompilerServices

    public static string GetSourceFilePathName( [CallerFilePath] string? callerFilePath = null ) //
        => callerFilePath ?? "";

一旦您具备上述条件,您可以按以下方式使用它:

string projectSourcePath = ProjectSourcePath.Value;

1 "proper" 指的是:防错的、百分百可靠的、没有任何假设的、不是凑合着用的,没有用绳带捆扎在一起的、不仅对某些项目有效,对其他项目也无效的、不太可能因为你改变无关事项而突然出现严重故障等等。


2
这很好。干得好,谢谢。 - Alan Macdonald
4
当然可以,但我这样做会得到什么好处呢?如果您查看Directory.GetParent()的源代码,它会执行大量奇怪的操作,包括路径规范化(这是不必要的,因为CallerFilePath已经产生了规范化的路径),在所有这些奇怪的操作之后,它进行了一个低效的反向线性搜索以查找最后一个分隔符的索引(而我已经知道是pathName.Length-myRelativePath.Length),最后只是调用了Substring()。那么,为什么不直接使用Substring()呢? - Mike Nakis
2
@MikeNakis 嗯,不知道所有的情况,我认为 GetParent 看起来更好,因为你不必计算子字符串;但是你说了那么多,Substring 更有意义。 - NotAPro
3
我知道这篇文章很老了,但是我认为它值得提一下:只有在编译项目的计算机上才能使用这种方法。也许这在原始问题中是隐含的,但我无法确定。无论如何,对于将来的搜索者,请注意这将在编译过程中内联文件在计算机上的实际绝对路径,因此不是可移植的。 - Thomas
1
这是唯一可行的解决方案,用于在使用另一个 Visual Studio 实例调试 Visual Studio 实例时识别文件。 - Simone
显示剩余4条评论

30

这也将通过从当前执行目录向上导航两个级别来为您提供项目目录(这不会为每次构建返回项目目录,但这是最常见的情况)。

System.IO.Path.GetFullPath(@"..\..\")

当然,您会希望将此内容包含在某种验证/错误处理逻辑内。


在我看来,这是最灵活的方法。我在单元测试和集成测试中使用它,其中路径实际上比一个文件夹更深。 - Soleil
因为某些原因,这给了我根驱动器。 - Captain Prinny

13

如果你想知道你的解决方案所在的目录,你需要执行以下操作:

 var parent = Directory.GetParent(Directory.GetCurrentDirectory()).Parent;
            if (parent != null)
            {
                var directoryInfo = parent.Parent;
                string startDirectory = null;
                if (directoryInfo != null)
                {
                    startDirectory = directoryInfo.FullName;
                }
                if (startDirectory != null)
                { /*Do whatever you want "startDirectory" variable*/}
            }

如果你只使用GetCurrrentDirectory()方法,无论是调试还是发布,你都会得到构建文件夹。希望这能帮到你!如果你忘记了验证,它将像这样:

var startDirectory = Directory.GetParent(Directory.GetCurrentDirectory()).Parent.Parent.FullName;

9

根据Gucu112的答案,但是对于.NET Core控制台/窗口应用程序,应该是这样的:

string projectDir = 
    Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"..\..\.."));

我在一个.NET Core窗口应用程序的xUnit项目中使用了这个。

8

如果您确实想要确保获取源项目目录,无论bin输出路径设置为什么:

  1. 添加一个预建事件命令行(Visual Studio:项目属性 -> 生成事件):

    echo $(MSBuildProjectDirectory) > $(MSBuildProjectDirectory)\Resources\ProjectDirectory.txt

  2. ProjectDirectory.txt文件添加到项目的Resources.resx中(如果尚不存在,请右键单击项目 -> 添加新项 -> 资源文件)

  3. 通过Resources.ProjectDirectory从代码中访问。

2
这是这里唯一的好答案。由于我正在使用目录进行单元测试,所以我这样做:echo $(ProjectDir)> $(ProjectDir)\ Input \ ProjectDir.txt 然后将ProjectDir.txt包含为单元测试部署项。无论测试在哪里运行,我现在都有构建文件夹。 - Ed Bayiates

7
这个解决方案对我来说效果很好,在开发、测试和生产服务器上都能用,使用了C#的ASP.NET MVC5
var projectDir = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory);

如果您需要在项目配置文件中使用项目目录,请使用以下代码:
$(ProjectDir)

5

我曾遇到类似的情况,在无果的谷歌搜索后,我声明了一个公共字符串,修改调试/发布路径的字符串值以获取项目路径。使用此方法的好处是,由于它使用当前项目的目录,所以无论您是从调试目录还是发布目录工作,都不会有影响:

public string DirProject()
{
    string DirDebug = System.IO.Directory.GetCurrentDirectory();
    string DirProject = DirDebug;

    for (int counter_slash = 0; counter_slash < 4; counter_slash++)
    {
        DirProject = DirProject.Substring(0, DirProject.LastIndexOf(@"\"));
    }

    return DirProject;
}

然后,您只需使用一行代码即可随时调用它:

string MyProjectDir = DirProject();

这应该在大多数情况下有效。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接