Как получить путь к сборке, в которой находится код?

782

Есть ли способ получить путь к сборке, в которой находится текущий код? Я не хочу путь к вызывающей сборке, только тот, который содержит код.

В основном мой модульный тест должен прочитать некоторые тестовые файлы XML, которые расположены относительно DLL. Я хочу, чтобы путь всегда разрешался правильно, независимо от того, запускается ли dll-тестирование из TestDriven.NET, через графический интерфейс MbUnit или что-то еще.

Изменить : Люди, кажется, неправильно понимают, что я спрашиваю.

Моя тестовая библиотека находится в скажем

C: \ Projects \ MyApplication \ daotests \ Bin \ Debug \ daotests.dll

и я хотел бы получить этот путь:

C: \ Projects \ MyApplication \ daotests \ Bin \ Debug \

Три предложения пока что подводят меня, когда я запускаю из MbUnit Gui:

  • Environment.CurrentDirectory дает c: \ Program Files \ MbUnit

  • System.Reflection.Assembly.GetAssembly(typeof(DaoTests)).Location дает C: \ Documents and Settings \ george \ Local Settings \ Temp \ .... \ DaoTests.dll

  • System.Reflection.Assembly.GetExecutingAssembly().Location дает так же, как и предыдущий.

Джордж Мауэр
источник
102
Это ваше решение: var dir = AppDomain.CurrentDomain.BaseDirectory;
Джалал эш-Шаер
7
Это должно быть принятым решением. AppDomain.CurrentDomain.BaseDirectory - правильный подход.
aBetterGamer
2
Я пришел сюда в поисках решения для пакета nuget для чтения файла JSON из его каталога pacakge. Кажется, что при выполнении пакета nuget «AppDomain.CurrentDomain.BaseDirectory» указывает на каталог запущенных проектов, а не на каталог пакета nuget. Кажется, что ни один из них не нацелен на каталог пакета nuget правильно.
Лукас
@ Лукас, нет, не будет, потому что это не то, о чем этот вопрос (на самом деле, когда его спросили, nuget не существует) - не стесняйтесь задавать новый вопрос и пинговать меня там, но я могу сказать вам прямо сейчас, что это невозможно в большинстве случаев. Для большинства проектов каталог nuget находится packagesрядом с файлом sln. НО, когда вы компилируете и распространяете вещи, нет файла sln и каталога пакетов. Во время компиляции все необходимое (но не все) копируется в каталог bin. Лучше всего использовать сценарий пост-сборки, чтобы скопировать нужный файл.
Джордж Мауэр

Ответы:

1038

Я определил следующее свойство, так как мы часто используем его в модульном тестировании.

public static string AssemblyDirectory
{
    get
    {
        string codeBase = Assembly.GetExecutingAssembly().CodeBase;
        UriBuilder uri = new UriBuilder(codeBase);
        string path = Uri.UnescapeDataString(uri.Path);
        return Path.GetDirectoryName(path);
    }
}

Это Assembly.Locationсвойство иногда дает некоторые забавные результаты при использовании NUnit (где сборки запускаются из временной папки), поэтому я предпочитаю использовать, CodeBaseкоторый дает вам путь в формате URI, затем UriBuild.UnescapeDataStringудаляет File://в начале и GetDirectoryNameменяет его на обычный формат Windows ,

Джон Сибли
источник
29
Это одна проблема, с которой я столкнулся, если имя вашего каталога: c: \ My% 20Directory, то Uri.UnescapeDataString вернет: c: \ My Directory Это означает, что File.Exists ("c: \ My Directory \ MyFile.txt ") вернет false, поскольку правильный путь на самом деле" c: \ My% 20Directory \ MyFile.txt ". Я сталкивался с этим, поскольку в наших путях SVN есть пробелы, и когда мы их проверяем, он кодирует пробелы.
row1
5
Будьте внимательны, когда используете эту функцию для проверки File.Exist (), так как этот метод вернет false в пути UNC. Вместо этого используйте ответ @ Keith.
А-Я.
3
Не знал, что вы можете поставить статические перед публикой. Приятно знать, и я думаю, что я предпочитаю для удобочитаемости
Валамас
5
Примечание: это не работает с сетевыми местоположениями (например, \\ REMOT_EPC \ Folder)
Muxa
5
Также это не будет работать, если в каталоге есть цифры #. Цифровые знаки допускаются в каталогах и именах файлов в Windows.
Huemac
322

