Как преобразовать Assembly.CodeBase в путь файловой системы в С#?

У меня есть проект, в котором хранятся шаблоны в папке \Templates рядом с DLL и EXE.

Я хочу определить этот путь файла во время выполнения, но используя технику, которая будет работать внутри unit test, а также в процессе производства (и я не хочу отключать теневое копирование в NUnit!)

Assembly.Location не подходит, потому что он возвращает теневой скопированный путь сборки при работе под NUnit.

Environment.CommandLine также ограничен, потому что в NUnit et al он возвращает путь к NUnit, а не к моему проекту.

Assembly.CodeBase выглядит многообещающим, но это путь UNC:

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

Теперь я мог бы превратить это в локальный путь файловой системы, используя строковые манипуляции, но я подозреваю, что есть более чистый способ сделать это в каком-то месте где-то в рамках .NET. Кто-нибудь знает рекомендуемый способ сделать это?

(Бросьте исключение, если путь UNC не является URL file:/// в этом контексте абсолютно точным)

Ответ 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;

Ответ 2

Assembly.CodeBase выглядит многообещающим, но это UNC-путь:

Заметьте, что это нечто, приближающееся к файл uri, а не UNC путь.


Вы решаете это, выполняя манипуляции с строкой вручную. Серьезно.

Попробуйте все другие методы, которые вы можете найти в SO со следующим каталогом (verbatim):

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

Это допустимый, хотя и несколько необычный, путь к Windows. (Некоторые люди будут иметь один из этих символов там, и вы хотите, чтобы ваш метод работал для всех этих, не так ли?)

Доступная база кода (нам не нужны свойства Location, right?) (на моем Win7 с .NET 4 ):

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

assembly.EscapedCodeBase -> file:///C:/Test/Space(%20)(h%23)(p%20)%7B%5Ba%26%5D,[email protected],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);
    }

Я уверен, что можно придумать лучшие решения, возможно, можно даже придумать решение, которое делает надлежащее URL-кодирование/расшифровку свойства CodeBase, если оно является локальным путем, но при условии, что можно просто удалить с file:/// и с этим покончить, я бы сказал, что это решение стоит достаточно хорошо, если конечно действительно уродливо.

Ответ 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");

Я использую это, чтобы иметь возможность регистрироваться из библиотек DLL, используя автономный файл log4net.config.

Ответ 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&],[email protected],p%,+}.,/Release", @"file:///C:/Test/Space( )(h#)(p%20){[a&],[email protected],p%,+}.,/Release".GetPathFromUri());
        Assert.AreEqual(@"C:/Test/Space( )(h#)(p%20){[a&],[email protected],p%,+}.,/Release",  @"file://C:/Test/Space( )(h#)(p%20){[a&],[email protected],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));
    }

Ответ 5

Поскольку вы отметили этот вопрос NUnit, вы также можете использовать AssemblyHelper.GetDirectoryName для получения исходного каталога исполняющей сборки:

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