【问题标题】:How can I convert Assembly.CodeBase into a filesystem path in C#?如何将 Assembly.CodeBase 转换为 C# 中的文件系统路径?
【发布时间】:2010-11-05 15:56:29
【问题描述】:

我有一个项目将模板存储在 DLL 和 EXE 旁边的 \Templates 文件夹中。

我想在运行时确定这个文件路径,但使用一种可以在单元测试和生产环境中工作的技术(我不想在 NUnit 中禁用卷影复制!)

Assembly.Location 不好,因为它在 NUnit 下运行时返回影子复制程序集的路径。

Environment.CommandLine 的用途也有限,因为在 NUnit 等中它返回 NUnit 的路径,而不是我的项目。

Assembly.CodeBase 看起来很有希望,但它是一条 UNC 路径:

file:///D:/projects/MyApp/MyApp/bin/debug/MyApp.exe

现在我可以使用字符串操作将其转换为本地文件系统路径,但我怀疑有一种更简洁的方法可以将其隐藏在 .NET 框架的某个地方。任何人都知道这样做的推荐方法吗?

(在这种情况下,如果 UNC 路径不是 file:/// URL,则抛出异常是绝对可以的)

【问题讨论】:

标签: c# .net nunit codebase


【解决方案1】:

你需要使用 System.Uri.LocalPath:

string localPath = new Uri("file:///D:/projects/MyApp/MyApp/bin/debug/MyApp.exe").LocalPath;

所以如果你想要当前正在执行的程序集的原始位置:

string localPath = new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath;

LocalPath 包括程序集的文件名,例如,

D:\projects\MyApp\MyApp\bin\debug\MyApp.exe

如果你想要程序集的目录,那么使用 System.IO.Path.GetDirectoryName():

string localDirectory = Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath);

这会给你:

D:\projects\MyApp\MyApp\bin\debug