Это помогает?

//get the full location of the assembly with DaoTests in it
string fullPath = System.Reflection.Assembly.GetAssembly(typeof(DaoTests)).Location;

//get the folder that's in
string theDirectory = Path.GetDirectoryName( fullPath );
Кит
источник
посмотрите мои правки, это не так, это что-то странное в том, как работает MbUnit?
Джордж Мауэр
3
Установите файлы XML для содержимого, скопированного с помощью библиотеки DLL или ресурсов, прочитанных из библиотеки DLL.
Кит
22
Или простоtypeof(DaoTests).Assembly
SLaks
4
@SLaks @JohnySkovdal @Keith: Эй, ребята, используйте Assembly.GetExecutingAssembly(). Он «получает сборку, которая содержит код, который выполняется в данный момент» (из описания метода). Я использую это в моем AddIn " EntitiesToDTOs ". См. AssemblyHelper.cs для реального примера.
kzfabi
4
У него была проблема с сообщением @John Silby, так как оно не похоже на то, что оно работает для путей UNC ... например, \\ Server \ Folder \ File.ext. Этот сделал свое дело. +1
Черника
312

Это так просто, как это:

var dir = AppDomain.CurrentDomain.BaseDirectory;
Джалал эш-Шаер
источник
11
Это должно быть принятым решением. AppDomain.CurrentDomain.BaseDirectory - правильный подход.
aBetterGamer
5
спасибо за то, что обратили мое внимание на это - не уверен, было ли это доступно в то время, когда я задавал вопрос, но сейчас.
Джордж Мауэр
120
Нет, это неправильно Это возвращает путь ОРИГИНАЛЬНОЙ ВХОДНОЙ ТОЧКИ, а не исполняемый в данный момент код. Если вы загрузили сборку вручную по другому пути или если она была загружена из GAC, она вернет неправильный результат. Этот ответ правильный: stackoverflow.com/a/283917/243557 Быстрее все еще есть Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location).
Натанчер
9
На самом деле это не будет работать в веб-приложениях, но, насколько я понял, для любого типа приложений должно работать следующее расширение:AppDomain.CurrentDomain.RelativeSearchPath ?? AppDomain.CurrentDomain.BaseDirectory
Илья Черномордик
4
Это отлично подходит для модульного тестирования, если вы просто хотите получить исходный путь к корзине вашей тестовой сборки (скажем, для доступа к файлам вспомогательных данных в подпапках). Тестовая сборка - это точка входа в ваш код.
MarioDS
68

То же самое, что и ответ Джона, но чуть менее подробный метод расширения.

public static string GetDirectoryPath(this Assembly assembly)
{
    string filePath = new Uri(assembly.CodeBase).LocalPath;
    return Path.GetDirectoryName(filePath);            
}

Теперь вы можете сделать:

var localDir = Assembly.GetExecutingAssembly().GetDirectoryPath();

или если вы предпочитаете:

var localDir = typeof(DaoTests).Assembly.GetDirectoryPath();
Sneal
источник
6
Вы имели в виду assemblyвместо Assembly.GetExecutingAssembly()?
Чувак Паскалу
3
Как указывает Чувак, вы передали аргумент и не смогли его использовать.
Крис Москини
4
Этот ответ просто неверен для рассматриваемого вопроса. Модифицированная версия этого ответа может дать вам путь к данной сборке. Однако здесь мы специально ищем исполняемую сборку, и поэтому передача в сборке не имеет смысла. Метод расширения является неправильным инструментом для работы.
Эдвард Брей
46

