AXForum  
Вернуться   AXForum > Блоги > CRM, SharePoint и Черная Магия
All
Забыли пароль?
Зарегистрироваться Правила Справка Пользователи Сообщения за день Поиск Все разделы прочитаны

Добро пожаловать в мой блог! Изначально он не задумывался как блог CRM разработчика, но жизнь сама внесла нужные коррективы. Тут я публикою все свои наблюдения относительно обозначенных в заголовке систем. Если Вы найдете в нем что-то интересное для Вас, как для заказчика, то буду рад сотрудничать с Вами! В моей компетенции 100% задач по MS CRM 3.0/4.0/2011:
  • Консалтинг
  • Проектирование
  • Разработка
  • Обучение


MVP 2010, 2011
Рейтинг: 4.50. Голосов: 2.

Еще один способ регистрации сборок с зависимостями

Запись от Артем Enot Грунин размещена 28.12.2017 в 11:07
Теги assembly, ilrepack

В одной из прошлых статей я рассказывал о замечательном инструменте 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:

Нажмите на изображение для увеличения
Название: 01. New Project.png
Просмотров: 60867
Размер:	75.7 Кб
ID:	426

Убедитесь, что используете правильную версию .NET Framework, которая совместима с вашей версией CRM. Для D365 (v 8) подходит 4.5.2.

Теперь давайте откроем Package Manager Console и установим несколько NuGet пакетов:

Нажмите на изображение для увеличения
Название: 02. Nuget.png
Просмотров: 61524
Размер:	28.4 Кб
ID:	427

Для начала подключим необходимые сборки SDK. Для этого выполним команду:
X++:
Install-Package Microsoft.CrmSdk.CoreAssemblies -Version 8.2.0.2
Опять же, правильно укажите версию. Я использую последнюю доступную для v8.

Далее, чисто для примера я установлю популярную сборку Newtonsoft.Json. Разумеется, вы можете использовать любую:
X++:
Install-Package Newtonsoft.Json
Давайте теперь напишем простой плагин. Для этого заменим содержимое Class1 на что-то вроде:
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");
        }
    }
}
Давайте теперь подпишем, соберем нашу сборку и зарегистрируем ее в системе. Для простоты примера я использую Plugin Registrator в составе XrmToolBox, но вы можете использовать любой инструмент. При регистрации укажем, что сборку нужно разместить в Sandbox:

Нажмите на изображение для увеличения
Название: 03. Deploy.png
Просмотров: 60881
Размер:	134.6 Кб
ID:	428

Теперь нужно как-то выполнить плагин. Для простоты, зарегистрируем шаг на создание Организации. Вы можете выбрать любой другой способ:

Нажмите на изображение для увеличения
Название: 04. Plugin.png
Просмотров: 60862
Размер:	115.8 Кб
ID:	429

И выполним наш плагин тем способом, которым задумали на предыдущем шаге.

Если плагин синхронный, мы должны немедленно увидеть ошибку:

Нажмите на изображение для увеличения
Название: 05. Error.png
Просмотров: 61060
Размер:	26.4 Кб
ID:	430

Мы видим, что при выполнении плагина произошла ошибка: в песочнице отсутствует сборка "Newtonsoft.Json". На самом деле, ошибку выбросил JIT компилятор еще до запуска нашего плагина: мы не сможем поймать ее в try-catch как мы не изгилялись. Тем не менее, пришло время это исправить!

Чтобы это сделать, установим нужную нам таску для MSBuild выполнив следующую команду в консоли NuGet:
X++:
Install-Package ILRepack.Lib.MSBuild.Task -Version 2.0.15.2
После этого, необходимо выгрузить наш проект из памяти VS:

Нажмите на изображение для увеличения
Название: 06. Unload.png
Просмотров: 61053
Размер:	34.2 Кб
ID:	431

После чего мы сможем изменить файл проекта (в VS 2017 можно редактировать проект, не выгружая его из памяти):

Название: 07. Edit.png
Просмотров: 8655

Размер: 19.6 Кб

Теперь найдем строчку <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

Сразу после нее должен появиться импорт нашей таски:

Нажмите на изображение для увеличения
Название: 08. Cproj.png
Просмотров: 60998
Размер:	37.0 Кб
ID:	433

Теперь нам нужно раскомментировать блок
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 ==========
Вы также должны заметить, что итоговая сборка заметно набрала вес, что не удивительно - мы добавили в нее зависимую сборку Newtonsoft.Json.dll. Возможно, вы не увидите прирост скорости слияния на таких объемах, но точно оцените его, когда у вас будет 10 зависимых сборок по 100 публичных классов в каждой.

Теперь обновим нашу сборку в CRM и мы увидим, что теперь мы можем сохранить организацию без ошибок. На всякий случай заглянем в журнал трассировки и увидим заветное сообщение:

