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

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

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

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

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

C:\ Проекты \ MyApplication \ daotests \ Bin\Debug\daotests.dll

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

C:\ Проекты \ 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дает так же, как и предыдущий.

31 ответ

Решение

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

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.

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

var dir = AppDomain.CurrentDomain.BaseDirectory;

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

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

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

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

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

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

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

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

string path = System.Reflection.Assembly.GetExecutingAssembly().Location

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

AppDomain.CurrentDomain.RelativeSearchPath ?? AppDomain.CurrentDomain.BaseDirectory

Начиная с.net framework 4.6 / .net core 1.0, теперь существует AppContext.BaseDirectory, который должен давать тот же результат, что иAppDomain.CurrentDomain.BaseDirectory, за исключением того, что домены приложений не были частью стандартного API 1.x.net core 1.x /.net.

AppContext.BaseDirectory
AppDomain.CurrentDomain.BaseDirectory

работает с MbUnit GUI.

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

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

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

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

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

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

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

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

tl;dr

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


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

Концепция сборки, которая содержит исполняемый код, и файла dll, который содержит сборку, не взаимосвязаны. Сборка может происходить из файла DLL, но это не обязательно.

С помощью Assembly.Load(Byte[])( MSDN) вы можете загрузить сборку прямо из массива байтов в памяти. Не имеет значения, откуда берется массив байтов. Он может быть загружен из файла, загружен из Интернета, динамически сгенерирован, ...

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

Этот метод находится в исполняемой сборке, которая находится по адресу «D:/Software/DynamicAssemblyLoad/DynamicAssemblyLoad/bin/Debug/Runner.exe»

      static void Main(string[] args)
{
    var fileContent = File.ReadAllBytes(@"C:\Library.dll");

    var assembly = Assembly.Load(fileContent);

    // Call the method of the library using reflection
    assembly
        ?.GetType("Library.LibraryClass")
        ?.GetMethod("PrintPath", BindingFlags.Public | BindingFlags.Static)
        ?.Invoke(null, null);

    Console.WriteLine("Hello from Application:");
    Console.WriteLine($"GetViaAssemblyCodeBase: {GetViaAssemblyCodeBase(assembly)}");
    Console.WriteLine($"GetViaAssemblyLocation: {assembly.Location}");
    Console.WriteLine($"GetViaAppDomain       : {AppDomain.CurrentDomain.BaseDirectory}");

    Console.ReadLine();
}

Этот класс находится в Library.dll:

      public class LibraryClass
{
    public static void PrintPath()
    {
        var assembly = Assembly.GetAssembly(typeof(LibraryClass));
        Console.WriteLine("Hello from Library:");
        Console.WriteLine($"GetViaAssemblyCodeBase: {GetViaAssemblyCodeBase(assembly)}");
        Console.WriteLine($"GetViaAssemblyLocation: {assembly.Location}");
        Console.WriteLine($"GetViaAppDomain       : {AppDomain.CurrentDomain.BaseDirectory}");
    }
}

Для полноты картины ниже представлены реализации GetViaAssemblyCodeBase() что одинаково для обеих сборок:

      private static string GetViaAssemblyCodeBase(Assembly assembly)
{
    var codeBase = assembly.CodeBase;
    var uri = new UriBuilder(codeBase);
    return Uri.UnescapeDataString(uri.Path);
}

Бегун напечатает следующий вывод:

      Hello from Library:
GetViaAssemblyCodeBase: D:/Software/DynamicAssemblyLoad/DynamicAssemblyLoad/bin/Debug/Runner.exe
GetViaAssemblyLocation:
GetViaAppDomain       : D:\Software\DynamicAssemblyLoad\DynamicAssemblyLoad\bin\Debug\
Hello from Application:
GetViaAssemblyCodeBase: D:/Software/DynamicAssemblyLoad/DynamicAssemblyLoad/bin/Debug/Runner.exe
GetViaAssemblyLocation:
GetViaAppDomain       : D:\Software\DynamicAssemblyLoad\DynamicAssemblyLoad\bin\Debug\

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

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

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

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

var codeBaseUrl = Assembly.GetExecutingAssembly().CodeBase;
var filePathToCodeBase = new Uri(codeBaseUrl).LocalPath;
var directoryPath = Path.GetDirectoryName(filePathToCodeBase);

Вот порт 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
var assembly = System.Reflection.Assembly.GetExecutingAssembly();
var assemblyPath = assembly.GetFiles()[0].Name;
var assemblyDir = System.IO.Path.GetDirectoryName(assemblyPath);

За все эти годы никто не упоминал об этом. Уловка, которую я узнал от удивительного проекта 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;
    }
}

Я использовал 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.

В приложении 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"

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

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;

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

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

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

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

Вы получите неправильный каталог, если путь содержит символ "#". Поэтому я использую модификацию ответа Джона Сибли, которая представляет собой комбинацию 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);
     }
}

Для ASP.Net это не работает. Я нашел более подходящее решение в разделе Почему AppDomain.CurrentDomain.BaseDirectory не содержит "bin" в приложении asp.net?. Он работает как для приложения Win, так и для веб-приложения ASP.Net.

public string ApplicationPath
    {
        get
        {
            if (String.IsNullOrEmpty(AppDomain.CurrentDomain.RelativeSearchPath))
            {
                return AppDomain.CurrentDomain.BaseDirectory; //exe folder for WinForms, Consoles, Windows Services
            }
            else
            {
                return AppDomain.CurrentDomain.RelativeSearchPath; //bin folder for Web Apps 
            }
        }
    }
string path = Path.GetDirectoryName(typeof(DaoTests).Module.FullyQualifiedName);

Я считаю, что мое решение подходит для поиска местоположения.

var executingAssembly = new FileInfo((Assembly.GetExecutingAssembly().Location)).Directory.FullName;

Это то, что я придумал. Между веб-проектами - юнит-тесты (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;
    }
}

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

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).

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

Может быть TestDriven.NET а также MbUnit GUI имеет те же настройки.

Я использую это, чтобы получить путь к 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"

Другие вопросы по тегам