Единственное решение, которое работало для меня при использовании общих ресурсов CodeBase и UNC Network, было:

System.IO.Path.GetDirectoryName(new System.Uri(System.Reflection.Assembly.GetExecutingAssembly().CodeBase).LocalPath);

Это также работает с обычными URI тоже.

Игнасио Солер Гарсия
источник
5
Это должен быть принятый ответ. Это действительно раздражает, что кодовая база по умолчанию не обрабатывает UNC-ресурсы правильно.
Даниэль Гилберт,
Это происходит сбой, когда папка содержит пробелы и бог знает, что другие символы ...
MarioDS
1
Я много использовал это и нашел один сценарий, когда он терпит неудачу: если эта строка кода сама является частью пакета NuGet, который затем используется приложением! Мы также можем поддержать этот сценарий, заменив GetExecutingAssembly()на GetCallingAssembly().
Тимо
@Timo: вы проверили, есть ли у этого изменения побочные эффекты? Если это так, пожалуйста, отредактируйте ответ, чтобы включить исправление.
Игнасио Солер Гарсия
@IgnacioSolerGarcia К сожалению, я должен сообщить, что он работал только на один уровень, то есть он не работает, если пакет NuGet был вызван другим пакетом NuGet! Я теперь с помощью этого (из комментария на этой странице по Черномор): AppDomain.CurrentDomain.RelativeSearchPath ?? AppDomain.CurrentDomain.BaseDirectory. Первая часть предназначена для веб-приложений, а вторая - для других приложений.
Тимо
32

Это должно работать, если сборка не скопирована тенями :

string path = System.Reflection.Assembly.GetExecutingAssembly().Location
jodonnell
источник
14

Что насчет этого:

System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
huseyint
источник
11

Я подозреваю, что реальная проблема заключается в том, что ваш тестовый бегун копирует вашу сборку в другое место. Во время выполнения нет никакого способа определить, откуда была скопирована сборка, но вы, вероятно, можете щелкнуть переключателем, чтобы сообщить исполнителю теста о том, что нужно запустить сборку, а не копировать ее в теневой каталог.

Конечно, такое переключение может быть разным для каждого участника теста.

Рассматривали ли вы встраивание своих XML-данных в качестве ресурсов в тестовую сборку?

Курт Хагенлохер
источник
+1 за указание на проблему с теневым копированием. Тем не менее, действительно возможно определить исходное место из Assembly.CodeBase.
tm1
11
AppDomain.CurrentDomain.BaseDirectory

работает с интерфейсом MbUnit.

user368021
источник
Это отлично работает для записи файла относительно корневого каталога в веб-приложении asp.net
Филип Питтл
Я обнаружил, что этот работает лучше всего в целом. Выберите это, если вы не уверены.
Эрик Бергштедт
10

Я считаю, что это будет работать для любого вида приложений:

AppDomain.CurrentDomain.RelativeSearchPath ?? AppDomain.CurrentDomain.BaseDirectory
Илья Черномордик
источник
1
Мои эксперименты показывают, что это самый надежный ответ, охватывающий не только веб-приложения и консольные приложения, но также вызовы из модульных тестов и пакетов NuGet (вложенные в любой уровень рекурсии).
Тимо
8

Насколько я могу судить, у большинства других ответов есть несколько проблем.

Правильный способ сделать это для дисковой (в отличие от веб-сборки ) сборки без GAC - использовать текущую исполняемую сборку.CodeBase свойство .

