Добро пожаловать в мой блог! Изначально он не задумывался как блог CRM разработчика, но жизнь сама внесла нужные коррективы. Тут я публикою все свои наблюдения относительно обозначенных в заголовке систем. Если Вы найдете в нем что-то интересное для Вас, как для заказчика, то буду рад сотрудничать с Вами! В моей компетенции 100% задач по MS CRM 3.0/4.0/2011:
MVP 2010, 2011
- Консалтинг
- Проектирование
- Разработка
- Обучение
MVP 2010, 2011
Еще один способ регистрации сборок с зависимостями
Запись от Артем Enot Грунин размещена 28.12.2017 в 11:07
В одной из прошлых статей я рассказывал о замечательном инструменте Fody/Costura, который позволяет быстрее и элегантнее, нежели ILMerge, объединить несколько сборок .NET в одну. Увы, инструмент имеет критический недостаток: такую сборку нельзя зарегистрировать в Sanbox и, следовательно, в Online версии.
С одной стороны, вопрос поддержки совместимости с онлайн версией не так уж и актуален в нашей стране; виной тому и спорные моменты в законодательстве и особенности менталитета. С другой стороны, не одним онлайном жив сэндбокс! Так что, если вы можете, убрать нагрузку с фронтэнда и перенести ее на апликейшен сервер, то не стоит жертвовать такой возможностью.
Так как же быть, если ваша сборка плагинов набрала вес и процесс публикации решения занимает 20 минут (реальная цифра для сборки большого решения в режиме DLL + PDB)? Выход есть. Тот же подход, но новые версии инструментов. Представляю вашему вниманию, ILRepack: https://github.com/gluck/il-repack. Утилита использует тот же синтаксис командной строки что и ILMerge, так что у вас не должно быть проблем с переходом. Если же вместо командной строки вы используете таски для MSBuild, тогда рекомендую вот этот вариант: https://github.com/ravibpatel/ILRepack.Lib.MSBuild.Task. Есть и другие проекты, но они у меня не заработали, а в этот я даже немного поконтрибутил.
Какие есть плюсы? В моем случае, размер итоговой сборки плагинов уменьшился с 16 до 9 MB (и, следовательно, выросла скорость ее загрузки на сервер). Что касается времени на выполнение слияния, то оно уменьшилось с 1,5 минут, до 16 секунд для DLL + PDB. Считаю, что это вполне весомый аргумент, чтобы пройти через муки перехода и тестирования.
Давайте теперь посмотрим его в работе! Для этого откроем Visual Studio и создадим новый проект типа Class Library (можно использовать любой, но в демо я делаю упор на сценарии разработки под CRM). Я назову его ILRepack.Sample:
Убедитесь, что используете правильную версию .NET Framework, которая совместима с вашей версией CRM. Для D365 (v 8) подходит 4.5.2.
Теперь давайте откроем Package Manager Console и установим несколько NuGet пакетов:
Для начала подключим необходимые сборки SDK. Для этого выполним команду:
Опять же, правильно укажите версию. Я использую последнюю доступную для v8.
Далее, чисто для примера я установлю популярную сборку Newtonsoft.Json. Разумеется, вы можете использовать любую:
Давайте теперь напишем простой плагин. Для этого заменим содержимое Class1 на что-то вроде:
Давайте теперь подпишем, соберем нашу сборку и зарегистрируем ее в системе. Для простоты примера я использую Plugin Registrator в составе XrmToolBox, но вы можете использовать любой инструмент. При регистрации укажем, что сборку нужно разместить в Sandbox:
Теперь нужно как-то выполнить плагин. Для простоты, зарегистрируем шаг на создание Организации. Вы можете выбрать любой другой способ:
И выполним наш плагин тем способом, которым задумали на предыдущем шаге.
Если плагин синхронный, мы должны немедленно увидеть ошибку:
Мы видим, что при выполнении плагина произошла ошибка: в песочнице отсутствует сборка "Newtonsoft.Json". На самом деле, ошибку выбросил JIT компилятор еще до запуска нашего плагина: мы не сможем поймать ее в try-catch как мы не изгилялись. Тем не менее, пришло время это исправить!
Чтобы это сделать, установим нужную нам таску для MSBuild выполнив следующую команду в консоли NuGet:
После этого, необходимо выгрузить наш проект из памяти VS:
После чего мы сможем изменить файл проекта (в VS 2017 можно редактировать проект, не выгружая его из памяти):
Теперь найдем строчку <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
Сразу после нее должен появиться импорт нашей таски:
Теперь нам нужно раскомментировать блок
И заменить его примерно следующим:
Разберем содержимое нашего конфига.
<Target Name="AfterBuild"> Обозначает, что ILRepack будет запущен после сборки. При необходимости, можно указать условия запуска, например, <Target Name="AfterBuild" Condition="'$(Configuration)' == 'Release'">. Это может быть полезно, если вы отлаживаетесь локально с использованием Plugin Profiler. В этом случае вам априори доступны все зависимые сборки и можно не терять время на слияние.
Группа значений <ItemGroup\InputAssemblies>. Как нетрудно догадаться, это перечень сборок, которые нужно объединить. Неочевидно, но факт: первой должна идти основная сборка проекта. С точки зрения MSBuild основной сборки там может не быть вообще, однако задача ILRepack.Lib.MSBuild.Task и сам ILRepack ожидают от нас именно это. Чтобы не хардкодить значение, мы используем параметры MSBuild $(OutputPath) и$(AssemblyName). Полный список можно посмотреть тут: https://msdn.microsoft.com/ru-ru/library/bb629394.aspx
Далее идут параметры самой задачи <ILRepack />. Parallel говорит, что сборка будет осуществляться параллельно несколькими ядрами CPU. Впрочем, судя по коду ILRepack, этого сейчас не происходит. InputAssemblies - список сборок из предыдущего элемента конфигурации. KeyFile - ключ для подписи итоговой сборки. Так как сборка плагинов должна быть подписана, нужно указать этот параметр. Для удобства я так же использую переменную MSBuild вместо ссылки на файл. Если хотите, можете использовать что-то вроде "$(ProjectDir)\AnyKey.snk". OutputFile - название итоговой сборки. Может отличаться от имени основной сборки проекта, или совпадать, как в этом примере. Полный список параметров задачи можно посмотреть тут: https://github.com/ravibpatel/ILRepack.Lib.MSBuild.Task
Теперь заново загрузим проект и выполним команду Rebuild. Это важно! Если студия решит, что изменений в исходниках не было, то сборка не будет запущена, а значит не будет выполнена и наша задача AfterBuild.
Если все прошло удачно, в консоли мы видим примерно такой результат:
Вы также должны заметить, что итоговая сборка заметно набрала вес, что не удивительно - мы добавили в нее зависимую сборку Newtonsoft.Json.dll. Возможно, вы не увидите прирост скорости слияния на таких объемах, но точно оцените его, когда у вас будет 10 зависимых сборок по 100 публичных классов в каждой.
Теперь обновим нашу сборку в CRM и мы увидим, что теперь мы можем сохранить организацию без ошибок. На всякий случай заглянем в журнал трассировки и увидим заветное сообщение:
На всякий случай напоминаю, что по умолчания журнал трассировки плагинов выключен, так что, если хотите увидеть это сообщение, нужно зайти в Системные параметры на вкладку Настройка и разрешить ведение журнала.
Какие могут быть сложности в реальном проекте?
На более сложном проекте, сборки которого имеют сложные зависимости, ILRepack может начать ругаться на то, что в списке сборок InputAssemblies отсутствуют зависимые библиотеки. Не факт, что вы столкнетесь с этой проблемой, однако в моем проекте, инструмент в процессе слияния выдавал ошибку "невозможности загрузить Mocrosoft.Xrm.Sdk.dll или одну из ее зависимостей". Ни в коем случае не включайте сборки .NET, или SDK в свою сборку! Вы гарантированно получите трудно диагностируемый reference hell. Вместо этого, используйте параметр LibraryPath ( -lib в командной строке) чтобы указать каталог, где живут сборки SDK (даже если это тупо каталог bin - $(OutputPath)).
Второй момент. Если вам, по какой-то причине нужно точно указать -targetPlatform (параметр командной строки ILMerge/ILRepack), например,
Знайте, что у таски для этого два параметра: TargetPlatformVersion и TargetPlatformDirectory. Изначально, второго параметра у нее вообще не было, это мы с автором допиливали вместе. Указать оба параметра может потребоваться, если вы где-то используете рефлексию, или какой-нибудь DI инжектор типа Ninject. В моем случае с ним были проблемы, если не указан параметр TargetPlatformDirectory.
Третий момент. ILRepack имеет полезный параметр Internalize, которого нет в ILMerge. Его польза заключается в том, что он делает все типы, кроме тех что описаны в первой сборке internal. Проще говоря, если в других сборках были публичные типы (public class), то их модификатор доступа будет изменен на Internal. Это может быть очень полезно, если вы собираетесь передавать свою сборку третьим лицам и не хотите, чтобы они получили доступ к вашим базовым классам и другим инструментам. Однако!, если вы используете early-binding, это может стать проблемой. Может так оказаться, что ваши классы-наследники Entity находятся не в той сборке, где находится сам плагин, например, MyProject.Entities.dll. Это довольно распространенная практика, чтобы использовать одни и те же DTO классы совместно с проектами Plugins, Workflows и какими-то внешними. В этом случае, если вы собираетесь использовать параметр Internalize, не забывайте указать эту сборку в параметре InternalizeExclude. Иначе вы получите ошибку Unknown type при десерелизации этих типов в любом запросе к Organization Service.
Четвертый момент. Вероятно, это проблема Visual Studio, а не самой задачи, но о ней все же стоит знать новичку. Если ваш файл проекта уже был модифицирован, по какой-то причине при установке, или обновлении версии задачи ILRepack.Lib.MSBuild.Task, строка
Может быть вставлена после <Target Name="AfterBuild" >. Студия не будет на это ругаться, однако при этом задача не будет выполнена. Обязательно убедитесь, что в процессе билда появляется сообщение "Merge succeeded"
На этом, вроде бы, все. Надеюсь статья будет вам полезна. Честно говоря, учитывая количество читателей я думаю забить вести блог на этой площадке и перебираться на какой-то более цитируемый источник
С одной стороны, вопрос поддержки совместимости с онлайн версией не так уж и актуален в нашей стране; виной тому и спорные моменты в законодательстве и особенности менталитета. С другой стороны, не одним онлайном жив сэндбокс! Так что, если вы можете, убрать нагрузку с фронтэнда и перенести ее на апликейшен сервер, то не стоит жертвовать такой возможностью.
Так как же быть, если ваша сборка плагинов набрала вес и процесс публикации решения занимает 20 минут (реальная цифра для сборки большого решения в режиме DLL + PDB)? Выход есть. Тот же подход, но новые версии инструментов. Представляю вашему вниманию, ILRepack: https://github.com/gluck/il-repack. Утилита использует тот же синтаксис командной строки что и ILMerge, так что у вас не должно быть проблем с переходом. Если же вместо командной строки вы используете таски для MSBuild, тогда рекомендую вот этот вариант: https://github.com/ravibpatel/ILRepack.Lib.MSBuild.Task. Есть и другие проекты, но они у меня не заработали, а в этот я даже немного поконтрибутил.
Какие есть плюсы? В моем случае, размер итоговой сборки плагинов уменьшился с 16 до 9 MB (и, следовательно, выросла скорость ее загрузки на сервер). Что касается времени на выполнение слияния, то оно уменьшилось с 1,5 минут, до 16 секунд для DLL + PDB. Считаю, что это вполне весомый аргумент, чтобы пройти через муки перехода и тестирования.
Давайте теперь посмотрим его в работе! Для этого откроем Visual Studio и создадим новый проект типа Class Library (можно использовать любой, но в демо я делаю упор на сценарии разработки под CRM). Я назову его ILRepack.Sample:
Убедитесь, что используете правильную версию .NET Framework, которая совместима с вашей версией CRM. Для D365 (v 8) подходит 4.5.2.
Теперь давайте откроем Package Manager Console и установим несколько NuGet пакетов:
Для начала подключим необходимые сборки SDK. Для этого выполним команду:
X++:
Install-Package Microsoft.CrmSdk.CoreAssemblies -Version 8.2.0.2
Далее, чисто для примера я установлю популярную сборку Newtonsoft.Json. Разумеется, вы можете использовать любую:
X++:
Install-Package Newtonsoft.Json
X++:
using Microsoft.Xrm.Sdk; using System; namespace ILRepack.Sample { public class Plugin : IPlugin { public void Execute(IServiceProvider serviceProvider) { Run(serviceProvider); } private static void Run(IServiceProvider serviceProvider) { ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService)); Type knownType = typeof(Newtonsoft.Json.JsonSerializer); tracingService.Trace($"{ knownType } is known type"); } } }
Теперь нужно как-то выполнить плагин. Для простоты, зарегистрируем шаг на создание Организации. Вы можете выбрать любой другой способ:
И выполним наш плагин тем способом, которым задумали на предыдущем шаге.
Если плагин синхронный, мы должны немедленно увидеть ошибку:
Мы видим, что при выполнении плагина произошла ошибка: в песочнице отсутствует сборка "Newtonsoft.Json". На самом деле, ошибку выбросил JIT компилятор еще до запуска нашего плагина: мы не сможем поймать ее в try-catch как мы не изгилялись. Тем не менее, пришло время это исправить!
Чтобы это сделать, установим нужную нам таску для MSBuild выполнив следующую команду в консоли NuGet:
X++:
Install-Package ILRepack.Lib.MSBuild.Task -Version 2.0.15.2
После чего мы сможем изменить файл проекта (в VS 2017 можно редактировать проект, не выгружая его из памяти):
Теперь найдем строчку <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
Сразу после нее должен появиться импорт нашей таски:
Теперь нам нужно раскомментировать блок
X++:
<Target Name="AfterBuild">
</Target>
X++:
<Target Name="AfterBuild"> <ItemGroup> <InputAssemblies Include="$(OutputPath)\$(AssemblyName).dll" /> <InputAssemblies Include="$(OutputPath)\Newtonsoft.Json.dll" /> </ItemGroup> <ILRepack Parallel="true" InputAssemblies="@(InputAssemblies)" KeyFile="$(AssemblyOriginatorKeyFile)" OutputFile="$(OutputPath)\$(AssemblyName).dll" /> </Target>
<Target Name="AfterBuild"> Обозначает, что ILRepack будет запущен после сборки. При необходимости, можно указать условия запуска, например, <Target Name="AfterBuild" Condition="'$(Configuration)' == 'Release'">. Это может быть полезно, если вы отлаживаетесь локально с использованием Plugin Profiler. В этом случае вам априори доступны все зависимые сборки и можно не терять время на слияние.
Группа значений <ItemGroup\InputAssemblies>. Как нетрудно догадаться, это перечень сборок, которые нужно объединить. Неочевидно, но факт: первой должна идти основная сборка проекта. С точки зрения MSBuild основной сборки там может не быть вообще, однако задача ILRepack.Lib.MSBuild.Task и сам ILRepack ожидают от нас именно это. Чтобы не хардкодить значение, мы используем параметры MSBuild $(OutputPath) и$(AssemblyName). Полный список можно посмотреть тут: https://msdn.microsoft.com/ru-ru/library/bb629394.aspx
Далее идут параметры самой задачи <ILRepack />. Parallel говорит, что сборка будет осуществляться параллельно несколькими ядрами CPU. Впрочем, судя по коду ILRepack, этого сейчас не происходит. InputAssemblies - список сборок из предыдущего элемента конфигурации. KeyFile - ключ для подписи итоговой сборки. Так как сборка плагинов должна быть подписана, нужно указать этот параметр. Для удобства я так же использую переменную MSBuild вместо ссылки на файл. Если хотите, можете использовать что-то вроде "$(ProjectDir)\AnyKey.snk". OutputFile - название итоговой сборки. Может отличаться от имени основной сборки проекта, или совпадать, как в этом примере. Полный список параметров задачи можно посмотреть тут: https://github.com/ravibpatel/ILRepack.Lib.MSBuild.Task
Теперь заново загрузим проект и выполним команду Rebuild. Это важно! Если студия решит, что изменений в исходниках не было, то сборка не будет запущена, а значит не будет выполнена и наша задача AfterBuild.
Если все прошло удачно, в консоли мы видим примерно такой результат:
X++:
1>------ Rebuild All started: Project: ILRepack.Sample, Configuration: Debug Any CPU ------ 1> ILRepack.Sample -> \ILRepack.Sample\bin\Debug\ILRepack.Sample.dll 1> Added assembly 'bin\Debug\\ILRepack.Sample.dll' 1> Added assembly 'bin\Debug\\Newtonsoft.Json.dll' 1> Merging 2 assembies to 'bin\Debug\\ILRepack.Sample.dll' 1> Merge succeeded in 1,1207542 s ========== Rebuild All: 1 succeeded, 0 failed, 0 skipped ==========
Теперь обновим нашу сборку в CRM и мы увидим, что теперь мы можем сохранить организацию без ошибок. На всякий случай заглянем в журнал трассировки и увидим заветное сообщение:
На всякий случай напоминаю, что по умолчания журнал трассировки плагинов выключен, так что, если хотите увидеть это сообщение, нужно зайти в Системные параметры на вкладку Настройка и разрешить ведение журнала.
Какие могут быть сложности в реальном проекте?
На более сложном проекте, сборки которого имеют сложные зависимости, ILRepack может начать ругаться на то, что в списке сборок InputAssemblies отсутствуют зависимые библиотеки. Не факт, что вы столкнетесь с этой проблемой, однако в моем проекте, инструмент в процессе слияния выдавал ошибку "невозможности загрузить Mocrosoft.Xrm.Sdk.dll или одну из ее зависимостей". Ни в коем случае не включайте сборки .NET, или SDK в свою сборку! Вы гарантированно получите трудно диагностируемый reference hell. Вместо этого, используйте параметр LibraryPath ( -lib в командной строке) чтобы указать каталог, где живут сборки SDK (даже если это тупо каталог bin - $(OutputPath)).
Второй момент. Если вам, по какой-то причине нужно точно указать -targetPlatform (параметр командной строки ILMerge/ILRepack), например,
X++:
/targetplatform:v4,C:\Windows\Microsoft.NET\Framework\v4.0.30319
Третий момент. ILRepack имеет полезный параметр Internalize, которого нет в ILMerge. Его польза заключается в том, что он делает все типы, кроме тех что описаны в первой сборке internal. Проще говоря, если в других сборках были публичные типы (public class), то их модификатор доступа будет изменен на Internal. Это может быть очень полезно, если вы собираетесь передавать свою сборку третьим лицам и не хотите, чтобы они получили доступ к вашим базовым классам и другим инструментам. Однако!, если вы используете early-binding, это может стать проблемой. Может так оказаться, что ваши классы-наследники Entity находятся не в той сборке, где находится сам плагин, например, MyProject.Entities.dll. Это довольно распространенная практика, чтобы использовать одни и те же DTO классы совместно с проектами Plugins, Workflows и какими-то внешними. В этом случае, если вы собираетесь использовать параметр Internalize, не забывайте указать эту сборку в параметре InternalizeExclude. Иначе вы получите ошибку Unknown type при десерелизации этих типов в любом запросе к Organization Service.
Четвертый момент. Вероятно, это проблема Visual Studio, а не самой задачи, но о ней все же стоит знать новичку. Если ваш файл проекта уже был модифицирован, по какой-то причине при установке, или обновлении версии задачи ILRepack.Lib.MSBuild.Task, строка
X++:
<Import Project="..\packages\ILRepack.Lib.MSBuild.Task.2.0.15.2\build\ILRepack.Lib.MSBuild.Task.targets" Condition="Exists('..\packages\ILRepack.Lib.MSBuild.Task.2.0.15.2\build\ILRepack.Lib.MSBuild.Task.targets')" />
На этом, вроде бы, все. Надеюсь статья будет вам полезна. Честно говоря, учитывая количество читателей я думаю забить вести блог на этой площадке и перебираться на какой-то более цитируемый источник
Всего комментариев 6
Комментарии
-
Цитата:Какие могут быть сложности в реальном проекте?
На более сложном проекте, сборки которого имеют сложные зависимости, ILRepack может начать ругаться на то, что в списке сборок InputAssemblies отсутствуют зависимые библиотеки. Не факт, что вы столкнетесь с этой проблемой, однако в моем проекте, инструмент в процессе слияния выдавал ошибку "невозможности загрузить Mocrosoft.Xrm.Sdk.dll или одну из ее зависимостей". Ни в коем случае не включайте сборки .NET, или SDK в свою сборку! Вы гарантированно получите трудно диагностируемый reference hell. Вместо этого, используйте параметр LibraryPath ( -lib в командной строке) чтобы указать каталог, где живут сборки SDK (даже если это тупо каталог bin - $(OutputPath)).
А если ServiceContext и файл-описание и исключить из проекта, все мержится хорошо.X++:Failed to resolve assembly: 'Microsoft.Xrm.Sdk, Version=8.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'
Подскажите, как это можно обойти, правильно ли я понял что цитата выше решает эту проблему?Запись от ximik33rus размещена 06.08.2019 в 13:25 -
Цитата:Если используется ServiceContext и файл описания метаданных CRM, у нас возникает ошибка:
А если ServiceContext и файл-описание и исключить из проекта, все мержится хорошо.X++:Failed to resolve assembly: 'Microsoft.Xrm.Sdk, Version=8.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'
Подскажите, как это можно обойти, правильно ли я понял что цитата выше решает эту проблему?Запись от Артем Enot Грунин размещена 06.08.2019 в 13:48 -
Спасибо, уже нашли Вашу новую статью + https://github.com/ravibpatel/ILRepack.Lib.MSBuild.Task, но и по ним не происходит запуска слияния длл.
Вот наши файлы. что может быть не так?
X++:ILRepack.targets <!-- ILRepack --> <?xml version="1.0" encoding="utf-8" ?> <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> < Target Name = "AfterBuild" Condition = "'$(Configuration)' == 'Debug'" > <ItemGroup> <InputAssemblies Include="$(OutputPath)\$(AssemblyName).dll" /> <InputAssemblies Include="$(OutputPath)\Newtonsoft.Json.dll" /> </ItemGroup> <ILRepack Parallel="true" <!-- Internalize = " true " --> <!-- InternalizeExclude = "@(DoNotInternalizeAssemblies)" --> InputAssemblies="@(InputAssemblies)" LibraryPath="$(OutputPath)" KeyFile="$(AssemblyOriginatorKeyFile)" TargetKind = "Dll" OutputFile="$(OutputPath)\$(AssemblyName).dll"/> </Target> </Project> ILRepack.Config.props <? xml version = " 1.0 " encoding = " utf-8 " ?> < Project xmlns = " [url]http://schemas.microsoft.com/developer/msbuild/2003[/url] " > < PropertyGroup > < ILRepackTargetsFile > $(SolutionDir) ILRepack.targets </ ILRepackTargetsFile > < KeyFile > $(SolutionDir)123.snk </ KeyFile > </ PropertyGroup > </ Project >
Запись от ximik33rus размещена 06.08.2019 в 14:52 -
Очень хороший вопрос! Есть пара вещей, которые меня смущают, но они не должны быть очень уж критичны. Конфиг, который я опубликовал тут и в новой статье взят из реального проекта, который корректно собирается. props-файл я вообще не использую. Зачем он вам, вы же уже указали ключ? Возможно это следует включить в мануал. Нужно "подписать" основную сборку, остальные замурует в нее с тем же ключом. Вторая странность в том, что вы мержите только для Debug конфигурации. Обычно или делают наоборот: собирают только для релиза, или вообще не указывают условие.
Все-таки, давайте к нашим баранам. Возможно и не в мерже проблема, а в самой сборке. Что за контекст и что за файлы метаданных?Запись от Артем Enot Грунин размещена 06.08.2019 в 15:46 -
Спасибо, разобрались! Опечатались в одном месте.
И props-файл действительно не нужен.Запись от ximik33rus размещена 06.08.2019 в 17:01 -
Запись от Артем Enot Грунин размещена 06.08.2019 в 17:07