Нажмите на изображение для увеличения
Название: 09. Trace.png
Просмотров: 61095
Размер:	19.7 Кб
ID:	434

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

Какие могут быть сложности в реальном проекте?

На более сложном проекте, сборки которого имеют сложные зависимости, 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
Знайте, что у таски для этого два параметра: 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, строка

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')" />
Может быть вставлена после <Target Name="AfterBuild" >. Студия не будет на это ругаться, однако при этом задача не будет выполнена. Обязательно убедитесь, что в процессе билда появляется сообщение "Merge succeeded"

На этом, вроде бы, все. Надеюсь статья будет вам полезна. Честно говоря, учитывая количество читателей я думаю забить вести блог на этой площадке и перебираться на какой-то более цитируемый источник
Размещено в CRM
Просмотров 805303 Комментарии 6
Всего комментариев 6

Комментарии

  1. Старый комментарий
    Цитата:
    Какие могут быть сложности в реальном проекте?

    На более сложном проекте, сборки которого имеют сложные зависимости, ILRepack может начать ругаться на то, что в списке сборок InputAssemblies отсутствуют зависимые библиотеки. Не факт, что вы столкнетесь с этой проблемой, однако в моем проекте, инструмент в процессе слияния выдавал ошибку "невозможности загрузить Mocrosoft.Xrm.Sdk.dll или одну из ее зависимостей". Ни в коем случае не включайте сборки .NET, или SDK в свою сборку! Вы гарантированно получите трудно диагностируемый reference hell. Вместо этого, используйте параметр LibraryPath ( -lib в командной строке) чтобы указать каталог, где живут сборки SDK (даже если это тупо каталог bin - $(OutputPath)).
    Если используется ServiceContext и файл описания метаданных CRM, у нас возникает ошибка:

    X++:
     Failed to resolve assembly: 'Microsoft.Xrm.Sdk, Version=8.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'
    А если ServiceContext и файл-описание и исключить из проекта, все мержится хорошо.

    Подскажите, как это можно обойти, правильно ли я понял что цитата выше решает эту проблему?
    Запись от ximik33rus размещена 06.08.2019 в 13:25 ximik33rus is offline
  2. Старый комментарий
    Аватар для Артем Enot Грунин
    Цитата:
    Сообщение от ximik33rus Просмотреть комментарий
    Если используется ServiceContext и файл описания метаданных CRM, у нас возникает ошибка:

    X++:
     Failed to resolve assembly: 'Microsoft.Xrm.Sdk, Version=8.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'
    А если ServiceContext и файл-описание и исключить из проекта, все мержится хорошо.

    Подскажите, как это можно обойти, правильно ли я понял что цитата выше решает эту проблему?
    Не совсем понял что вы добавляете, но да: нужно добавить параметр LibraryPath. Кроме того, в этой статье ошибка: не нужно править файл проекта, нужно создать отдельный конфиг. Попробуйте более новую статью: https://fixrm.wordpress.com/2019/06/...s-for-d365-ce/
    Запись от Артем Enot Грунин размещена 06.08.2019 в 13:48 Артем Enot Грунин is offline
  3. Старый комментарий
    Спасибо, уже нашли Вашу новую статью + 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 ximik33rus is offline
  4. Старый комментарий
    Аватар для Артем Enot Грунин
    Очень хороший вопрос! Есть пара вещей, которые меня смущают, но они не должны быть очень уж критичны. Конфиг, который я опубликовал тут и в новой статье взят из реального проекта, который корректно собирается. props-файл я вообще не использую. Зачем он вам, вы же уже указали ключ? Возможно это следует включить в мануал. Нужно "подписать" основную сборку, остальные замурует в нее с тем же ключом. Вторая странность в том, что вы мержите только для Debug конфигурации. Обычно или делают наоборот: собирают только для релиза, или вообще не указывают условие.


    Все-таки, давайте к нашим баранам. Возможно и не в мерже проблема, а в самой сборке. Что за контекст и что за файлы метаданных?
    Запись от Артем Enot Грунин размещена 06.08.2019 в 15:46 Артем Enot Грунин is offline
  5. Старый комментарий
    Спасибо, разобрались! Опечатались в одном месте.
    И props-файл действительно не нужен.
    Запись от ximik33rus размещена 06.08.2019 в 17:01 ximik33rus is offline
  6. Старый комментарий
    Аватар для Артем Enot Грунин
    Пожалуйста. В следующий раз пишите в новый блог
    Запись от Артем Enot Грунин размещена 06.08.2019 в 17:07 Артем Enot Грунин is offline
 


Рейтинг@Mail.ru
Часовой пояс GMT +3, время: 17:02.