Как разрешить адреналин NuGet
Я создаю библиотеку с некоторым функционалом с именем CompanyName.SDK
, который должен быть интегрирован в проект компании CompanyName.SomeSolution
CompanyName.SDK.dll
должен быть развернут через пакет NuGet.
Пакет CompanyName.SDK
имеет зависимость от сторонних пакетов NuGet. Например, допустим Unity
. Текущая зависимость находится на v3.5.1405-prerelease
Unity
.
CompanyName.SomeSolution.Project1
зависит от Unity
v2.1.505.2
.
CompanyName.SomeSolution.Project2
зависит от Unity
v3.0.1304.1
.
Интеграция CompanyName.SDK
в это решение добавляет зависимость от Unity
v3.5.1405-prerelease
.
Пусть возьмем, что CompanyName.SomeSolution
имеет один runnable выходной проект CompanyName.SomeSolution.Application
, который зависит от двух выше и от CompanyName.SDK
И здесь начинаются проблемы. Все сборки Unity
имеют одинаковые имена во всех пакетах без спецификатора спецификации. И в целевой папке это будет только одна версия Unity
сборок: v3.5.1405-prerelease
через bindingRedirect
в app.config
.
Как код в Project1
, Project2
и SDK
использует точно необходимые версии зависимых пакетов, которые они были закодированы, скомпилированы и протестированы с помощью <
ПРИМЕЧАНИЕ 1: Unity
- просто пример, реальная ситуация в 10 раз хуже с модулями 3rdparty, зависящими от других модулей 3rdparty, которые в свою очередь имеют 3-4 версии одновременно.
ПРИМЕЧАНИЕ 2. Я не могу обновить все пакеты до их последних версий, потому что есть пакеты, у которых есть зависимость не последней версии других пакетов.
ПРИМЕЧАНИЕ 3. Предположим, что у зависимых пакетов есть изменения в изменении между версиями. Это реальная проблема, почему я задаю этот вопрос.
ПРИМЕЧАНИЕ 4: Я знаю о вопросе о конфликтах между разными версиями одной и той же зависимой сборки, но ответы там не решают корень проблемы - они просто скрывают ее.
ПРИМЕЧАНИЕ 5. Где, черт возьми, это обещанное решение "DLL Hell"? Он просто появляется с другой позиции.
ПРИМЕЧАНИЕ 6. Если вы считаете, что использование GAC каким-то образом является вариантом, напишите пошаговое руководство или дайте мне ссылку.
Ответы
Ответ 1
Пакет Unity
не является хорошим примером, потому что вы должны использовать его только в одном месте под названием Root of Composition. И Composition Root
должен быть как можно ближе к точке входа приложения. В вашем примере это CompanyName.SomeSolution.Application
Кроме того, где я сейчас работаю, возникает точно такая же проблема. И то, что я вижу, проблема часто возникает в результате сквозных проблем, таких как ведение журнала. Решение, которое вы можете применить, заключается в преобразовании сторонних зависимостей в зависимости от сторонних зависимостей. Вы можете сделать это, введя абстракции для этих понятий. Фактически, у этого есть другие преимущества, такие как:
- более удобный код
- лучшая тестируемость
- избавиться от нежелательной зависимости (каждому клиенту
CompanyName.SDK
действительно нужна зависимость Unity
?)
Итак, возьмем для примера мнимую библиотеку .NET Logging
:
CompanyName.SDK.dll
зависит от .NET Logging 3.0
CompanyName.SomeSolution.Project1
зависит от .NET Logging 2.0
CompanyName.SomeSolution.Project2
зависит от .NET Logging 1.0
Существуют разрывы между версиями .NET Logging
.
Вы можете создать свою собственную стороннюю зависимость, введя интерфейс ILogger
:
public interface ILogger
{
void LogWarning();
void LogError();
void LogInfo();
}
CompanyName.SomeSolution.Project1
и CompanyName.SomeSolution.Project2
должен использовать интерфейс ILogger
. Они зависят от сторонней зависимости ILogger
интерфейса. Теперь вы храните эту библиотеку .NET Logging
за одним местом и легко выполняете обновление, потому что вам нужно сделать это в одном месте. Кроме того, нарушение изменений между версиями больше не является проблемой, поскольку используется одна версия библиотеки .NET Logging
.
Фактическая реализация интерфейса ILogger
должна быть в другой сборке, и это должно быть только место, где вы ссылаетесь на библиотеку .NET Logging
.
В CompanyName.SomeSolution.Application
, где вы создаете свое приложение, вы должны теперь привязать абстракцию ILogger
к конкретной реализации.
Мы используем этот подход, и мы также используем NuGet
для распространения наших абстракций и наших реализаций. К сожалению, проблемы с версиями могут появляться вместе с вашими собственными пакетами. Чтобы избежать этих проблем, примените Semantic Versioning в пакетах, которые вы развертываете через NuGet
для вашей компании. Если что-то изменится в вашей базе кода, которая распространяется через NuGet
, вы должны изменить во всех пакетах, которые распространяются через NuGet
. Например, у нас есть локальный сервер NuGet
:
-
DomainModel
-
Services.Implementation.SomeFancyMessagingLibrary
(который ссылается на DomainModel
и SomeFancyMessagingLibrary
)
- и многое другое...
Версия между этими пакетами синхронизируется, если версия изменена в DomainModel
, та же версия находится в Services.Implementation.SomeFancyMessagingLibrary
. Если нашим приложениям требуется обновление наших внутренних пакетов, все зависимости обновляются до одной и той же версии.
Ответ 2
Вы можете работать на уровне сборки после компиляции, чтобы решить эту проблему с...
Вариант 1
Вы можете попробовать объединить сборки с помощью ILMerge
. ilmerge/target:winexe/out:SelfContainedProgram.exe Program.exe ClassLibrary1.dll ClassLibrary2.dll
Результатом будет сборка, представляющая собой сумму вашего проекта и его необходимых зависимостей. Это имеет некоторые недостатки, такие как потеря поддержки моно и потеря идентификаторов сборки (имя, версия, культура и т.д.), Поэтому лучше всего, когда все сборки, которые нужно объединить, созданы вами.
Итак, вот...
Вариант 2
Вместо этого вы можете встраивать зависимости как ресурсы в свои проекты, как описано в этой статье. Вот соответствующая часть:
Во время выполнения CLR не сможет найти зависимую DLL сборки, что является проблемой. Чтобы это исправить, когда ваше приложение инициализирует, зарегистрирует метод обратного вызова в AppDomains Событие ResolveAssembly. Код должен выглядеть примерно так:
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => {
String resourceName = "AssemblyLoadingAndReflection." +
new AssemblyName(args.Name).Name + ".dll";
using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName)) {
Byte[] assemblyData = new Byte[stream.Length];
stream.Read(assemblyData, 0, assemblyData.Length);
return Assembly.Load(assemblyData);
}
};
Теперь первый раз поток вызывает метод, который ссылается на тип в зависимый файл DLL, событие AssemblyResolve будет вызвано и Код обратного вызова, показанный выше, найдет нужный встроенный ресурс DLL и загрузить его, вызвав перегрузку метода Load Assemblys, который принимает в качестве аргумента байт [].
Я думаю, что это вариант, который я бы использовал, если бы я был на вашем месте, жертвуя некоторым начальным временем запуска.
Обновление
Посмотрите здесь. Вы также можете попробовать использовать эти теги <probing>
в каждом проекте app.config, чтобы определить пользовательскую подпапку для поиска при поиске сборок в CLR.