При попытке создать динамическую сборку я столкнулся с проблемой. У меня был проект, который генерировался утилитой, я программно компилировал его используя Roslyn api.
Это апи требует передачи Metadata References, ссылок на сборки, которые нужны компилируемому проекту, я добавил всё из папки ProgramFiles/dotnet/sdk/3.2.1/ и все из папки с опубликованным вручную проектом. Таким образом я разрешил все статические привязки и "добавил" в манифест сборки всю необходимую информацию о зависимых сборках(имя, token, culture и т.д)
Все успешно скомпилировалось, roslyn не вернул никаких сообщений об ошибках компиляции, и я загрузил сборку используя csharp Assembly.LoadFrom(pathToAssembly)
. Далее, когда попытался рефлексивно обратиться к ее типам(csharp assembly.GetTypes().ToArray()
) получил сообщение об ошибке :
В итоге при попытке обратиться к информации о типах, CLR нужно было подгрузить сборку в которой эти типы лежат, но найти ее она не могла. А почему могла найти большинство других типов в сборке? На самом деле большинство типов на которые ссылалась эта сборка уже были загружены в домен, поэтому среда даже не стала их искать. Не смогла же найти только специфичные типы, которые использовала динамически созданная сборка. А как вообще среда загружает типы? Есть хорошая статья, по этому поводу, там написано:
These locations(where find dependencies) include:
- App base directory (in the same folder as the entry point application, no config required)
- Package cache folders (NuGet restore cache or NuGet fallback folders)
- An optimized package cache or packages store
- The servicing index (rarely used. For Windows Update purposes.)
- Shared framework (configured via runtimeconfig.json)
В моем случае можно ограничиться только App Base Directory, Package cache Folders(это указывается в projectname.runtimeconfig.dev.json) и Shared Framework.
App Base Directory - это все dll которые лежат в той же директории что и dll основного проекта Поиск в shared Framework и Package cache folders осуществляется указанием в prjectname.runtimeconfig.dev.json в моем случае он выглядит так:
{
"runtimeOptions": {
"additionalProbingPaths": [
"C:\\Users\\evgeniy\\.dotnet\\store\\|arch|\\|tfm|",
"C:\\Users\\evgeniy\\.nuget\\packages",
"C:\\Program Files\\dotnet\\sdk\\NuGetFallbackFolder"
]
}
}
Этот json индивидуален для каждой машины, он показывает, откуда нужно разрешать все зависимости, в данном случае из трех папок. Работает он совместно с projectname.deps.json
в котором указаны все статические зависимости проекта и путь до них
Тем самым комбинируя additionalProbingPaths и выделенные красной стрелочкой, пути разрешаются зависимости, получается в моем случае можно либо добавить недостающие зависимости в папку где лежит dll проекта, либо добавить все необходимые зависимости в prjectname.deps.json, а именно в разделы libraries и targets:
Я попробовал и то, и то, все работало, но я предпочту первый вариант.
Также есть еще файл projectname.runtimeconfig.json, в нем указывается версия используемая версия runtime'a:
{
"runtimeOptions": {
"tfm": "netcoreapp3.1",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "3.1.0"
}
}
}
Таким образом приложение использует общие сборки конкретной версии фреймворка, которые лежат в C:\Program Files\dotnet\shared\Microsoft.NETCore.App\3.1.2
(не точно)
В завершение, проблема возникла из-за двух типов линковки сборок, статического добавления имен сборок в манифест и загрузки во время исполнения, о чем я не знал. Решение проблемы, это перемещение dll от которых зависит приложение на уровень dll файла этого приложения
Если хотите загрузить сборку со всеми ее зависимостями(которые должны располагаться в файле с загружаемой .dll) нужно использовать Assembly.LoadFrom
, а не Assembly.LoadFile
. Во втором случае зависимые сборки не загружаются. Дело в том, что LoadFile, автоматически не разрешает никакие сборки, нужно делать это самостоятельно, подписавшись на AppDomain.AssemblyResolve
event.
Assembly.LoadFile
загружает сборку и начинает разрешать все ее зависимости в папке с этой сборкой, глобальном кеше nuget или GAC
Места где CLR будет искать сборки конфигурируются по-разному. В обоих случаях среда будет искать сборки в рантайме в директориях где лежит dll (это AppDomain.BaseDirectory), далее для .net framework существуют private path subdirectory and codebase locations. Они конфигурируются в (appconfig'e)[https://docs.microsoft.com/en-us/dotnet/framework/configure-apps/specify-assembly-location]. .net core же будет грузить сборки в путях указанных в .runtimeconfig.dev.json'e, в секции additionalProbingPaths, там by default пути к nuget кешам.