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

发布于 2024-09-30 18:47:25 字数 615 浏览 6 评论 0原文

我有一个项目,将模板存储在 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,则在这种情况下抛出异常是绝对没问题的)

I have a project that stores templates in a \Templates folder next to the DLLs and EXE.

I want to determine this file path at runtime, but using a technique that will work inside a unit test as well as in production (and I don't want to disable shadow-copying in NUnit!)

Assembly.Location is no good because it returns the shadow-copied assembly's path when running under NUnit.

Environment.CommandLine is also of limited use because in NUnit et al it returns the path to NUnit, not to my project.

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

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

Now I could turn this into a local filesystem path using string manipulation, but I suspect there's a cleaner way of doing it buried in the .NET framework somewhere. Anyone know a recommended way of doing this?

(Throwing an exception if the UNC path is not a file:/// URL is absolutely fine in this context)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

请远离我 2024-10-07 18:47:25

您需要使用 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

You need to use System.Uri.LocalPath:

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

So if you want the original location of the currently executing assembly:

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

The LocalPath includes the assembly's file name, e.g.,

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

If you want the assembly's directory, then use System.IO.Path.GetDirectoryName():

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

which will give you:

D:\projects\MyApp\MyApp\bin\debug
美煞众生 2024-10-07 18:47:25

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

请注意,它近似于 文件 uri,不是 UNC 路径


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

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

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

这是一个有效的 Windows 路径,尽管有些不寻常。 (有些人在路径中拥有这些字符之一,并且您希望您的方法适用于所有这些字符,对吧?)

可用的代码库(< a href="https://web.archive.org/web/20101010144616/http://blogs.msdn.com/b/suzcook/archive/2003/06/26/ assembly-codebase-vs- assembly-location。 aspx" rel="nofollow noreferrer">我们不需要 Location,对吗?)属性是(在我的 Win7 和 .NET 4 上):

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 方案):
    • 注意空格字符 () 如何转换为 %20
    • 但是 %20 序列会转换为 %20! (百分比 % 根本没有转义)
    • 没有人可以从这种损坏的形式中重建原始版本!

对于本地文件(这就是我真正关心的 CodeBase 内容,因为如果文件不是本地的,您可能无论如何都想使用 .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:/// 并完成它,我想说这个解决方案足够好,如果当然真的很丑。

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

Do note that it is something approximating a file uri, not an UNC path.


You solve this by doing string manipulation by hand. Seriously.

Try all other methods you can find on SO with the following directory (verbatim):

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

This is a valid, if somewhat unusual, Windows path. (Some people will have either one of these characters in there paths, and you would want you method to work for all of those, right?)

The available code base (we do not want Location, right?) properties are then (on my Win7 with .NET 4):

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

You will note:

  • CodeBase is not escaped at all, it's just the regular local path prefixed with file:/// and the backslashes replaced. As such, it does not work to feed this to System.Uri.
  • EscapedCodeBase is not escaped completely (I do not know if this is a bug or if this is a shortcoming of the URI scheme):
    • Note how the space character () translates to %20
    • but the %20 sequence also translates to %20! (percent % is not escaped at all)
    • No one can rebuild the original from this mangled form!

For local files (And that's really all I'd care about for the CodeBasestuff, because if the file ain't local, you probably want to use .Location anyway, the following works for me (note that it isn't the prettiest either:

    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);
    }

I am sure one can come up with better solutions, probably one could even come up with a solution that does proper URL en-/decoding of the CodeBase property if it's a local path, but given that one can just strip off the file:/// and be done with it, I'd say this solution stands as good enough, if certainly really ugly.

俯瞰星空 2024-10-07 18:47:25

这应该可行:

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 库中进行日志记录。

This should work:

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");

I am using this to be able to log from within the dll libraries using a standalone log4net.config file.

月竹挽风 2024-10-07 18:47:25

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

    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));
    }

One more solution, including complex paths:

    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));
    }

and tests:

    [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));
    }
怪我太投入 2024-10-07 18:47:25

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

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

Since you tagged this question NUnit, you can also use AssemblyHelper.GetDirectoryName to get the original directory of the executing assembly:

using System.Reflection;
using NUnit.Framework.Internal;
...
string path = AssemblyHelper.GetDirectoryName(Assembly.GetExecutingAssembly())
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文