Как преобразовать 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())