Это возвращает URL ( file://). Вместо того, чтобы возиться с манипуляциями со строками или UnescapeDataString, это можно преобразовать с минимальной суетой, используя LocalPathсвойство Uri.

var codeBaseUrl = Assembly.GetExecutingAssembly().CodeBase;
var filePathToCodeBase = new Uri(codeBaseUrl).LocalPath;
var directoryPath = Path.GetDirectoryName(filePathToCodeBase);
расточитель
источник
1
Не работает, если путь содержит #( EscapedCodeBaseработает, но EscapedCodeBase не работает, если путь содержит, например, %20дословно (что является допустимой последовательностью символов в пути Windows)
Martin Ba
Если мы хотим иметь этот код в пакете NuGet, мы можем исправить этот сценарий, заменив GetExecutingAssembly()на GetCallingAssembly().
Тимо
8

Как насчет этого ...

string ThisdllDirectory = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);

Тогда просто взломай то, что тебе не нужно

Дэвид С Фукс
источник
7
var assembly = System.Reflection.Assembly.GetExecutingAssembly();
var assemblyPath = assembly.GetFiles()[0].Name;
var assemblyDir = System.IO.Path.GetDirectoryName(assemblyPath);
Марк Сидаде
источник
7

Вот порт VB.NET кода Джона Сибли. Visual Basic не чувствителен к регистру, поэтому пара его имен переменных сталкивалась с именами типов.

Public Shared ReadOnly Property AssemblyDirectory() As String
    Get
        Dim codeBase As String = Assembly.GetExecutingAssembly().CodeBase
        Dim uriBuilder As New UriBuilder(codeBase)
        Dim assemblyPath As String = Uri.UnescapeDataString(uriBuilder.Path)
        Return Path.GetDirectoryName(assemblyPath)
    End Get
End Property
Майк Шалл
источник
6

За все эти годы никто не упоминал об этом. Уловка, которую я узнал от удивительного проекта ApprovalTests . Хитрость заключается в том, что вы используете отладочную информацию в сборке, чтобы найти исходный каталог.

Это не будет работать в режиме RELEASE, ни с включенными оптимизациями, ни на компьютере, отличном от того, на котором он был скомпилирован.

Но это даст вам пути относительно места расположения файла исходного кода, из которого вы вызываете его

public static class PathUtilities
{
    public static string GetAdjacentFile(string relativePath)
    {
        return GetDirectoryForCaller(1) + relativePath;
    }
    public static string GetDirectoryForCaller()
    {
        return GetDirectoryForCaller(1);
    }


    public static string GetDirectoryForCaller(int callerStackDepth)
    {
        var stackFrame = new StackTrace(true).GetFrame(callerStackDepth + 1);
        return GetDirectoryForStackFrame(stackFrame);
    }

    public static string GetDirectoryForStackFrame(StackFrame stackFrame)
    {
        return new FileInfo(stackFrame.GetFileName()).Directory.FullName + Path.DirectorySeparatorChar;
    }
}
Джордж Мауэр
источник
5

Текущий каталог, в котором вы находитесь.

Environment.CurrentDirectory;  // This is the current directory of your application

Если вы копируете файл .xml со сборкой, вы должны найти его.

или

System.Reflection.Assembly assembly = System.Reflection.Assembly.GetAssembly(typeof(SomeObject));

// The location of the Assembly
assembly.Location;
Дэвид Басараб
источник
это будет проблематично, если сборка была скопирована тенями .
спонсор
+1520! Environment.CurrentDirectoryработает, если вы используете отражение в классе задач MSBuild, где исполняемая сборка находится в GAC, а ваш код находится где-то еще.
вулкан ворон
4
В общем случае CurrentDirectory не сообщает вам, где находятся ваши исполняемые файлы. Это не то, для чего это используется. Просто бывает так, что исполняемые файлы находятся в одном и том же месте, поэтому многие программисты не понимают разницы. Затем они создают проблемы для некоторых конечных пользователей, которые ожидали, что приложение поймет правильное использование CurrentDirectory.
Бент Транберг
5

Я использовал Assembly.CodeBase вместо Location:

