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

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

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

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

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

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

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

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

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

5 ответов

Решение

Вам необходимо использовать 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;

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

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


Вы решаете это, выполняя ручные манипуляции со строками. Шутки в сторону.

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

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

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

Доступная кодовая база ( мы не хотим 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);
    }

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

Это должно работать:

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.

Еще одно решение, включая сложные пути:

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

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

using System.Reflection;
using NUnit.Framework.Internal;
...
string path = AssemblyHelper.GetDirectoryName(Assembly.GetExecutingAssembly())
Другие вопросы по тегам