【讨论】:

  • 请注意,当目录名称中包含“#”时,这不起作用
  • 这里的关键是使用Uri.LocalPath - 我们有一个错误,我们使用Uri.AbsolutePath,这似乎工作,直到你在路径名中有一个空格!
  • 实际上,如果你有一个包含%20的路径,即使LocalPath也行不通。 (这可能是可以接受的......包含任何%xxescape 的本地路径相当罕见。请参阅其他问题的评论:stackoverflow.com/questions/52797/…
  • 注意:即使使用EscapedCodeBase,对于某些用例,此答案可能不正确。请参阅下面@MartinBa 的答案了解原因,如果您重视正确性而不是易于开发,请使用他的解决方案。
  • 真正的宝石。出于某种原因,Assembly.GetCallingAssembly().CodeBase 开始返回不同的结果,或者 Path.GetDirectoryName() 失去了解析该结果的能力。很奇怪,但至少有这个解决方案。
【解决方案2】:

Assembly.CodeBase 看起来很有希望,但它是一条 UNC 路径:

请注意,它近似于 file uri不是 UNC path


您可以通过手动操作字符串来解决这个问题。 说真的。

尝试使用以下目录(逐字)在 SO 上找到的所有其他方法:

C:\Test\Space( )(h#)(p%20){[a&],t@,p%,+}.,\Release

这是一个有效的 Windows 路径,虽然有些不寻常。 (有些人在其中的路径中有这些字符之一,你希望你的方法适用于所有,对吧?)

然后可用的代码库 (we do not want Location, right?) 属性是(在我的带有 .NET 4 的 Win7 上):

assembly.CodeBase -> file:///C:/Test/Space( )(h#)(p%20){[a&],t@,p%,+}.,/Release

assembly.EscapedCodeBase -> file:///C:/Test/Space(%20)(h%23)(p%20)%7B%5Ba%26%5D,t@,p%,+%7D.,/Release

你会注意到:

  • CodeBase 根本没有转义,它只是以 file:/// 为前缀的常规本地路径,并替换了反斜杠。因此,将其提供给System.Uri不起作用
  • EscapedCodeBase 没有完全转义(我确实知道这是错误还是 URI scheme 的缺点):
    • 注意空格字符 () 如何转换为 %20
    • %20 序列转换为%20! (百分比% 根本没有转义)
    • 没有人可以用这种损坏的形式重建原件!

对于本地文件(这就是我真正关心 CodeBasestuff 的全部内容,因为如果文件不是本地文件,您可能还是想使用 .Location,以下对我有用(请注意它也不是最漂亮的:

    public static string GetAssemblyFullPath(Assembly assembly)
    {
        string codeBasePseudoUrl = assembly.CodeBase; // "pseudo" because it is not properly escaped
        if (codeBasePseudoUrl != null) {
            const string filePrefix3 = @"file:///";
            if (codeBasePseudoUrl.StartsWith(filePrefix3)) {
                string sPath = codeBasePseudoUrl.Substring(filePrefix3.Length);
                string bsPath = sPath.Replace('/', '\\');
                Console.WriteLine("bsPath: " + bsPath);
                string fp = Path.GetFullPath(bsPath);
                Console.WriteLine("fp: " + fp);
                return fp;
            }
        }
        System.Diagnostics.Debug.Assert(false, "CodeBase evaluation failed! - Using Location as fallback.");
        return Path.GetFullPath(assembly.Location);
    }

我相信人们可以想出更好的解决方案,如果它是本地路径,甚至可以想出一个解决方案,对 CodeBase 属性进行正确的 URL 编码/解码,但考虑到可以剥离关闭file:/// 并完成它,我会说这个解决方案已经足够好了,如果当然真的很丑。

【讨论】:

  • 如果是 UNC 路径,则为 file://server/share/path/to/assembly.dll(只有 2 个斜杠)。
  • @Joshua - 你的意思是说\\server\... 路径只用两个斜杠编码,而不是在有本地驱动器时使用 3 个?
  • @Joshua - 你知道\\?\UNC\server\share 路径是如何编码的吗?
  • 这太棒了,但是有什么特别的原因你会替换 / 到 \ 吗?这不会只会导致其他平台上的单声道问题吗?
  • @BjarkeCK 你可以使用 Path.DirectorySeparatorChar 来缓解这个问题。
【解决方案3】:

这应该可行:

ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();
Assembly asm = Assembly.GetCallingAssembly();
String path = Path.GetDirectoryName(new Uri(asm.EscapedCodeBase).LocalPath);

string strLog4NetConfigPath = System.IO.Path.Combine(path, "log4net.config");

我正在使用它来使用独立的 log4net.config 文件从 dll 库中登录。

【讨论】:

    【解决方案4】:

    另一种解决方案,包括复杂路径:

        public static string GetPath(this Assembly assembly)
        {
            return Path.GetDirectoryName(assembly.GetFileName());
        }
    
        public static string GetFileName(this Assembly assembly)
        {
            return assembly.CodeBase.GetPathFromUri();
        }
    
        public static string GetPathFromUri(this string uriString)
        {
            var uri = new Uri(Uri.EscapeUriString(uriString));
            return String.Format("{0}{1}", Uri.UnescapeDataString(uri.PathAndQuery), Uri.UnescapeDataString(uri.Fragment));
        }
    

    和测试:

        [Test]
        public void GetPathFromUriTest()
        {
            Assert.AreEqual(@"C:/Test/Space( )(h#)(p%20){[a&],t@,p%,+}.,/Release", @"file:///C:/Test/Space( )(h#)(p%20){[a&],t@,p%,+}.,/Release".GetPathFromUri());
            Assert.AreEqual(@"C:/Test/Space( )(h#)(p%20){[a&],t@,p%,+}.,/Release",  @"file://C:/Test/Space( )(h#)(p%20){[a&],t@,p%,+}.,/Release".GetPathFromUri());
        }
    
        [Test]
        public void AssemblyPathTest()
        {
            var asm = Assembly.GetExecutingAssembly();
    
            var path = asm.GetPath();
            var file = asm.GetFileName();
    
            Assert.IsNotEmpty(path);
            Assert.IsNotEmpty(file);
    
            Assert.That(File     .Exists(file));
            Assert.That(Directory.Exists(path));
        }
    

    【讨论】:

    • 不错!但我更喜欢反斜杠,所以我会将结果从GetPathFromUri 传递到Path.GetFullPath
    • 您需要考虑 Uri.Host 以支持网络路径。
    【解决方案5】:

    由于你标记了这个问题NUnit,你也可以使用AssemblyHelper.GetDirectoryName获取执行程序集的原始目录:

    using System.Reflection;
    using NUnit.Framework.Internal;
    ...
    string path = AssemblyHelper.GetDirectoryName(Assembly.GetExecutingAssembly())
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-11-17
      • 2011-01-27
      • 2021-02-15
      • 2014-04-28
      • 1970-01-01
      • 1970-01-01
      • 2010-10-19
      • 1970-01-01
      相关资源
      最近更新 更多