Assembly a;
a = Assembly.GetAssembly(typeof(DaoTests));
string s = a.CodeBase.ToUpper(); // file:///c:/path/name.dll
Assert.AreEqual(true, s.StartsWith("FILE://"), "CodeBase is " + s);
s = s.Substring(7, s.LastIndexOf('/') - 7); // 7 = "file://"
while (s.StartsWith("/")) {
    s = s.Substring(1, s.Length - 1);
}
s = s.Replace("/", "\\");

Это работает, но я больше не уверен, что это на 100% правильно. Страница по адресу http://blogs.msdn.com/suzcook/archive/2003/06/26/assembly-codebase-vs-assembly-location.aspx сказано:

«CodeBase - это URL-адрес места, где был найден файл, а Location - путь, по которому он был фактически загружен. Например, если сборка была загружена из Интернета, ее CodeBase может начинаться с« http: // » , но его Location может начинаться с "C: \". Если файл был скопирован теневым, Location будет путь к копии файла в каталоге теневого копирования. Также полезно знать, что CodeBase не гарантируется будет установлен для сборок в GAC. Однако расположение всегда будет устанавливаться для сборок, загруженных с диска ».

Вы можете использовать CodeBase вместо Location.

Дэн Гибсон
источник
1
@Kiquenet: так много кода только для преобразования URI в путь. Конечно, это может быть улучшено. Посмотрите на ответ Майка Шалла или SoMoS. Вы не должны пытаться преобразовывать URI на строковом уровне, а вместо этого использовать подходящие объекты. Хорошо, также неуклюже, что Assembly.CodeBase возвращает строку вместо более подходящего объекта, такого как URI или FileInfo.
7
2

Вы можете получить путь к корзине с помощью AppDomain.CurrentDomain.RelativeSearchPath.

суммировать
источник
2

Все предложенные ответы работают, когда разработчик может изменить код для включения необходимого фрагмента, но если вы хотите сделать это без изменения кода, вы можете использовать Process Explorer.

В нем будут перечислены все выполняющиеся библиотеки в системе. Возможно, вам потребуется определить идентификатор процесса вашего работающего приложения, но обычно это не так уж сложно.

Я написал полное описание того, как сделать это для DLL внутри II - http://nodogmablog.bryanhogan.net/2016/09/locating-and-checking-an-executing-dll-on-a-running-web -server /

Bryan
источник
Обратите внимание, что, во-первых, код в этой статье довольно ориентирован на IIS, а во-вторых, он дает вам (я полагаю) все загруженные в настоящее время библиотеки DLL, а не то, что выполняется одновременно.
Джордж Мауэр
Данный пример относится к iis, но те же шаги применяются, если dll работает в процессе вне iis. Это просто вопрос идентификации идентификатора процесса. Я обновлю статью, чтобы отметить это. Спасибо за предложение.
Брайан
2

в приложении Windows Form вы можете просто использовать Application.StartupPath

но для DLL и консольных приложений запоминать код гораздо сложнее ...

string slash = Path.DirectorySeparatorChar.ToString();
string root = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);

root += slash;
string settingsIni = root + "settings.ini"
Шейн Дж
источник
1
string path = Path.GetDirectoryName(typeof(DaoTests).Module.FullyQualifiedName);
Джесси С. Слайсер
источник
1

Вы получите неправильный каталог, если путь содержит символ «#». Поэтому я использую модификацию ответа Джона Сибли, которая представляет собой комбинацию UriBuilder.Path и UriBuilder.Fragment:

public static string AssemblyDirectory
{
    get
    {
        string codeBase = Assembly.GetExecutingAssembly().CodeBase;
        UriBuilder uri = new UriBuilder(codeBase);
        //modification of the John Sibly answer    
        string path = Uri.UnescapeDataString(uri.Path.Replace("/", "\\") + 
          uri.Fragment.Replace("/", "\\"));
        return Path.GetDirectoryName(path);
     }
}
Федор Шиханцов
источник
0

