关于.net:如何将Assembly.CodeBase转换为C#中的文件系统路径?

How can I convert Assembly.CodeBase into a filesystem path in C#?

我有一个项目,它将模板存储在靠近dlls和exe的\Templates文件夹中。

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

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

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

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

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

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

(如果unc路径不是file:///url,则引发异常在这种情况下是完全正确的)


您需要使用system.uri.localpath:

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

因此,如果需要当前正在执行的程序集的原始位置:

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


Assembly.CodeBase looks promising, but it's a UNC path:

请注意,它近似于文件URI,而不是UNC路径。

通过手工操作字符串来解决这个问题。说真的。

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

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

这是一个有效的(如果有点不寻常)Windows路径。(有些人在那里的路径中会有这两个字符中的任何一个,您希望您的方法适用于所有这些字符,对吗?)

可用的代码库(我们不需要Location,对吗?)属性是(在我的Win7和.NET 4上):

1
2
3
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没有完全转义(我不知道这是bug还是uri方案的缺点):
    • 注意空格字符(是如何翻译成%20的。
    • 但是%20序列也翻译成%20!(百分比%根本没有逃逸)
    • 没有人能从这个残缺的形式重建原始!

对于本地文件(这正是我所关心的CodeBase文件的全部内容,因为如果文件不是本地的,那么您可能无论如何都要使用.Location,以下内容对我很有用(请注意,它也不是最漂亮的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    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-en/解码,但是考虑到我们可以去掉file:///并完成它,我会说这个解决方案足够好,如果确实很难看的话。


这应该有效:

1
2
3
4
5
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库中进行日志记录。


还有一个解决方案,包括复杂路径:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    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));
    }

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    [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));
    }


由于您将此问题标记为nunit,因此还可以使用AssemblyHelper.GetDirectoryName获取执行程序集的原始目录:

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