Это то, что я придумал. Между веб-проектами юнит-тесты (nunit и resharper test runner) ; Я обнаружил, что это работает для меня.

Я искал код, чтобы определить, в какой конфигурации находится сборка Debug/Release/CustomName. Увы, #if DEBUG. Так что, если кто-то может улучшить это !

Не стесняйтесь редактировать и улучшать.

Получение папки приложения . Полезно для веб-корней, юнит-тестов, чтобы получить папку с тестовыми файлами.

public static string AppPath
{
    get
    {
        DirectoryInfo appPath = new DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory);

        while (appPath.FullName.Contains(@"\bin\", StringComparison.CurrentCultureIgnoreCase)
                || appPath.FullName.EndsWith(@"\bin", StringComparison.CurrentCultureIgnoreCase))
        {
            appPath = appPath.Parent;
        }
        return appPath.FullName;
    }
}

Получение папки bin : полезно для выполнения сборок с использованием отражения. Если файлы копируются туда из-за свойств сборки.

public static string BinPath
{
    get
    {
        string binPath = AppDomain.CurrentDomain.BaseDirectory;

        if (!binPath.Contains(@"\bin\", StringComparison.CurrentCultureIgnoreCase)
            && !binPath.EndsWith(@"\bin", StringComparison.CurrentCultureIgnoreCase))
        {
            binPath = Path.Combine(binPath, "bin");
            //-- Please improve this if there is a better way
            //-- Also note that apps like webapps do not have a debug or release folder. So we would just return bin.
#if DEBUG
            if (Directory.Exists(Path.Combine(binPath, "Debug"))) 
                        binPath = Path.Combine(binPath, "Debug");
#else
            if (Directory.Exists(Path.Combine(binPath, "Release"))) 
                        binPath = Path.Combine(binPath, "Release");
#endif
        }
            return binPath;
    }
}
Valamas
источник
0

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

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 из файла DLL).

мммммм
источник
Для чего здесь fileMapиспользуется?
Джордж Мауэр
0

Я нахожу свое решение адекватным для поиска местоположения.

var executingAssembly = new FileInfo((Assembly.GetExecutingAssembly().Location)).Directory.FullName;
Тез Уингфилд
источник
Это уже один из самых популярных ответов, и он прямо упоминается в этом вопросе как нечто, что не работает в этой ситуации.
Джордж Мауэр
Извинения должны пропустить это! Очевидно, я не прочитал полностью.
Тез Уингфилд,
0

Я получил такое же поведение NUnitв прошлом. По умолчанию NUnitкопирует вашу сборку во временный каталог. Вы можете изменить это поведение в NUnitнастройках:

введите описание изображения здесь

Возможно TestDriven.NETи MbUnitGUI имеют одинаковые настройки.

Андрей Бушман
источник
-3

Я использую это, чтобы получить путь к Bin Directory:

var i = Environment.CurrentDirectory.LastIndexOf(@"\");
var path = Environment.CurrentDirectory.Substring(0,i); 

Вы получаете этот результат:

"c: \ users \ ricooley \ Documents \ visual studio 2010 \ Projects \ Windows_Test_Project \ Windows_Test_Project \ bin"

rcooley56
источник
6
Я не вижу причины избегать Path.getDirectoryName здесь
Макс Келлер
@MaxKeller Если вы не видите причин, это не значит, что это правильно. Этот альтернативный метод Path.GetDirectoryName работает в десять раз быстрее.
Руслан Веселов
-3

Веб приложение?

Server.MapPath("~/MyDir/MyFile.ext")
user2009677
источник
2
@ christiandev это ответ, но может показаться, что это неправильный вопрос. Из вопроса довольно ясно, что это не веб-приложение, а сборка, запускаемая с помощью MbUnit. Тем не менее, ответ все еще не совсем корректен из-за теневого копирования в Asp.Net (хотя, возможно, это может быть тем, что ищет кто-то, занимающийся этим вопросом).
Джордж Мауэр