<?xml version="1.0" encoding="UTF-8"?>

<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
	<channel>
		<title>AXForum - Блоги - CRM, SharePoint и Черная Магия. Автор Артем Enot Грунин</title>
		<link>//axforum.info/forums/blog.php?u=11149</link>
		<description>Microsoft Dynamics: Axapta, CRM, Navision. Форум, Вопросы и помощь специалистов.</description>
		<language>ru</language>
		<lastBuildDate>Fri, 24 Apr 2026 16:09:40 GMT</lastBuildDate>
		<generator>vBulletin</generator>
		<ttl>15</ttl>
		<image>
			<url>http://axforum.info//img.axforum.info/misc/rss.jpg</url>
			<title>AXForum - Блоги - CRM, SharePoint и Черная Магия. Автор Артем Enot Грунин</title>
			<link>//axforum.info/forums/blog.php?u=11149</link>
		</image>
		<item>
			<title>Блог переехал!</title>
			<link>//axforum.info/forums/blog.php?b=8270</link>
			<pubDate>Thu, 06 Jun 2019 17:07:25 GMT</pubDate>
			<description>Все привет! Спасибо всем кто оставался со мной на этом ресурсе, даже тогда когда его забыл Гугл :) Внешний поиск снова работает, но я все же решил двигаться дальше. Теперь мой блог выходит гораздо чаше и на английском (ну, некоторой его форме). Добро пожаловать: https://fixrm.wordpress.com</description>
			<content:encoded><![CDATA[<div>Все привет! Спасибо всем кто оставался со мной на этом ресурсе, даже тогда когда его забыл Гугл :) Внешний поиск снова работает, но я все же решил двигаться дальше. Теперь мой блог выходит гораздо чаше и на английском (ну, некоторой его форме). Добро пожаловать: <a href="https://fixrm.wordpress.com" target="_blank">https://fixrm.wordpress.com</a></div>

]]></content:encoded>
			<dc:creator>Артем Enot Грунин</dc:creator>
			<guid isPermaLink="true">//axforum.info/forums/blog.php?b=8270</guid>
		</item>
		<item>
			<title>Еще один способ регистрации сборок с зависимостями</title>
			<link>//axforum.info/forums/blog.php?b=8256</link>
			<pubDate>Thu, 28 Dec 2017 08:07:58 GMT</pubDate>
			<description><![CDATA[В одной из прошлых статей (http://www.axforum.info/forums/blog.php?b=8247) я рассказывал о замечательном инструменте 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:

Вложение 426 (//axforum.info/forums/attachment.php?attachmentid=426)

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

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

Вложение 427 (//axforum.info/forums/attachment.php?attachmentid=427)

Для начала подключим необходимые сборки SDK. Для этого выполним команду:

Install-Package Microsoft.CrmSdk.CoreAssemblies -Version 8.2.0.2
Опять же, правильно укажите версию. Я использую последнюю доступную для v8.

Далее, чисто для примера я установлю популярную сборку Newtonsoft.Json. Разумеется, вы можете использовать любую:

Install-Package Newtonsoft.Json
Давайте теперь напишем простой плагин. Для этого заменим содержимое Class1 на что-то вроде:

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:

Вложение 428 (//axforum.info/forums/attachment.php?attachmentid=428)

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

Вложение 429 (//axforum.info/forums/attachment.php?attachmentid=429)

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

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

Вложение 430 (//axforum.info/forums/attachment.php?attachmentid=430)

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

Чтобы это сделать, установим нужную нам таску для MSBuild выполнив следующую команду в консоли NuGet:

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

Вложение 431 (//axforum.info/forums/attachment.php?attachmentid=431)

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

Вложение 432 (//axforum.info/forums/attachment.php?attachmentid=432)

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

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

Вложение 433 (//axforum.info/forums/attachment.php?attachmentid=433)

Теперь нам нужно раскомментировать блок

<Target Name="AfterBuild">
  </Target>
И заменить его примерно следующим:

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

Если все прошло удачно, в консоли мы видим примерно такой результат:

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

Вложение 434 (//axforum.info/forums/attachment.php?attachmentid=434)

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

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

Второй момент. Если вам, по какой-то причине нужно точно указать *-targetPlatform* (параметр командной строки ILMerge/ILRepack), например,

/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, строка


  <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"

На этом, вроде бы, все. Надеюсь статья будет вам полезна. Честно говоря, учитывая количество читателей я думаю забить вести блог на этой площадке и перебираться на какой-то более цитируемый источник :)]]></description>
			<content:encoded><![CDATA[<div>В одной из <a href="http://www.axforum.info/forums/blog.php?b=8247" target="_blank">прошлых статей</a> я рассказывал о замечательном инструменте Fody/Costura, который позволяет быстрее и элегантнее, нежели ILMerge, объединить несколько сборок .NET в одну. Увы, инструмент имеет критический недостаток: такую сборку нельзя зарегистрировать в Sanbox и, следовательно, в Online версии.<br />
<br />
С одной стороны, вопрос поддержки совместимости с онлайн версией не так уж и актуален в нашей стране; виной тому и спорные моменты в законодательстве и особенности менталитета. С другой стороны, не одним онлайном жив сэндбокс! Так что, если вы можете, убрать нагрузку с фронтэнда и перенести ее на апликейшен сервер, то не стоит жертвовать такой возможностью.<br />
<br />
Так как же быть, если ваша сборка плагинов набрала вес и процесс публикации решения занимает 20 минут (реальная цифра для сборки большого решения в режиме DLL + PDB)? Выход есть. Тот же подход, но новые версии инструментов. Представляю вашему вниманию, ILRepack: <a href="https://github.com/gluck/il-repack" target="_blank">https://github.com/gluck/il-repack</a>. Утилита использует тот же синтаксис командной строки что и ILMerge, так что у вас не должно быть проблем с переходом. Если же вместо командной строки вы используете таски для MSBuild, тогда рекомендую вот этот вариант: <a href="https://github.com/ravibpatel/ILRepack.Lib.MSBuild.Task" target="_blank">https://github.com/ravibpatel/ILRepack.Lib.MSBuild.Task</a>. Есть и другие проекты, но они у меня не заработали, а в этот я даже немного поконтрибутил.<br />
<br />
Какие есть плюсы? В моем случае, размер итоговой сборки плагинов уменьшился с 16 до 9 MB (и, следовательно, выросла скорость ее загрузки на сервер). Что касается времени на выполнение слияния, то оно уменьшилось с 1,5 минут, до 16 секунд для DLL + PDB. Считаю, что это вполне весомый аргумент, чтобы пройти через муки перехода и тестирования.<br />
<br />
Давайте теперь посмотрим его в работе! Для этого откроем Visual Studio и создадим новый проект типа Class Library (можно использовать любой, но в демо я делаю упор на сценарии разработки под CRM). Я назову его ILRepack.Sample:<br />
<br />
<a href="//axforum.info/forums/blog_attachment.php?attachmentid=426&amp;d=1514447829" rel="Lightbox" id="attachment426" ><img src="//axforum.info/forums/blog_attachment.php?attachmentid=426&amp;thumb=1&amp;d=1514447829" class="thumbnail" border="0" alt="Нажмите на изображение для увеличения
Название: 01. New Project.png
Просмотров: 61615
Размер:	75.7 Кб
ID:	426" style="margin: 2px" /></a><br />
<br />
Убедитесь, что используете правильную версию .NET Framework, которая совместима с вашей версией CRM. Для D365 (v 8) подходит 4.5.2.<br />
<br />
Теперь давайте откроем Package Manager Console и установим несколько NuGet пакетов:<br />
<br />
<a href="//axforum.info/forums/blog_attachment.php?attachmentid=427&amp;d=1514447829" rel="Lightbox" id="attachment427" ><img src="//axforum.info/forums/blog_attachment.php?attachmentid=427&amp;thumb=1&amp;d=1514447829" class="thumbnail" border="0" alt="Нажмите на изображение для увеличения
Название: 02. Nuget.png
Просмотров: 62294
Размер:	28.4 Кб
ID:	427" style="margin: 2px" /></a><br />
<br />
Для начала подключим необходимые сборки SDK. Для этого выполним команду:<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">Install-Package Microsoft.CrmSdk.CoreAssemblies -Version 8.2.0.2</pre></div>Опять же, правильно укажите версию. Я использую последнюю доступную для v8.<br />
<br />
Далее, чисто для примера я установлю популярную сборку Newtonsoft.Json. Разумеется, вы можете использовать любую:<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">Install-Package Newtonsoft.Json</pre></div>Давайте теперь напишем простой плагин. Для этого заменим содержимое Class1 на что-то вроде:<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">using Microsoft.Xrm.Sdk;
using System;

namespace ILRepack.Sample
{
    <span style="color: blue">public</span> <span style="color: blue">class</span> Plugin : IPlugin
    {
        <span style="color: blue">public</span> <span style="color: blue">void</span> Execute(IServiceProvider serviceProvider)
        {
            Run(serviceProvider);
        }

        <span style="color: blue">private</span> <span style="color: blue">static</span> <span style="color: blue">void</span> Run(IServiceProvider serviceProvider)
        {
            ITracingService tracingService =
                (ITracingService)serviceProvider.GetService(typeof(ITracingService));

            Type knownType = typeof(Newtonsoft.Json.JsonSerializer);
            tracingService.Trace($<span style="color: red">&quot;{ knownType } is known type&quot;</span>);
        }
    }
}</pre></div>Давайте теперь подпишем, соберем нашу сборку и зарегистрируем ее в системе. Для простоты примера я использую Plugin Registrator в составе XrmToolBox, но вы можете использовать любой инструмент. При регистрации укажем, что сборку нужно разместить в Sandbox:<br />
<br />
<a href="//axforum.info/forums/blog_attachment.php?attachmentid=428&amp;d=1514447836" rel="Lightbox" id="attachment428" ><img src="//axforum.info/forums/blog_attachment.php?attachmentid=428&amp;thumb=1&amp;d=1514447836" class="thumbnail" border="0" alt="Нажмите на изображение для увеличения
Название: 03. Deploy.png
Просмотров: 61644
Размер:	134.6 Кб
ID:	428" style="margin: 2px" /></a><br />
<br />
Теперь нужно как-то выполнить плагин. Для простоты, зарегистрируем шаг на создание Организации. Вы можете выбрать любой другой способ:<br />
<br />
<a href="//axforum.info/forums/blog_attachment.php?attachmentid=429&amp;d=1514447836" rel="Lightbox" id="attachment429" ><img src="//axforum.info/forums/blog_attachment.php?attachmentid=429&amp;thumb=1&amp;d=1514447836" class="thumbnail" border="0" alt="Нажмите на изображение для увеличения
Название: 04. Plugin.png
Просмотров: 61517
Размер:	115.8 Кб
ID:	429" style="margin: 2px" /></a><br />
<br />
И выполним наш плагин тем способом, которым задумали на предыдущем шаге.<br />
<br />
Если плагин синхронный, мы должны немедленно увидеть ошибку:<br />
<br />
<a href="//axforum.info/forums/blog_attachment.php?attachmentid=430&amp;d=1514447844" rel="Lightbox" id="attachment430" ><img src="//axforum.info/forums/blog_attachment.php?attachmentid=430&amp;thumb=1&amp;d=1514447844" class="thumbnail" border="0" alt="Нажмите на изображение для увеличения
Название: 05. Error.png
Просмотров: 61788
Размер:	26.4 Кб
ID:	430" style="margin: 2px" /></a><br />
<br />
Мы видим, что при выполнении плагина произошла ошибка: в песочнице отсутствует сборка &quot;Newtonsoft.Json&quot;. На самом деле, ошибку выбросил JIT компилятор еще до запуска нашего плагина: мы не сможем поймать ее в try-catch как мы не изгилялись. Тем не менее, пришло время это исправить!<br />
<br />
Чтобы это сделать, установим нужную нам таску для MSBuild выполнив следующую команду в консоли NuGet:<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">Install-Package ILRepack.Lib.MSBuild.Task -Version 2.0.15.2</pre></div>После этого, необходимо выгрузить наш проект из памяти VS:<br />
<br />
<a href="//axforum.info/forums/blog_attachment.php?attachmentid=431&amp;d=1514447844" rel="Lightbox" id="attachment431" ><img src="//axforum.info/forums/blog_attachment.php?attachmentid=431&amp;thumb=1&amp;d=1514447844" class="thumbnail" border="0" alt="Нажмите на изображение для увеличения
Название: 06. Unload.png
Просмотров: 61796
Размер:	34.2 Кб
ID:	431" style="margin: 2px" /></a><br />
<br />
После чего мы сможем изменить файл проекта (в VS 2017 можно редактировать проект, не выгружая его из памяти):<br />
<br />
<img src="//axforum.info/forums/blog_attachment.php?attachmentid=432&amp;d=1514447851" border="0" alt="Название: 07. Edit.png
Просмотров: 404067

Размер: 19.6 Кб" style="margin: 2px" /><br />
<br />
Теперь найдем строчку  &lt;Import Project=&quot;$(MSBuildToolsPath)\Microsoft.CSharp.targets&quot; /&gt;<br />
<br />
Сразу после нее должен появиться импорт нашей таски:<br />
<br />
<a href="//axforum.info/forums/blog_attachment.php?attachmentid=433&amp;d=1514447851" rel="Lightbox" id="attachment433" ><img src="//axforum.info/forums/blog_attachment.php?attachmentid=433&amp;thumb=1&amp;d=1514447851" class="thumbnail" border="0" alt="Нажмите на изображение для увеличения
Название: 08. Cproj.png
Просмотров: 61762
Размер:	37.0 Кб
ID:	433" style="margin: 2px" /></a><br />
<br />
Теперь нам нужно раскомментировать блок<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">&lt;Target Name=<span style="color: red">&quot;AfterBuild&quot;</span>&gt;
  &lt;/Target&gt;</pre></div>И заменить его примерно следующим:<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">  &lt;Target Name=<span style="color: red">&quot;AfterBuild&quot;</span>&gt;
    &lt;ItemGroup&gt;
      &lt;InputAssemblies Include=<span style="color: red">&quot;$(OutputPath)\$(AssemblyName).dll&quot;</span> /&gt;
      &lt;InputAssemblies Include=<span style="color: red">&quot;$(OutputPath)\Newtonsoft.Json.dll&quot;</span> /&gt;
    &lt;/ItemGroup&gt;
    &lt;ILRepack 
      Parallel=<span style="color: red">&quot;true&quot;</span>
      InputAssemblies=<span style="color: red">&quot;@(InputAssemblies)&quot;</span>
      KeyFile=<span style="color: red">&quot;$(AssemblyOriginatorKeyFile)&quot;</span> 
      OutputFile=<span style="color: red">&quot;$(OutputPath)\$(AssemblyName).dll&quot;</span>
   /&gt;
  &lt;/Target&gt;</pre></div>Разберем содержимое нашего конфига. <br />
<br />
&lt;Target Name=&quot;AfterBuild&quot;&gt; Обозначает, что ILRepack будет запущен после сборки. При необходимости, можно указать условия запуска, например, &lt;Target Name=&quot;AfterBuild&quot; Condition=&quot;'$(Configuration)' == 'Release'&quot;&gt;. Это может быть полезно, если вы отлаживаетесь локально с использованием Plugin Profiler. В этом случае вам априори доступны все зависимые сборки и можно не терять время на слияние.<br />
<br />
Группа значений &lt;ItemGroup\InputAssemblies&gt;. Как нетрудно догадаться, это перечень сборок, которые нужно объединить. Неочевидно, но факт: первой должна идти основная сборка проекта. С точки зрения MSBuild основной сборки там может не быть вообще, однако задача ILRepack.Lib.MSBuild.Task и сам ILRepack ожидают от нас именно это. Чтобы не хардкодить значение, мы используем параметры MSBuild $(OutputPath) и$(AssemblyName). Полный список можно посмотреть тут: <a href="https://msdn.microsoft.com/ru-ru/library/bb629394.aspx" target="_blank">https://msdn.microsoft.com/ru-ru/library/bb629394.aspx</a><br />
<br />
Далее идут параметры самой задачи &lt;ILRepack /&gt;. Parallel говорит, что сборка будет осуществляться параллельно несколькими ядрами CPU. Впрочем, судя по коду ILRepack, этого сейчас не происходит. InputAssemblies - список сборок из предыдущего элемента конфигурации. KeyFile - ключ для подписи итоговой сборки. Так как сборка плагинов должна быть подписана, нужно указать этот параметр. Для удобства я так же использую переменную MSBuild вместо ссылки на файл. Если хотите, можете использовать что-то вроде &quot;$(ProjectDir)\AnyKey.snk&quot;. OutputFile - название итоговой сборки. Может отличаться от имени основной сборки проекта, или совпадать, как в этом примере. Полный список параметров задачи можно посмотреть тут: <a href="https://github.com/ravibpatel/ILRepack.Lib.MSBuild.Task" target="_blank">https://github.com/ravibpatel/ILRepack.Lib.MSBuild.Task</a><br />
<br />
Теперь заново загрузим проект и выполним команду Rebuild. <b>Это важно! </b>Если студия решит, что изменений в исходниках не было, то сборка не будет запущена, а значит не будет выполнена и наша задача AfterBuild.<br />
<br />
Если все прошло удачно, в консоли мы видим примерно такой результат:<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">1&gt;------ Rebuild All started: Project: ILRepack.Sample, Configuration: Debug Any CPU ------
1&gt;  ILRepack.Sample -&gt; \ILRepack.Sample\bin\Debug\ILRepack.Sample.dll
1&gt;  Added assembly <span style="color: red">'bin\Debug\\ILRepack.Sample.dll'</span>
1&gt;  Added assembly <span style="color: red">'bin\Debug\\Newtonsoft.Json.dll'</span>
1&gt;  Merging 2 assembies to <span style="color: red">'bin\Debug\\ILRepack.Sample.dll'</span>
1&gt;  Merge succeeded in 1,1207542 s
========== Rebuild All: 1 succeeded, 0 failed, 0 skipped ==========</pre></div>Вы также должны заметить, что итоговая сборка заметно набрала вес, что не удивительно - мы добавили в нее зависимую сборку Newtonsoft.Json.dll. Возможно, вы не увидите прирост скорости слияния на таких объемах, но точно оцените его, когда у вас будет 10 зависимых сборок по 100 публичных классов в каждой.<br />
<br />
Теперь обновим нашу сборку в CRM и мы увидим, что теперь мы можем сохранить организацию без ошибок. На всякий случай заглянем в журнал трассировки и увидим заветное сообщение:<br />
<br />
<a href="//axforum.info/forums/blog_attachment.php?attachmentid=434&amp;d=1514447857" rel="Lightbox" id="attachment434" ><img src="//axforum.info/forums/blog_attachment.php?attachmentid=434&amp;thumb=1&amp;d=1514447857" class="thumbnail" border="0" alt="Нажмите на изображение для увеличения
Название: 09. Trace.png
Просмотров: 61850
Размер:	19.7 Кб
ID:	434" style="margin: 2px" /></a><br />
<br />
На всякий случай напоминаю, что по умолчания журнал трассировки плагинов выключен, так что, если хотите увидеть это сообщение, нужно зайти в Системные параметры на вкладку Настройка и разрешить ведение журнала.<br />
<br />
<b>Какие могут быть сложности в реальном проекте? <br />
</b><br />
На более сложном проекте, сборки которого имеют сложные зависимости, ILRepack может начать ругаться на то, что в списке сборок InputAssemblies отсутствуют зависимые библиотеки. Не факт, что вы столкнетесь с этой проблемой, однако в моем проекте, инструмент в процессе слияния выдавал ошибку &quot;невозможности загрузить Mocrosoft.Xrm.Sdk.dll или одну из ее зависимостей&quot;. <b>Ни в коем случае не включайте сборки .NET, или SDK в свою сборку!</b> Вы гарантированно получите трудно диагностируемый reference hell. Вместо этого, используйте параметр LibraryPath ( -lib в командной строке) чтобы указать каталог, где живут сборки SDK (даже если это тупо каталог bin - $(OutputPath)).<br />
<br />
Второй момент. Если вам, по какой-то причине нужно точно указать <b>-targetPlatform</b> (параметр командной строки ILMerge/ILRepack), например,<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">/targetplatform:v4,C:\Windows\Microsoft.NET\Framework\v4.0.30319</pre></div>Знайте, что у таски для этого два параметра: TargetPlatformVersion и TargetPlatformDirectory. Изначально, второго параметра у нее вообще не было, это мы с автором допиливали вместе. <b>Указать оба параметра</b> может потребоваться, если вы где-то используете рефлексию, или какой-нибудь DI инжектор типа Ninject. В моем случае с ним были проблемы, если не указан параметр TargetPlatformDirectory.<br />
<br />
Третий момент. ILRepack имеет полезный параметр Internalize, которого нет в ILMerge. Его польза заключается в том, что он делает все типы, кроме тех что описаны в первой сборке internal. Проще говоря, если в других сборках были публичные типы (public class), то их модификатор доступа будет изменен на Internal. Это может быть очень полезно, если вы собираетесь передавать свою сборку третьим лицам и не хотите, чтобы они получили доступ к вашим базовым классам и другим инструментам. <b>Однако!</b>, если вы используете early-binding, это может стать проблемой. Может так оказаться, что ваши классы-наследники Entity находятся не в той сборке, где находится сам плагин, например, MyProject.Entities.dll. Это довольно распространенная практика, чтобы использовать одни и те же DTO классы совместно с проектами Plugins, Workflows и какими-то внешними. В этом случае, если вы собираетесь использовать параметр Internalize, не забывайте указать эту сборку в параметре InternalizeExclude. Иначе вы получите ошибку Unknown type при десерелизации этих типов в любом запросе к Organization Service.<br />
<br />
Четвертый момент. Вероятно, это проблема Visual Studio, а не самой задачи, но о ней все же стоит знать новичку. Если ваш файл проекта уже был модифицирован, по какой-то причине при установке, или обновлении версии задачи ILRepack.Lib.MSBuild.Task, строка<br />
<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">  &lt;Import Project=<span style="color: red">&quot;..\packages\ILRepack.Lib.MSBuild.Task.2.0.15.2\build\ILRepack.Lib.MSBuild.Task.targets&quot;</span> Condition=<span style="color: red">&quot;Exists('..\packages\ILRepack.Lib.MSBuild.Task.2.0.15.2\build\ILRepack.Lib.MSBuild.Task.targets')&quot;</span> /&gt;</pre></div>Может быть вставлена после &lt;Target Name=&quot;AfterBuild&quot; &gt;. Студия не будет на это ругаться, однако при этом задача не будет выполнена. Обязательно убедитесь, что в процессе билда появляется сообщение &quot;Merge succeeded&quot;<br />
<br />
На этом, вроде бы, все. Надеюсь статья будет вам полезна. Честно говоря, учитывая количество читателей я думаю забить вести блог на этой площадке и перебираться на какой-то более цитируемый источник :)</div>

]]></content:encoded>
			<dc:creator>Артем Enot Грунин</dc:creator>
			<guid isPermaLink="true">//axforum.info/forums/blog.php?b=8256</guid>
		</item>
		<item>
			<title>Динозавр осваивает Modern Web - Часть пятая</title>
			<link>//axforum.info/forums/blog.php?b=8252</link>
			<pubDate>Fri, 27 Oct 2017 10:32:04 GMT</pubDate>
			<description><![CDATA[Часть пятая - Как работает сборка?

Предыдущая статья серии: Динозавр осваивает Modern Web - Часть четвертая (http://www.axforum.info/forums/blog.php?b=8251)

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

Для это вернемся в студию и посмотрим из чего состоит наш проект. Начнем в порядке значимости - с файла package.json. Не путать с packages.config! У них похожи не только названия, но и назначения. packages.config - это список NuGet пакетов, которые используются в решении, а package.json - это, в некотором роде, конфигурация NPM (менеджера пакетов Node):

Вложение 413 (//axforum.info/forums/attachment.php?attachmentid=413)

В корне этого файла описывается информация о нашем пакете на тот случай если мы захотим разместить его в NPM. Это, пожалуй, ключевое отличие от NuGet каждый проект - кандидат сдать утилитой в другом проекте. Далее, идут разделы dependencies, devDependencies и scripts:

Вложение 414 (//axforum.info/forums/attachment.php?attachmentid=414)

Раздел dependencies - аналог Reference в .NET. Тут содержится перечень своих, или сторонних "библиотек", которые нужны для работы нашего приложения. В данном случае, количество зависимостей минимально - это библиотеки react и react-dom, которые всегда идут в паре. Насколько понял, их разделили, чтобы независимо развивать React Native - библиотеку для работы с мобильными приложениями. Здесь же мы видим библиотеку react-scripts-ts, которая, вообще-то инструмент разработки, а не библиотека, которую мы используем в своем решении, но разработчикам должно быть виднее. Видимо какие-то ее части нужны для работы приложения, которое компилируется по этому шаблону.

Раздел devDependencies похож на предыдущий, с той лишь разницей, что он содержит компоненты, которые не войдут в приложение, но используются как инструментарий для его создания. В данном случае мы видим несколько библиотек @types. Зачем они нужны мы разберем чуть позже. Если кратко, это некий аналог h. -(header) файлов в C/C++, но для TypeScript. Они добавляют недостающие описания типов для библиотек которые написаны на чистом JS, чтобы лучше работала подсветка синтаксиса, а компилятор TypeScript мог лучше отлеживать типы данных.

Раздел "scripts" отвечает за ту магию, которая выполняется командами start и build, а также теми, которые мы еще не использовали. Команда start является стандартной, поэтому может использоваться без инструкции run, а остальные должны использоваться с ней. Например:

npm run testОбратите внимание, что все команды выполняются при помощи инструмента react-scripts-ts. Именно он делает за нас всю работу по горячей загрузке обновленных файлов во время отладки и сборку готового решения по команде build.

Идем далее. Папка "node modules". Как нетрудно догадаться, сюда будут складываться все пакеты из разделов dependencies и devDependencies. Нужно отметить, что студия имеет встроенную поддержку NPM и сама умеет "читать" изменения в package.json. Если мы добавим какой-то модуль, он автоматически будет скачан и размещен в каталоге node modules. Важный момент! Версия Node, которая входит в состав студии может быть устаревшей. Это может приводить к проблемам при загрузке пакетов, или при запуске команд из окна студии. 

Чтобы этого избежать, щелкните правой кнопкой на файле package.json и выберите Configure External Tools:

Вложение 415 (//axforum.info/forums/attachment.php?attachmentid=415)

В открывшемся окне, нужно переместить PATH на верхнюю строчку:

Вложение 416 (//axforum.info/forums/attachment.php?attachmentid=416)

Теперь студия будет использовать ту же версию Node, которую мы используем в консоли.

Следующие два файла, которые отвечают за конфигурацию решения - это tsconfig.json и tslint.json. Первый из них - файл конфига компилятора TypeScript. Он отсутствует в версии create-react-app c использованием Java Script.   Второй - настройки другого специфичного инструмента - линтера. Изначально линт - это программа проверки исходных кодов для языка C на предмет типовых ошибок и корявых конструкций, типа if (a=1). Позже, линтерами стали называть весь класс подобных программ. В большинстве случаев, вы не будете изменять эти конфиги. Оба этих инструмента поддерживаются студией, так что все ошибки компиляции, или варнинги линтера вы увидите в консоли ошибок:

Вложение 417 (//axforum.info/forums/attachment.php?attachmentid=417)

Все прочие элементы решения отвечают за его наполнение, а не настройку. Подробно назначение каждого из них описано в документации по create-react-app: https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#advanced-configuration, так что я расскажу лишь о них лишь кратко. По концепции решения, все исполняемые файлы должны быть расположены в каталоге src. Именно там сборщик будет искать наши TypeScript файлы для компиляции. В папке public должны быть расположены статичные "асеты" проекта, такие как картинки, библиотеки, которые недоступны через NPM и пр. файлы, которые не нужно "компилировать". Так же, тут располагается шаблон стартовой страницы index.html над которым тоже поработает сборщик. В частности, в страницу автоматически будут добавлены ссылки на скомпилированные скрипты.

Давайте теперь посмотрим, как выглядит сам сборщик. Если вы пробовали что-то делать по мануалам из интернета, вы наверняка провели несколько увлекательных минут с настройкой вебпака, который мы пока что совершенно не касались. Что ж, самое время оценить тот колоссальный труд, который проделала команда разработки create-react-app, чтобы избавить нас от необходимости настраивать сборку от проекта к проекту.

Для этого, откроем консоль в корне решения и выполним

npm run ejectЭта операция необратима, о чем нас честно предупредят. Возможно вы захотите сохранить резервную копию проекта до ее выполнения.

В итоге мы увидим, что в наш проект добавилось много всего нового:

Вложение 418 (//axforum.info/forums/attachment.php?attachmentid=418)

Изменился и package.json. В него добавилось много новых зависимостей, которые заботливо прятал от нас пакет react-scripts-ts. Изменилась и секция со скриптами. Мы по-прежнему можем выполнить build и start, но теперь вместо react-scripts-ts будут выполняться локальные скрипты в каталоге проекта.

Давайте теперь обновим каталог решения в Solution Explorer и перейдем в новый каталог "config" где находится сердце нашего сборщика - файл конфигурации webpack:

Вложение 419 (//axforum.info/forums/attachment.php?attachmentid=419)

В одной из прошлых статей этой серии, мы выяснили, что сборка модерн веб приложений, как правило делается с использованием этого инструмента. Сразу скажу, что конфигурация не обязана быть такой монструозной. Чаще всего достаточно конфига на 5-10 строчек, но тут мы видим высший пилотаж! Почти 300 строк хитрой конфигурации, оптимизации и компоновки решения, на выходе из которой мы и получим наши уродливые имена файлов. Впрочем, теперь мы можем все изменить!

Давайте откроем webpack.config.prod.js и найдем в нем следующий фрагмент:

Вложение 420 (//axforum.info/forums/attachment.php?attachmentid=420)

Загрузчики (loaders) - это элемент процесса сборки, который отвечает за выбор инструмента "компиляции" для каждого конкретного файла. Давайте уберем вхождение блока [hash:8] во всех загрузчиках, чтобы убрать хеши из имен файлов. Дополнительно придется поискать по ключевому слову hash, чтобы найти другие места конфига, где они могут использоваться, например:

const cssFilename = 'static/css/[name].[contenthash:8].css';
filename: 'static/js/[name].[chunkhash:8].js',
chunkFilename: 'static/js/[name].[chunkhash:8].chunk.js',

Так же следует закомментировать подключение плагина SWPrecacheWebpackPlugin, так как он, в любом случае не будет работать в CRM.

Если все было сделано правильно, мы получим приемлемый "статичный" билд:

Вложение 421 (//axforum.info/forums/attachment.php?attachmentid=421)

Не забудьте убрать хэши из имен файлов в spkl.json, так как теперь мы в них не нуждаемся.

И вот мы, наконец, разобрались как создать, скомпилировать и опубликовать решение в CRM. Осталось понять кто такие React и TypeScript и чем они могут быть нам полезны. Об этом я расскажу в следующих статьях серии.]]></description>
			<content:encoded><![CDATA[<div>Часть пятая - Как работает сборка?<br />
<br />
Предыдущая статья серии:<a href="http://www.axforum.info/forums/blog.php?b=8251" target="_blank"> Динозавр осваивает Modern Web - Часть четвертая</a><br />
<br />
В прошлой статье мы с вами успешно собрали и разместили в CRM наше первое приложение на React. Теперь осталось понять почему все так сложно и где там был сам реакт. В общем-то это нормальная реакция человека, который только начал во все это погружаться. Непривычные инструменты, новый синтаксис и идеология собьют с толку кого угодно. Но, не все так плохо. Давайте разбираться как все это работает!<br />
<br />
Для это вернемся в студию и посмотрим из чего состоит наш проект. Начнем в порядке значимости - с файла package.json. Не путать с packages.config! У них похожи не только названия, но и назначения. packages.config - это список NuGet пакетов, которые используются в решении, а package.json - это, в некотором роде, конфигурация NPM (менеджера пакетов Node):<br />
<br />
<img src="//axforum.info/forums/blog_attachment.php?attachmentid=413&amp;d=1509100108" border="0" alt="Название: 01 explorer.png
Просмотров: 176571

Размер: 23.6 Кб" style="margin: 2px" /><br />
<br />
В корне этого файла описывается информация о нашем пакете на тот случай если мы захотим разместить его в NPM. Это, пожалуй, ключевое отличие от NuGet каждый проект - кандидат сдать утилитой в другом проекте. Далее, идут разделы dependencies, devDependencies и scripts:<br />
<br />
<a href="//axforum.info/forums/blog_attachment.php?attachmentid=414&amp;d=1509100108" rel="Lightbox" id="attachment414" ><img src="//axforum.info/forums/blog_attachment.php?attachmentid=414&amp;thumb=1&amp;d=1509100108" class="thumbnail" border="0" alt="Нажмите на изображение для увеличения
Название: 02 package.png
Просмотров: 42846
Размер:	33.7 Кб
ID:	414" style="margin: 2px" /></a><br />
<br />
Раздел dependencies - аналог Reference в .NET. Тут содержится перечень своих, или сторонних &quot;библиотек&quot;, которые нужны для работы нашего приложения. В данном случае, количество зависимостей минимально - это библиотеки react и react-dom, которые всегда идут в паре. Насколько понял, их разделили, чтобы независимо развивать React Native - библиотеку для работы с мобильными приложениями. Здесь же мы видим библиотеку react-scripts-ts, которая, вообще-то инструмент разработки, а не библиотека, которую мы используем в своем решении, но разработчикам должно быть виднее. Видимо какие-то ее части нужны для работы приложения, которое компилируется по этому шаблону.<br />
<br />
Раздел devDependencies похож на предыдущий, с той лишь разницей, что он содержит компоненты, которые не войдут в приложение, но используются как инструментарий для его создания. В данном случае мы видим несколько библиотек @types. Зачем они нужны мы разберем чуть позже. Если кратко, это некий аналог h. -(header) файлов в C/C++, но для TypeScript. Они добавляют недостающие описания типов для библиотек которые написаны на чистом JS, чтобы лучше работала подсветка синтаксиса, а компилятор TypeScript мог лучше отлеживать типы данных.<br />
<br />
Раздел &quot;scripts&quot; отвечает за ту магию, которая выполняется командами start и build, а также теми, которые мы еще не использовали. Команда start является стандартной, поэтому может использоваться без инструкции run, а остальные должны использоваться с ней. Например:<br />
<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">npm run test</pre></div>Обратите внимание, что все команды выполняются при помощи инструмента react-scripts-ts. Именно он делает за нас всю работу по горячей загрузке обновленных файлов во время отладки и сборку готового решения по команде build.<br />
<br />
Идем далее. Папка &quot;node modules&quot;. Как нетрудно догадаться, сюда будут складываться все пакеты из разделов dependencies и devDependencies. Нужно отметить, что студия имеет встроенную поддержку NPM и сама умеет &quot;читать&quot; изменения в package.json. Если мы добавим какой-то модуль, он автоматически будет скачан и размещен в каталоге node modules. Важный момент! Версия Node, которая входит в состав студии может быть устаревшей. Это может приводить к проблемам при загрузке пакетов, или при запуске команд из окна студии. <br />
<br />
Чтобы этого избежать, щелкните правой кнопкой на файле package.json и выберите Configure External Tools:<br />
<br />
<a href="//axforum.info/forums/blog_attachment.php?attachmentid=415&amp;d=1509100117" rel="Lightbox" id="attachment415" ><img src="//axforum.info/forums/blog_attachment.php?attachmentid=415&amp;thumb=1&amp;d=1509100117" class="thumbnail" border="0" alt="Нажмите на изображение для увеличения
Название: 03 configure.png
Просмотров: 43092
Размер:	24.2 Кб
ID:	415" style="margin: 2px" /></a><br />
<br />
В открывшемся окне, нужно переместить PATH на верхнюю строчку:<br />
<br />
<a href="//axforum.info/forums/blog_attachment.php?attachmentid=416&amp;d=1509100117" rel="Lightbox" id="attachment416" ><img src="//axforum.info/forums/blog_attachment.php?attachmentid=416&amp;thumb=1&amp;d=1509100117" class="thumbnail" border="0" alt="Нажмите на изображение для увеличения
Название: 04 path.png
Просмотров: 42834
Размер:	38.9 Кб
ID:	416" style="margin: 2px" /></a><br />
<br />
Теперь студия будет использовать ту же версию Node, которую мы используем в консоли.<br />
<br />
Следующие два файла, которые отвечают за конфигурацию решения - это tsconfig.json и tslint.json. Первый из них - файл конфига компилятора TypeScript. Он отсутствует в версии create-react-app c использованием Java Script.   Второй - настройки другого специфичного инструмента - линтера. Изначально линт - это программа проверки исходных кодов для языка C на предмет типовых ошибок и корявых конструкций, типа if (a=1). Позже, линтерами стали называть весь класс подобных программ. В большинстве случаев, вы не будете изменять эти конфиги. Оба этих инструмента поддерживаются студией, так что все ошибки компиляции, или варнинги линтера вы увидите в консоли ошибок:<br />
<br />
<img src="//axforum.info/forums/blog_attachment.php?attachmentid=417&amp;d=1509100128" border="0" alt="Название: 05 lint.png
Просмотров: 176827

Размер: 18.0 Кб" style="margin: 2px" /><br />
<br />
Все прочие элементы решения отвечают за его наполнение, а не настройку. Подробно назначение каждого из них описано в документации по create-react-app: <a href="https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#advanced-configuration" target="_blank">https://github.com/facebookincubator...-configuration</a>, так что я расскажу лишь о них лишь кратко. По концепции решения, все исполняемые файлы должны быть расположены в каталоге src. Именно там сборщик будет искать наши TypeScript файлы для компиляции. В папке public должны быть расположены статичные &quot;асеты&quot; проекта, такие как картинки, библиотеки, которые недоступны через NPM и пр. файлы, которые не нужно &quot;компилировать&quot;. Так же, тут располагается шаблон стартовой страницы index.html над которым тоже поработает сборщик. В частности, в страницу автоматически будут добавлены ссылки на скомпилированные скрипты.<br />
<br />
Давайте теперь посмотрим, как выглядит сам сборщик. Если вы пробовали что-то делать по мануалам из интернета, вы наверняка провели несколько увлекательных минут с настройкой вебпака, который мы пока что совершенно не касались. Что ж, самое время оценить тот колоссальный труд, который проделала команда разработки create-react-app, чтобы избавить нас от необходимости настраивать сборку от проекта к проекту.<br />
<br />
Для этого, откроем консоль в корне решения и выполним<br />
<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">npm run eject</pre></div>Эта операция необратима, о чем нас честно предупредят. Возможно вы захотите сохранить резервную копию проекта до ее выполнения.<br />
<br />
В итоге мы увидим, что в наш проект добавилось много всего нового:<br />
<br />
<a href="//axforum.info/forums/blog_attachment.php?attachmentid=418&amp;d=1509100128" rel="Lightbox" id="attachment418" ><img src="//axforum.info/forums/blog_attachment.php?attachmentid=418&amp;thumb=1&amp;d=1509100128" class="thumbnail" border="0" alt="Нажмите на изображение для увеличения
Название: 06 eject.png
Просмотров: 42450
Размер:	53.1 Кб
ID:	418" style="margin: 2px" /></a><br />
<br />
Изменился и package.json. В него добавилось много новых зависимостей, которые заботливо прятал от нас пакет react-scripts-ts. Изменилась и секция со скриптами. Мы по-прежнему можем выполнить build и start, но теперь вместо react-scripts-ts будут выполняться локальные скрипты в каталоге проекта.<br />
<br />
Давайте теперь обновим каталог решения в Solution Explorer и перейдем в новый каталог &quot;config&quot; где находится сердце нашего сборщика - файл конфигурации webpack:<br />
<br />
<img src="//axforum.info/forums/blog_attachment.php?attachmentid=419&amp;d=1509100135" border="0" alt="Название: 07 webpack.png
Просмотров: 176933

Размер: 19.0 Кб" style="margin: 2px" /><br />
<br />
В одной из прошлых статей этой серии, мы выяснили, что сборка модерн веб приложений, как правило делается с использованием этого инструмента. Сразу скажу, что конфигурация не обязана быть такой монструозной. Чаще всего достаточно конфига на 5-10 строчек, но тут мы видим высший пилотаж! Почти 300 строк хитрой конфигурации, оптимизации и компоновки решения, на выходе из которой мы и получим наши уродливые имена файлов. Впрочем, теперь мы можем все изменить!<br />
<br />
Давайте откроем webpack.config.prod.js и найдем в нем следующий фрагмент:<br />
<br />
<a href="//axforum.info/forums/blog_attachment.php?attachmentid=420&amp;d=1509100135" rel="Lightbox" id="attachment420" ><img src="//axforum.info/forums/blog_attachment.php?attachmentid=420&amp;thumb=1&amp;d=1509100135" class="thumbnail" border="0" alt="Нажмите на изображение для увеличения
Название: 08 loaders.png
Просмотров: 42826
Размер:	30.5 Кб
ID:	420" style="margin: 2px" /></a><br />
<br />
Загрузчики (loaders) - это элемент процесса сборки, который отвечает за выбор инструмента &quot;компиляции&quot; для каждого конкретного файла. Давайте уберем вхождение блока [hash:8] во всех загрузчиках, чтобы убрать хеши из имен файлов. Дополнительно придется поискать по ключевому слову hash, чтобы найти другие места конфига, где они могут использоваться, например:<br />
<br />
const cssFilename = 'static/css/[name].[contenthash:8].css';<br />
filename: 'static/js/[name].[chunkhash:8].js',<br />
chunkFilename: 'static/js/[name].[chunkhash:8].chunk.js',<br />
<br />
Так же следует закомментировать подключение плагина SWPrecacheWebpackPlugin, так как он, в любом случае не будет работать в CRM.<br />
<br />
Если все было сделано правильно, мы получим приемлемый &quot;статичный&quot; билд:<br />
<br />
<img src="//axforum.info/forums/blog_attachment.php?attachmentid=421&amp;d=1509100140" border="0" alt="Название: 09 static.png
Просмотров: 176950

Размер: 14.1 Кб" style="margin: 2px" /><br />
<br />
Не забудьте убрать хэши из имен файлов в spkl.json, так как теперь мы в них не нуждаемся.<br />
<br />
И вот мы, наконец, разобрались как создать, скомпилировать и опубликовать решение в CRM. Осталось понять кто такие React и TypeScript и чем они могут быть нам полезны. Об этом я расскажу в следующих статьях серии.</div>

]]></content:encoded>
			<dc:creator>Артем Enot Грунин</dc:creator>
			<guid isPermaLink="true">//axforum.info/forums/blog.php?b=8252</guid>
		</item>
		<item>
			<title>Динозавр осваивает Modern Web - Часть четвертая</title>
			<link>//axforum.info/forums/blog.php?b=8251</link>
			<pubDate>Wed, 18 Oct 2017 12:27:00 GMT</pubDate>
			<description><![CDATA[Часть четвертая - Практический пример

Предыдущая статья серии: *Динозавр осваивает Modern Web - Часть третья (http://www.axforum.info/forums/blog.php?b=8250)*


В прошлой статье мы определились с тем, что будем осваивать библиотеку React и писать на языке TypeScript. Вынужден признать, что я довольно нехило размазал теоретическую часть, но, думаю, это было оправданно. У меня еще свежи воспоминания, о том, как я начинал погружался в предметную область. Нет такого количества пива, которое могло бы облегчить мои муки в тот момент. Мне тогда страшно хотелось убивать авторов всех статей, которые я читаю, так как ни одна не давала ответ на вопрос: "Где здесь находится ебаный Build, который выложит готовый сайт в папку Deploy". Так как эта серия статей рассчитана, в первую очередь, на таких же динозавров, как я, я уверен, что они мои старания оценят :)

Итак, приступим. В первую очередь скачаем и установим Node.js: https://nodejs.org/en/. Какая-то его версия входит в состав Visual Studio, но нам она подходит. Не нужно гнаться за новизной, нам подходит последняя стабильная версия. При установке, обязательно разрешите Node.js прописаться в переменной %PATH%.

Теперь запустите командную строку (не обязательно в папке, куда установили Node, достаточно просто cmd) и выполните команду

node -vили

npm -vВы должны увидеть номер установленной версии:

Вложение 396 (//axforum.info/forums/attachment.php?attachmentid=396)

Это никак не пригодится нам в разработке, мы просто проверили что все работает.

Обратите внимание на каталог пользователя по умолчанию. Сюда менеджер пакетов будет сваливать все глобально устанавливаемые инструменты. Возможно, это поведение как-то можно изменить, но я никогда этим не заморачивался. Нужно отметить, что NuGet и .Net Core ведут себя в этом плане одинаково плохо.

Теперь откройте проводник перейдите в папку где живут ваши проекты. Пусть это будет C:\TempProjects. Теперь введите в строке адреса cmd и нажмите enter:

Вложение 397 (//axforum.info/forums/attachment.php?attachmentid=397)

Вуаля! Консоль откроется в нужном каталоге и не нужно париться с >cd/ 

Теперь настал черед грязной магии. Скачаем через NPM утилиту, которая сгенерирует шаблон нашего будущего проекта:

npm install -g create-react-appБудьте готовы, что это займет некоторое время:

Вложение 398 (//axforum.info/forums/attachment.php?attachmentid=398)

Флаг -g говорит о том, что инструмент нужно установить глобально. На практике это обозначает, что модуль будет скачан в каталог пользователя по умолчанию и будет доступен для запуска из любой папки. У глобальной установки есть противники, позже вы сами займете сторону.

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

create-react-app crmreactapp --scripts-version=react-scripts-tsПервый параметр говорит, как назвать наш проект, второй - что мы будем использовать TypeScript в паре с React.

Опять же, эта процедура займет куда больше времени, чем потребовалось бы для создания проекта в Visual Studio. К слову, поэтому и существуют горячие поклонники альтернативных менеджеров пакетов, так как они могут выполнять эту задачу быстрее, и/или эффективнее.

В результате, в каталоге TempProjects будет создана папка crmreactapp с некоторым содержимым:  

Вложение 399 (//axforum.info/forums/attachment.php?attachmentid=399)

Теперь перейдем в каталог crmreactapp:

cd crmreactappИ выполним следующую магическую команду:

npm startОткроется окно браузера, и вы увидите нечто подобное:

Вложение 400 (//axforum.info/forums/attachment.php?attachmentid=400)

Самое время подключить студию!

Запустите VS и выберите открыть "Открыть веб сайт":

Вложение 401 (//axforum.info/forums/attachment.php?attachmentid=401)

И выберите каталог C:\TempProjects\crmreactapp. В итоге, в Solution Explorer вы должны увидеть содержимое сайта, который создали:

Вложение 402 (//axforum.info/forums/attachment.php?attachmentid=402)

Теперь, давайте попробуем сделать то, что советует нам наш сайт! Откройте файл App.ts в каталоге src и измените в нем что-нибудь (странный синтаксис мы обсудим позже)! Например, измените фразу Welcome to React на что-то свое и сохраните изменения:

Вложение 403 (//axforum.info/forums/attachment.php?attachmentid=403)

Вы увидите, что уже через секунду браузер сам обновит страницу и вы увидите изменения:

Вложение 404 (//axforum.info/forums/attachment.php?attachmentid=404)

Пока что я не показал вам ничего принципиально нового. Все это вы могли видеть в официальных мануалах Microsoft и Facebook которые я рекомендую вам потыкать, если вы все еще это не сделали, чтобы как-то освоиться:
https://github.com/Microsoft/TypeScript-React-Starter
https://reactjs.org/docs/installation.html

Теперь давайте остановим выполнение предыдущей команды через Clrl+C и запустим сборку решения:

npm run buildНе забудьте указать ключевое слово run! В итоге вы должны увидеть что-то похожее:

Вложение 405 (//axforum.info/forums/attachment.php?attachmentid=405)

А в каталоге сайта должна появиться папка build.

Ее содержимое чем-то похоже на структуру нашего проекта, но она содержит какие-то очень странные файлы и папки, типа main.9a0fe4f1.css и main.aad7ae77.js (имена ваших файлов могут отличаться). Это, собственно, результат сборки утилитой react-scripts-ts, которая установилась вместе с create-react-app. Позже мы разберемся как работает все это хозяйство и почему у файлов такие кривые имена.

Гораздо интереснее другое: попробуйте открыть в браузере файл C:\TempProjects\crmreactapp\build\index.html и увидите пустой экран. Если открыть консоль браузера мы увидим, что документ содержит ошибки:

Вложение 412 (//axforum.info/forums/attachment.php?attachmentid=412)

Как же так, ведь нам обещали нормальный билд? Судя по всему, такие относительные пути будут корректно работать при запуске сайта под Node, хотя я не до конца понял логику разработчиков. Чтобы исправить ошибку, откройте файл package.json в корне сайта и добавьте строчку "homepage": ".":

Вложение 406 (//axforum.info/forums/attachment.php?attachmentid=406)

Обратите внимание, что студия даже будет подсказывать нам параметры по мере набора! Однако не ведитесь на ее провокации. Точка - вполне себе корректная домашняя страница.

Повторно выполните сборку приложения (не в студии, через командную строку!). Не нужно удалять, или чистить каталог build  - за нас его очистят автоматически.

Обновите окно браузера, и вы увидите, что теперь статичная страница корректно работает:

Вложение 407 (//axforum.info/forums/attachment.php?attachmentid=407)

Самое время сохранить результаты наших трудов, чтобы они не пропали! В главном меню студии нажмите File - Save all, или просто нажмите Ctrl+Shift+S. Студия предложит вам сохранить файл решения (Solution). Очень важно, чтобы он находился в родительской папке решения! По умолчанию, студия может попытаться сохранить его в "Мои документы", или другом каталоге. В нашем случае, файл решения нужно поместить в папку TempProjects где размещен наш сайт. Если вы предпочитаете создавать директорию для решения - сделайте это сейчас.

Давайте теперь снова откроем отладчик и на этот раз перейдем в раздел со скриптами:

Вложение 408 (//axforum.info/forums/attachment.php?attachmentid=408)

Боже, какой кошмар! Загрузится такое, конечно, быстро, но как это отлаживать? Не бойтесь, к нам на помощь спешат Code Maps! Эта технология позволяет браузеру сопоставить минимизированный/дотфуцированный код и оригинал. К счастью, ничего делать не нужно наш оригинальный TypeScript код находится в соседнем разделе:

Вложение 409 (//axforum.info/forums/attachment.php?attachmentid=409)

Более того, его даже можно отлаживать!

Все что нам осталось - это правильно разместить полученные веб-ресурсы в CRM c сохранением всех виртуальных путей. Для этого, можно воспользоваться XrmToolbox и плагином Web Resource Manager, или попробовать автоматизировать эту операцию. Лично я предпочитаю второй подход.

Давайте так и сделаем. Зайдите в Tools - NuGet Package Manager и вызовите Package Manager Consloe

Вложение 410 (//axforum.info/forums/attachment.php?attachmentid=410)

В открывшейся внизу консоли выполните

Install-Package spklЭто относительно новый замечательный инструмент от Scott Durow https://github.com/scottdurow/SparkleXrm/wiki/spkl

По сути, это альтернатива брошенному CRM Developer Toolkit выполненная на современный Modern Web лад. Иными словами, мы все будем делать привычным путем: через консоль и конфиги :)

После установки в каталоге с решением (вот для чего нужно, чтобы оно лежало рядом) будет создана папка packages, где помимо всего прочего, будет развернута утилита spkl. В самом решении будет создан ее файл конфигурации spkl.json (на остальные файлы пока внимание не обращаем). 

В настоящий момент нас интересует раздел "webresources", который описывает параметры публикации ресурсов в CRM. Нам необходимо изменить его следующим образом:
    • Параметр "root" должен указывать на папку "build"
    • Параметр "solution" должен содержать уникальное имя решения, в которое производится публикация (иначе будет выбрано решение по умолчанию)
    • Параметр "files" должен  содержать путь ко всем необходимым файлам в папке build. Помимо ресурсов нашего приложения, build содержит различные манифесты, которые не нужны CRM. Их не нужно перечислять в этом списке

В результате, ваш spkl.json должен выглядеть похожим образом:

"webresources": [
    {
      /* 
      Option - profile - Provide a comma delimitered list of profile names that can be referenced when calling spkl
      */
      "profile": "default,debug",

      /*
      Optional - root - Provide the relatative path of the webresources.
      */
      "root": "build/",

      /*
      Optional - solution - Add webresources to a solution when deploying
      */
      "solution": "reactapp",

      /*
      Required - files - List the webresources to deploy relatative to the root of this file (or the the root parameter above)
      */
      "files": [
        {
          "uniquename": "new_/index.html",
          "file": "index.html",
          "description": ""
        },
        {
          "uniquename": "new_/static/css/main.c17080f1.css",
          "file": "static\\css\\main.c17080f1.css",
          "description": ""
        },
        {
          "uniquename": "new_/static/js/main.b047ec63.js",
          "file": "static\\js\\main.b047ec63.js",
          "description": ""
        },
        {
          "uniquename": "new_/static/media/logo.5d5d9eef.svg",
          "file": "static\\media\\logo.5d5d9eef.svg",
          "description": ""
        }
      ]
    }
  ]Обратите внимание на 2 вещи! Префикс издателя и имена файлов. В примере используется префикс по умолчанию: "new", не забудьте его изменить, если будете копировать конфиг. Вторая проблема: при сборке решения в оригинальное имя файла добавляется его хэш. В мире полноценной веб-разработки это очень даже здорово, так как не нужно париться насчет кэша браузера - имена файлов изменили, браузер пометит кэш как устаревший и обновит файлы. Нам такой подход, разумеется не подходит. Во-первых, так нам потребуется менять конфиг каждый раз после сборки (если изменился код, изменится и хэш), а во-вторых придется каждый раз удалять старые версии из CRM, что может быть сложно.

Тем не менее, для первого раза попробуем опубликовать как есть. Для этого, откроем каталог сайта, зайдем в каталог spkl и выполним в нем команду:

deploy-webresourcesВ данном случае, мы уже не используем NPM, это обычный батник, который разыщет утилиту spkl.exe и вызовет команду spkl webresources

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

Чтобы проверить, что все работает, не обязательно вставлять страницу в какие-то фреймы и помещать на форму. Достаточно открыть index.html в редакторе веб ресурсов, или по прямой ссылке вида http(s)://crm-srv/OrgName/WebResources/prefix_/index.html?preview=1 - как вам будет удобно. Забавный момент: поддержка формата svg только недавно появилась в онлайн версии, так что если вы экспериментируете с онпремис, вы должны увидеть что-то подобное:

Вложение 411 (//axforum.info/forums/attachment.php?attachmentid=411)

Итак, мы собрали и опубликовали в CRM наше первое реакт приложение. Вопросов должно было только прибавится: как это вообще работает, что делать с хэшами, что это вообще за синтаксис такой и так далее. Об этом я расскажу в следующих статьях серии:

*Динозавр осваивает Modern Web - Часть пятая (http://www.axforum.info/forums/blog.php?b=8252)*]]></description>
			<content:encoded><![CDATA[<div>Часть четвертая - Практический пример<br />
<br />
Предыдущая статья серии: <b><a href="http://www.axforum.info/forums/blog.php?b=8250" target="_blank">Динозавр осваивает Modern Web - Часть третья</a></b><br />
<br />
<br />
В прошлой статье мы определились с тем, что будем осваивать библиотеку React и писать на языке TypeScript. Вынужден признать, что я довольно нехило размазал теоретическую часть, но, думаю, это было оправданно. У меня еще свежи воспоминания, о том, как я начинал погружался в предметную область. Нет такого количества пива, которое могло бы облегчить мои муки в тот момент. Мне тогда страшно хотелось убивать авторов всех статей, которые я читаю, так как ни одна не давала ответ на вопрос: &quot;Где здесь находится ебаный Build, который выложит готовый сайт в папку Deploy&quot;. Так как эта серия статей рассчитана, в первую очередь, на таких же динозавров, как я, я уверен, что они мои старания оценят :)<br />
<br />
Итак, приступим. В первую очередь скачаем и установим Node.js: <a href="https://nodejs.org/en/" target="_blank">https://nodejs.org/en/</a>. Какая-то его версия входит в состав Visual Studio, но нам она подходит. Не нужно гнаться за новизной, нам подходит последняя стабильная версия. При установке, обязательно разрешите Node.js прописаться в переменной %PATH%.<br />
<br />
Теперь запустите командную строку (не обязательно в папке, куда установили Node, достаточно просто cmd) и выполните команду<br />
<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">node -v</pre></div>или<br />
<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">npm -v</pre></div>Вы должны увидеть номер установленной версии:<br />
<br />
<a href="//axforum.info/forums/blog_attachment.php?attachmentid=396&amp;d=1507897513" rel="Lightbox" id="attachment396" ><img src="//axforum.info/forums/blog_attachment.php?attachmentid=396&amp;thumb=1&amp;d=1507897513" class="thumbnail" border="0" alt="Нажмите на изображение для увеличения
Название: 1 NPM.png
Просмотров: 40689
Размер:	10.6 Кб
ID:	396" style="margin: 2px" /></a><br />
<br />
Это никак не пригодится нам в разработке, мы просто проверили что все работает.<br />
<br />
Обратите внимание на каталог пользователя по умолчанию. Сюда менеджер пакетов будет сваливать все глобально устанавливаемые инструменты. Возможно, это поведение как-то можно изменить, но я никогда этим не заморачивался. Нужно отметить, что NuGet и .Net Core ведут себя в этом плане одинаково плохо.<br />
<br />
Теперь откройте проводник перейдите в папку где живут ваши проекты. Пусть это будет C:\TempProjects. Теперь введите в строке адреса cmd и нажмите enter:<br />
<br />
<a href="//axforum.info/forums/blog_attachment.php?attachmentid=397&amp;d=1507897513" rel="Lightbox" id="attachment397" ><img src="//axforum.info/forums/blog_attachment.php?attachmentid=397&amp;thumb=1&amp;d=1507897513" class="thumbnail" border="0" alt="Нажмите на изображение для увеличения
Название: 2 cmd.png
Просмотров: 40633
Размер:	20.3 Кб
ID:	397" style="margin: 2px" /></a><br />
<br />
Вуаля! Консоль откроется в нужном каталоге и не нужно париться с &gt;cd/ <br />
<br />
Теперь настал черед грязной магии. Скачаем через NPM утилиту, которая сгенерирует шаблон нашего будущего проекта:<br />
<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">npm install -g create-react-app</pre></div>Будьте готовы, что это займет некоторое время:<br />
<br />
<a href="//axforum.info/forums/blog_attachment.php?attachmentid=398&amp;d=1507897519" rel="Lightbox" id="attachment398" ><img src="//axforum.info/forums/blog_attachment.php?attachmentid=398&amp;thumb=1&amp;d=1507897519" class="thumbnail" border="0" alt="Нажмите на изображение для увеличения
Название: 3 install.png
Просмотров: 41063
Размер:	23.5 Кб
ID:	398" style="margin: 2px" /></a><br />
<br />
Флаг -g говорит о том, что инструмент нужно установить глобально. На практике это обозначает, что модуль будет скачан в каталог пользователя по умолчанию и будет доступен для запуска из любой папки. У глобальной установки есть противники, позже вы сами займете сторону.<br />
<br />
Теперь не покидая нашу папку выполним создание проекта:<br />
<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">create-react-app crmreactapp --scripts-version=react-scripts-ts</pre></div>Первый параметр говорит, как назвать наш проект, второй - что мы будем использовать TypeScript в паре с React.<br />
<br />
Опять же, эта процедура займет куда больше времени, чем потребовалось бы для создания проекта в Visual Studio. К слову, поэтому и существуют горячие поклонники альтернативных менеджеров пакетов, так как они могут выполнять эту задачу быстрее, и/или эффективнее.<br />
<br />
В результате, в каталоге TempProjects будет создана папка crmreactapp с некоторым содержимым:  <br />
<br />
<a href="//axforum.info/forums/blog_attachment.php?attachmentid=399&amp;d=1507897519" rel="Lightbox" id="attachment399" ><img src="//axforum.info/forums/blog_attachment.php?attachmentid=399&amp;thumb=1&amp;d=1507897519" class="thumbnail" border="0" alt="Нажмите на изображение для увеличения
Название: 4 app.png
Просмотров: 41068
Размер:	61.5 Кб
ID:	399" style="margin: 2px" /></a><br />
<br />
Теперь перейдем в каталог crmreactapp:<br />
<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">cd crmreactapp</pre></div>И выполним следующую магическую команду:<br />
<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">npm start</pre></div>Откроется окно браузера, и вы увидите нечто подобное:<br />
<br />
<a href="//axforum.info/forums/blog_attachment.php?attachmentid=400&amp;d=1507897527" rel="Lightbox" id="attachment400" ><img src="//axforum.info/forums/blog_attachment.php?attachmentid=400&amp;thumb=1&amp;d=1507897527" class="thumbnail" border="0" alt="Нажмите на изображение для увеличения
Название: 5 start.png
Просмотров: 41062
Размер:	84.7 Кб
ID:	400" style="margin: 2px" /></a><br />
<br />
Самое время подключить студию!<br />
<br />
Запустите VS и выберите открыть &quot;Открыть веб сайт&quot;:<br />
<br />
<a href="//axforum.info/forums/blog_attachment.php?attachmentid=401&amp;d=1507897527" rel="Lightbox" id="attachment401" ><img src="//axforum.info/forums/blog_attachment.php?attachmentid=401&amp;thumb=1&amp;d=1507897527" class="thumbnail" border="0" alt="Нажмите на изображение для увеличения
Название: 6 open.png
Просмотров: 41927
Размер:	47.6 Кб
ID:	401" style="margin: 2px" /></a><br />
<br />
И выберите каталог C:\TempProjects\crmreactapp. В итоге, в Solution Explorer вы должны увидеть содержимое сайта, который создали:<br />
<br />
<img src="//axforum.info/forums/blog_attachment.php?attachmentid=402&amp;d=1507897534" border="0" alt="Название: 7 solution.png
Просмотров: 167897

Размер: 24.0 Кб" style="margin: 2px" /><br />
<br />
Теперь, давайте попробуем сделать то, что советует нам наш сайт! Откройте файл App.ts в каталоге src и измените в нем что-нибудь (странный синтаксис мы обсудим позже)! Например, измените фразу Welcome to React на что-то свое и сохраните изменения:<br />
<br />
<a href="//axforum.info/forums/blog_attachment.php?attachmentid=403&amp;d=1507897534" rel="Lightbox" id="attachment403" ><img src="//axforum.info/forums/blog_attachment.php?attachmentid=403&amp;thumb=1&amp;d=1507897534" class="thumbnail" border="0" alt="Нажмите на изображение для увеличения
Название: 8 react.png
Просмотров: 40722
Размер:	33.7 Кб
ID:	403" style="margin: 2px" /></a><br />
<br />
Вы увидите, что уже через секунду браузер сам обновит страницу и вы увидите изменения:<br />
<br />
<a href="//axforum.info/forums/blog_attachment.php?attachmentid=404&amp;d=1507897542" rel="Lightbox" id="attachment404" ><img src="//axforum.info/forums/blog_attachment.php?attachmentid=404&amp;thumb=1&amp;d=1507897542" class="thumbnail" border="0" alt="Нажмите на изображение для увеличения
Название: 9 localhost.png
Просмотров: 41043
Размер:	84.7 Кб
ID:	404" style="margin: 2px" /></a><br />
<br />
Пока что я не показал вам ничего принципиально нового. Все это вы могли видеть в официальных мануалах Microsoft и Facebook которые я рекомендую вам потыкать, если вы все еще это не сделали, чтобы как-то освоиться:<br />
<a href="https://github.com/Microsoft/TypeScript-React-Starter" target="_blank">https://github.com/Microsoft/TypeScript-React-Starter</a><br />
<a href="https://reactjs.org/docs/installation.html" target="_blank">https://reactjs.org/docs/installation.html</a><br />
<br />
Теперь давайте остановим выполнение предыдущей команды через Clrl+C и запустим сборку решения:<br />
<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">npm run build</pre></div>Не забудьте указать ключевое слово run! В итоге вы должны увидеть что-то похожее:<br />
<br />
<a href="//axforum.info/forums/blog_attachment.php?attachmentid=405&amp;d=1507897542" rel="Lightbox" id="attachment405" ><img src="//axforum.info/forums/blog_attachment.php?attachmentid=405&amp;thumb=1&amp;d=1507897542" class="thumbnail" border="0" alt="Нажмите на изображение для увеличения
Название: 10 build.png
Просмотров: 40641
Размер:	34.5 Кб
ID:	405" style="margin: 2px" /></a><br />
<br />
А в каталоге сайта должна появиться папка build.<br />
<br />
Ее содержимое чем-то похоже на структуру нашего проекта, но она содержит какие-то очень странные файлы и папки, типа main.9a0fe4f1.css и main.aad7ae77.js (имена ваших файлов могут отличаться). Это, собственно, результат сборки утилитой react-scripts-ts, которая установилась вместе с create-react-app. Позже мы разберемся как работает все это хозяйство и почему у файлов такие кривые имена.<br />
<br />
Гораздо интереснее другое: попробуйте открыть в браузере файл C:\TempProjects\crmreactapp\build\index.html и увидите пустой экран. Если открыть консоль браузера мы увидим, что документ содержит ошибки:<br />
<br />
<a href="//axforum.info/forums/blog_attachment.php?attachmentid=412&amp;d=1507897854" rel="Lightbox" id="attachment412" ><img src="//axforum.info/forums/blog_attachment.php?attachmentid=412&amp;thumb=1&amp;d=1507897854" class="thumbnail" border="0" alt="Нажмите на изображение для увеличения
Название: 10.1 errors.png
Просмотров: 41063
Размер:	12.2 Кб
ID:	412" style="margin: 2px" /></a><br />
<br />
Как же так, ведь нам обещали нормальный билд? Судя по всему, такие относительные пути будут корректно работать при запуске сайта под Node, хотя я не до конца понял логику разработчиков. Чтобы исправить ошибку, откройте файл package.json в корне сайта и добавьте строчку &quot;homepage&quot;: &quot;.&quot;:<br />
<br />
<img src="//axforum.info/forums/blog_attachment.php?attachmentid=406&amp;d=1507897550" border="0" alt="Название: 11 homepage.png
Просмотров: 169004

Размер: 26.0 Кб" style="margin: 2px" /><br />
<br />
Обратите внимание, что студия даже будет подсказывать нам параметры по мере набора! Однако не ведитесь на ее провокации. Точка - вполне себе корректная домашняя страница.<br />
<br />
Повторно выполните сборку приложения (не в студии, через командную строку!). Не нужно удалять, или чистить каталог build  - за нас его очистят автоматически.<br />
<br />
Обновите окно браузера, и вы увидите, что теперь статичная страница корректно работает:<br />
<br />
<a href="//axforum.info/forums/blog_attachment.php?attachmentid=407&amp;d=1507897550" rel="Lightbox" id="attachment407" ><img src="//axforum.info/forums/blog_attachment.php?attachmentid=407&amp;thumb=1&amp;d=1507897550" class="thumbnail" border="0" alt="Нажмите на изображение для увеличения
Название: 12 static.png
Просмотров: 40876
Размер:	87.2 Кб
ID:	407" style="margin: 2px" /></a><br />
<br />
Самое время сохранить результаты наших трудов, чтобы они не пропали! В главном меню студии нажмите File - Save all, или просто нажмите Ctrl+Shift+S. Студия предложит вам сохранить файл решения (Solution). Очень важно, чтобы он находился в родительской папке решения! По умолчанию, студия может попытаться сохранить его в &quot;Мои документы&quot;, или другом каталоге. В нашем случае, файл решения нужно поместить в папку TempProjects где размещен наш сайт. Если вы предпочитаете создавать директорию для решения - сделайте это сейчас.<br />
<br />
Давайте теперь снова откроем отладчик и на этот раз перейдем в раздел со скриптами:<br />
<br />
<a href="//axforum.info/forums/blog_attachment.php?attachmentid=408&amp;d=1507897560" rel="Lightbox" id="attachment408" ><img src="//axforum.info/forums/blog_attachment.php?attachmentid=408&amp;thumb=1&amp;d=1507897560" class="thumbnail" border="0" alt="Нажмите на изображение для увеличения
Название: 13 debugger.png
Просмотров: 40945
Размер:	21.4 Кб
ID:	408" style="margin: 2px" /></a><br />
<br />
Боже, какой кошмар! Загрузится такое, конечно, быстро, но как это отлаживать? Не бойтесь, к нам на помощь спешат Code Maps! Эта технология позволяет браузеру сопоставить минимизированный/дотфуцированный код и оригинал. К счастью, ничего делать не нужно наш оригинальный TypeScript код находится в соседнем разделе:<br />
<br />
<a href="//axforum.info/forums/blog_attachment.php?attachmentid=409&amp;d=1507897560" rel="Lightbox" id="attachment409" ><img src="//axforum.info/forums/blog_attachment.php?attachmentid=409&amp;thumb=1&amp;d=1507897560" class="thumbnail" border="0" alt="Нажмите на изображение для увеличения
Название: 14 codemap.png
Просмотров: 41608
Размер:	40.1 Кб
ID:	409" style="margin: 2px" /></a><br />
<br />
Более того, его даже можно отлаживать!<br />
<br />
Все что нам осталось - это правильно разместить полученные веб-ресурсы в CRM c сохранением всех виртуальных путей. Для этого, можно воспользоваться XrmToolbox и плагином Web Resource Manager, или попробовать автоматизировать эту операцию. Лично я предпочитаю второй подход.<br />
<br />
Давайте так и сделаем. Зайдите в Tools - NuGet Package Manager и вызовите Package Manager Consloe<br />
<br />
<a href="//axforum.info/forums/blog_attachment.php?attachmentid=410&amp;d=1507897570" rel="Lightbox" id="attachment410" ><img src="//axforum.info/forums/blog_attachment.php?attachmentid=410&amp;thumb=1&amp;d=1507897570" class="thumbnail" border="0" alt="Нажмите на изображение для увеличения
Название: 15 nuget.png
Просмотров: 41451
Размер:	31.2 Кб
ID:	410" style="margin: 2px" /></a><br />
<br />
В открывшейся внизу консоли выполните<br />
<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">Install-Package spkl</pre></div>Это относительно новый замечательный инструмент от Scott Durow <a href="https://github.com/scottdurow/SparkleXrm/wiki/spkl" target="_blank">https://github.com/scottdurow/SparkleXrm/wiki/spkl</a><br />
<br />
По сути, это альтернатива брошенному CRM Developer Toolkit выполненная на современный Modern Web лад. Иными словами, мы все будем делать привычным путем: через консоль и конфиги :)<br />
<br />
После установки в каталоге с решением (вот для чего нужно, чтобы оно лежало рядом) будет создана папка packages, где помимо всего прочего, будет развернута утилита spkl. В самом решении будет создан ее файл конфигурации spkl.json (на остальные файлы пока внимание не обращаем). <br />
<br />
В настоящий момент нас интересует раздел &quot;webresources&quot;, который описывает параметры публикации ресурсов в CRM. Нам необходимо изменить его следующим образом:<br />
    • Параметр &quot;root&quot; должен указывать на папку &quot;build&quot;<br />
    • Параметр &quot;solution&quot; должен содержать уникальное имя решения, в которое производится публикация (иначе будет выбрано решение по умолчанию)<br />
    • Параметр &quot;files&quot; должен  содержать путь ко всем необходимым файлам в папке build. Помимо ресурсов нашего приложения, build содержит различные манифесты, которые не нужны CRM. Их не нужно перечислять в этом списке<br />
<br />
В результате, ваш spkl.json должен выглядеть похожим образом:<br />
<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code"><span style="color: red">&quot;webresources&quot;</span>: [
    {
      <span style="color: green">/* 
      Option - profile - Provide a comma delimitered list of profile names that can be referenced when calling spkl
      */</span>
      <span style="color: red">&quot;profile&quot;</span>: <span style="color: red">&quot;default,debug&quot;</span>,

      <span style="color: green">/*
      Optional - root - Provide the relatative path of the webresources.
      */</span>
      <span style="color: red">&quot;root&quot;</span>: <span style="color: red">&quot;build/&quot;</span>,

      <span style="color: green">/*
      Optional - solution - Add webresources to a solution when deploying
      */</span>
      <span style="color: red">&quot;solution&quot;</span>: <span style="color: red">&quot;reactapp&quot;</span>,

      <span style="color: green">/*
      Required - files - List the webresources to deploy relatative to the root of this file (or the the root parameter above)
      */</span>
      <span style="color: red">&quot;files&quot;</span>: [
        {
          <span style="color: red">&quot;uniquename&quot;</span>: <span style="color: red">&quot;new_/index.html&quot;</span>,
          <span style="color: red">&quot;file&quot;</span>: <span style="color: red">&quot;index.html&quot;</span>,
          <span style="color: red">&quot;description&quot;</span>: <span style="color: red">&quot;&quot;</span>
        },
        {
          <span style="color: red">&quot;uniquename&quot;</span>: <span style="color: red">&quot;new_/static/css/main.c17080f1.css&quot;</span>,
          <span style="color: red">&quot;file&quot;</span>: <span style="color: red">&quot;static\\css\\main.c17080f1.css&quot;</span>,
          <span style="color: red">&quot;description&quot;</span>: <span style="color: red">&quot;&quot;</span>
        },
        {
          <span style="color: red">&quot;uniquename&quot;</span>: <span style="color: red">&quot;new_/static/js/main.b047ec63.js&quot;</span>,
          <span style="color: red">&quot;file&quot;</span>: <span style="color: red">&quot;static\\js\\main.b047ec63.js&quot;</span>,
          <span style="color: red">&quot;description&quot;</span>: <span style="color: red">&quot;&quot;</span>
        },
        {
          <span style="color: red">&quot;uniquename&quot;</span>: <span style="color: red">&quot;new_/static/media/logo.5d5d9eef.svg&quot;</span>,
          <span style="color: red">&quot;file&quot;</span>: <span style="color: red">&quot;static\\media\\logo.5d5d9eef.svg&quot;</span>,
          <span style="color: red">&quot;description&quot;</span>: <span style="color: red">&quot;&quot;</span>
        }
      ]
    }
  ]</pre></div>Обратите внимание на 2 вещи! Префикс издателя и имена файлов. В примере используется префикс по умолчанию: &quot;new&quot;, не забудьте его изменить, если будете копировать конфиг. Вторая проблема: при сборке решения в оригинальное имя файла добавляется его хэш. В мире полноценной веб-разработки это очень даже здорово, так как не нужно париться насчет кэша браузера - имена файлов изменили, браузер пометит кэш как устаревший и обновит файлы. Нам такой подход, разумеется не подходит. Во-первых, так нам потребуется менять конфиг каждый раз после сборки (если изменился код, изменится и хэш), а во-вторых придется каждый раз удалять старые версии из CRM, что может быть сложно.<br />
<br />
Тем не менее, для первого раза попробуем опубликовать как есть. Для этого, откроем каталог сайта, зайдем в каталог spkl и выполним в нем команду:<br />
<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">deploy-webresources</pre></div>В данном случае, мы уже не используем NPM, это обычный батник, который разыщет утилиту spkl.exe и вызовет команду spkl webresources<br />
<br />
Вам будут заданы стандартные вопросы по поиску вашего сервера и организации, после чего, если конфиг не содержит ошибок, решение будет опубликовано в CRM.<br />
<br />
Чтобы проверить, что все работает, не обязательно вставлять страницу в какие-то фреймы и помещать на форму. Достаточно открыть index.html в редакторе веб ресурсов, или по прямой ссылке вида http(s)://crm-srv/OrgName/WebResources/prefix_/index.html?preview=1 - как вам будет удобно. Забавный момент: поддержка формата svg только недавно появилась в онлайн версии, так что если вы экспериментируете с онпремис, вы должны увидеть что-то подобное:<br />
<br />
<a href="//axforum.info/forums/blog_attachment.php?attachmentid=411&amp;d=1507897570" rel="Lightbox" id="attachment411" ><img src="//axforum.info/forums/blog_attachment.php?attachmentid=411&amp;thumb=1&amp;d=1507897570" class="thumbnail" border="0" alt="Нажмите на изображение для увеличения
Название: 16 crm.png
Просмотров: 40731
Размер:	10.1 Кб
ID:	411" style="margin: 2px" /></a><br />
<br />
Итак, мы собрали и опубликовали в CRM наше первое реакт приложение. Вопросов должно было только прибавится: как это вообще работает, что делать с хэшами, что это вообще за синтаксис такой и так далее. Об этом я расскажу в следующих статьях серии:<br />
<br />
<b><a href="http://www.axforum.info/forums/blog.php?b=8252" target="_blank">Динозавр осваивает Modern Web - Часть пятая</a></b></div>

]]></content:encoded>
			<dc:creator>Артем Enot Грунин</dc:creator>
			<guid isPermaLink="true">//axforum.info/forums/blog.php?b=8251</guid>
		</item>
		<item>
			<title>Динозавр осваивает Modern Web - Часть третья</title>
			<link>//axforum.info/forums/blog.php?b=8250</link>
			<pubDate>Mon, 16 Oct 2017 11:36:00 GMT</pubDate>
			<description><![CDATA[Часть третья - Технологии

Предыдущая статья серии: Динозавр осваивает Modern Web - Часть вторая (http://www.axforum.info/forums/blog.php?b=8249)

Итак, в прошлый раз мы выяснили, что в современной веб разработке применяются три ключевых инструмента:
    • Менеджер пакетов
    • Рендер-препроцессор
    • Сборщик

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

    • NPM (Node Package Manager) это наш менеджер пакетов. Инструмент поставляется в составе дистрибутива Node и не требует дополнительной настройки. Все остальные инструменты мы будем устанавливать через него!
    • Babel. Изначально нечто вроде библиотеки "полифилов" на js, которая реализует поддержку языковых конструкций js, которые еще не стали стандартом, или не имеют единогласной поддержки мультиполярным интернет сообществом. В настоящий момент - это расширяемый плагинами инструмент, задача которого "транслировать" расширенные версии веб-языков во что-то более поддерживаемое. Об этом позже
    • WebPack. Монструозная система компоновки, сборки и даже запуска веб приложений! Умеет запускаться в режиме веб-сервера и прямо на ходу подгружать изменения без перезапуска. Инструмент, который реально впечатляет, однако чудовищно сложен в освоении в полной мере.

Разумеется, у них есть альтернативы! Немного погуглив, вы непременно узнаете, что NPM используют только лохи, а все крутые пацаны используют Bower и так далее. Тем не менее, указанные выше инструменты (возможно за исключением Babel) являются стандартом де-факто. Какой бы фреймворк для разработки вы не выбрали, в его документации вы непременно найдете инструкции как их использовать. Эти же инструменты Microsoft использует в шаблонах проектов ASP.NET Core, хотя поддерживает и другие средства.

Итак, со средой разработки мы определились. Теперь самое интересное - нужно выбрать язык и фреймворк. Вариантов множество, из популярных можно выделить React, Angular, Knokout и Aurelia. Кто-то из них моложе, кто-то мощнее, кто-то вообще-то не фреймворк, а так - библиотека. Это дело вкуса. Я для этой статьи выбрал React и вот почему:

    • Религиозная неприязнь к продуктам Google (поисковик не считается!). Angular тяжелый, сложный и непонятный, к тому же развивается во все стороны сразу. Внутренние противоречия в ходе его разработки даже привели к тому, что один из ключевых членов команды ушел и создал Aurelia :) Кстати, теперь активно сотрудничает с Microsoft
    • Как было сказано выше, React - это и не фреймворк вовсе, а библиотека. Иными словами, работать с ним как с JQuery - "приатач и хуяч"! Никто не заставляет сильно погружаться в идеологию
    • Образцово показательная модульность. На React очень удобно писать UI компоненты, которые можно выделить в отдельную библиотеку и повторно использовать. Например, на разных формах
    • Простота, быстрота, эффективность и пр. за что его любят
    • По непроверенным слухам, Microsoft использует React в своем знаменитом Custom Control Framework, который вот-вот станет доступен сторонним разработчикам. В общем, и нам пора!

Теперь язык. Как я уже говорил, никто не пишет на JavaScript. Во всяком случае на текущей официально принятой (или поддерживаемой большинством браузеров) версии стандарта ESMAScript. Все мы знаем что JS - это жопоязык созданный, чтобы мы страдали, но это же самое можно сказать и про тот же С/С++! Чем же хуже него JS? Дело в том, что JS изначально скриптовый язык - считайте, что это язык командной строки. Его первоначальная задача - последовательно выполнять команды сценария, а не автоматизировать жизненный цикл приложений. Чтобы стало полегче, в следующих версиях стандарта вводят разные инновации, такие как модули, классы, стрелочные функции и так далее:
https://learn.javascript.ru/modules
https://learn.javascript.ru/es-class
http://jsraccoon.ru/es6-arrow-functions
http://jsraccoon.ru/es6-block-scoped-declarations
http://jsraccoon.ru/es6-interpolation

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

Вот здесь снова возникает выбор: использовать JS6, или другие транслируемые языки, такие как TypeScript, CoffeeScript, Dart и другие? Я для себя выбрал TypeScript и вот почему:

    • Его любит даже команда Angular!
    • Он был специально доработан для React!
    • TypeScript обратно совместим с JS. Это значит, что код любой существующей JS библиотеки, например, Reract является корректным TypeScript кодом
    • Язык разрабатывается Microsoft и по официальной версии заменит Script# в новых версиях CRM. Ну, вы поняли: значит и нам пора!
    
Итак, с инструментарием определились. Пора начинать кодировать! Об этом я расскажу в следующих постах этой серии:

*Динозавр осваивает Modern Web - Часть четвертая (http://www.axforum.info/forums/blog.php?b=8251)*]]></description>
			<content:encoded><![CDATA[<div>Часть третья - Технологии<br />
<br />
Предыдущая статья серии: <a href="http://www.axforum.info/forums/blog.php?b=8249" target="_blank">Динозавр осваивает Modern Web - Часть вторая</a><br />
<br />
Итак, в прошлый раз мы выяснили, что в современной веб разработке применяются три ключевых инструмента:<br />
    • Менеджер пакетов<br />
    • Рендер-препроцессор<br />
    • Сборщик<br />
<br />
Забавно, но выбор инструментов совершенно не зависит от того, какой фреймворк, или язык мы выберем для разработки. Возможно, когда-то в прошлом, это было принципиально, но теперь уже нет. В документации любого фреймворка вы, чаще всего, встретите инструкции по использованию следующих инструментов:<br />
<br />
    • NPM (Node Package Manager) это наш менеджер пакетов. Инструмент поставляется в составе дистрибутива Node и не требует дополнительной настройки. Все остальные инструменты мы будем устанавливать через него!<br />
    • Babel. Изначально нечто вроде библиотеки &quot;полифилов&quot; на js, которая реализует поддержку языковых конструкций js, которые еще не стали стандартом, или не имеют единогласной поддержки мультиполярным интернет сообществом. В настоящий момент - это расширяемый плагинами инструмент, задача которого &quot;транслировать&quot; расширенные версии веб-языков во что-то более поддерживаемое. Об этом позже<br />
    • WebPack. Монструозная система компоновки, сборки и даже запуска веб приложений! Умеет запускаться в режиме веб-сервера и прямо на ходу подгружать изменения без перезапуска. Инструмент, который реально впечатляет, однако чудовищно сложен в освоении в полной мере.<br />
<br />
Разумеется, у них есть альтернативы! Немного погуглив, вы непременно узнаете, что NPM используют только лохи, а все крутые пацаны используют Bower и так далее. Тем не менее, указанные выше инструменты (возможно за исключением Babel) являются стандартом де-факто. Какой бы фреймворк для разработки вы не выбрали, в его документации вы непременно найдете инструкции как их использовать. Эти же инструменты Microsoft использует в шаблонах проектов ASP.NET Core, хотя поддерживает и другие средства.<br />
<br />
Итак, со средой разработки мы определились. Теперь самое интересное - нужно выбрать язык и фреймворк. Вариантов множество, из популярных можно выделить React, Angular, Knokout и Aurelia. Кто-то из них моложе, кто-то мощнее, кто-то вообще-то не фреймворк, а так - библиотека. Это дело вкуса. Я для этой статьи выбрал React и вот почему:<br />
<br />
    • Религиозная неприязнь к продуктам Google (поисковик не считается!). Angular тяжелый, сложный и непонятный, к тому же развивается во все стороны сразу. Внутренние противоречия в ходе его разработки даже привели к тому, что один из ключевых членов команды ушел и создал Aurelia :) Кстати, теперь активно сотрудничает с Microsoft<br />
    • Как было сказано выше, React - это и не фреймворк вовсе, а библиотека. Иными словами, работать с ним как с JQuery - &quot;приатач и хуяч&quot;! Никто не заставляет сильно погружаться в идеологию<br />
    • Образцово показательная модульность. На React очень удобно писать UI компоненты, которые можно выделить в отдельную библиотеку и повторно использовать. Например, на разных формах<br />
    • Простота, быстрота, эффективность и пр. за что его любят<br />
    • По непроверенным слухам, Microsoft использует React в своем знаменитом Custom Control Framework, который вот-вот станет доступен сторонним разработчикам. В общем, и нам пора!<br />
<br />
Теперь язык. Как я уже говорил, никто не пишет на JavaScript. Во всяком случае на текущей официально принятой (или поддерживаемой большинством браузеров) версии стандарта ESMAScript. Все мы знаем что JS - это жопоязык созданный, чтобы мы страдали, но это же самое можно сказать и про тот же С/С++! Чем же хуже него JS? Дело в том, что JS изначально скриптовый язык - считайте, что это язык командной строки. Его первоначальная задача - последовательно выполнять команды сценария, а не автоматизировать жизненный цикл приложений. Чтобы стало полегче, в следующих версиях стандарта вводят разные инновации, такие как модули, классы, стрелочные функции и так далее:<br />
<a href="https://learn.javascript.ru/modules" target="_blank">https://learn.javascript.ru/modules</a><br />
<a href="https://learn.javascript.ru/es-class" target="_blank">https://learn.javascript.ru/es-class</a><br />
<a href="http://jsraccoon.ru/es6-arrow-functions" target="_blank">http://jsraccoon.ru/es6-arrow-functions</a><br />
<a href="http://jsraccoon.ru/es6-block-scoped-declarations" target="_blank">http://jsraccoon.ru/es6-block-scoped-declarations</a><br />
<a href="http://jsraccoon.ru/es6-interpolation" target="_blank">http://jsraccoon.ru/es6-interpolation</a><br />
<br />
Но, модерн веб не был бы модерн, если бы ждал, пока все это станет доступным повсеместно! Вместо этого, синтаксис будущего можно использовать уже сейчас, но его придется прогнать через рендер, который преобразует его к синтаксису текущей версии. Разумеется, не без потерь в виде читаемости и производительности.<br />
<br />
Вот здесь снова возникает выбор: использовать JS6, или другие транслируемые языки, такие как TypeScript, CoffeeScript, Dart и другие? Я для себя выбрал TypeScript и вот почему:<br />
<br />
    • Его любит даже команда Angular!<br />
    • Он был специально доработан для React!<br />
    • TypeScript обратно совместим с JS. Это значит, что код любой существующей JS библиотеки, например, Reract является корректным TypeScript кодом<br />
    • Язык разрабатывается Microsoft и по официальной версии заменит Script# в новых версиях CRM. Ну, вы поняли: значит и нам пора!<br />
    <br />
Итак, с инструментарием определились. Пора начинать кодировать! Об этом я расскажу в следующих постах этой серии:<br />
<br />
<b><a href="http://www.axforum.info/forums/blog.php?b=8251" target="_blank">Динозавр осваивает Modern Web - Часть четвертая</a></b></div>

]]></content:encoded>
			<dc:creator>Артем Enot Грунин</dc:creator>
			<guid isPermaLink="true">//axforum.info/forums/blog.php?b=8250</guid>
		</item>
		<item>
			<title>Динозавр осваивает Modern Web - Часть вторая</title>
			<link>//axforum.info/forums/blog.php?b=8249</link>
			<pubDate>Thu, 12 Oct 2017 13:13:14 GMT</pubDate>
			<description><![CDATA[Часть вторая - Что такое Modern Web и зачем он вам?

Предыдущая статья серии: Динозавр осваивает Modern Web - Часть первая (http://www.axforum.info/forums/blog.php?b=8248)

В первой статье этого поста мы выяснили, что игнорировать веб технологии не-Microsoft более невозможно, стало быть, нужно их изучать. Более того, картина мира такова, что именно за ними будущее веба, нравится вам это, или нет. Уверен, если вы пробовали погрузиться в предметную область, вы ни раз видели высказывания вида: "Смиритесь, это будущее", или "Просто примите это". Увы, другого пути просто нет. Мир Modern Web настолько отличается от привычного нам, динозаврам, что проще вымереть, чем его принять.

Так что же это такое? Для себя я был назвал его "суммой технологий". Основой веба, как десять лет назад, так и сейчас являются три ключевых "компонента":
    • HTML
    • CSS
    • Java Script

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

Так, ключевыми элементами процесса разработки в настоящее время являются три базовых элемента:
    • Менеджер пакетов
    • Рендер-препроцессор
    • Сборщик

Обратите внимание, что тут нет ни фреймворка, ни компилятора, ни веб-сервера - ничего из того что было бы нам известно. Что для чего нужно?

Менеджер пакетов используется для… загрузки, установки и публикации пакетов! Логично на нахера он нужен? Тут все просто: все придумано до нас, так что если вы собираетесь писать очередного "убийцу jQuery", тогда вы, скорее всего, захотите использовать сам jQuery, или любую другую готовую библиотеку. Для этого в общем-то и нужен менеджер пакетов. Если хотите, это местный аналог NuGet.

Рендер-препроцессор. Тут сложнее. Как я уже говорил, современный веб не использует абстракцию от веб-стандартов, он их улучшает. Соответственно, никто не пишет на чистом JS - это же самоубийство! Вместо этого все пишут на ES6, TypeScript, реже Dart/CoffeScript и других аналогах, которые позже нужно будет "скомпилировать", или, если правильно выразится "транслировать" в чистый JS. Для этого и нужен Рендер-препроцессор. Попутно этот компонент может выполнять и другие задачи, например, минификацию файла, обфускацию кода и т.д.

Функции сборщика еще менее понятны нам с вами. В нашем мире есть Build/Deploy и этим все сказано. Тут хер: все сам, все сам. Задача сборщика: собрать в кучу все пакеты (библиотеки и фреймворки) которые вы используете в своем проекте (а также их собственные зависимости!), прогнать и их и ваш собственный код через все необходимые препроцессоры, после чего выложить результат в нужном виде в нужное место. Тут нужно отметить, что вам, вероятно, нужен разный вид в разных средах: например, чистовая версия для продакшена должна быть минифицирована, или должна иметь другие переменные окружения и т.д.

Феномен номер 2. Обратите внимание, что все эти инструменты призваны получить готовый сайт, а не его абстракцию. Тут нет какого-то среднего слоя, типа ASP, Java, PHP, который выполнится на сервере и высрет в сокет комплект из HTML+CSS+JS. Единственные исключения - это, конечно, бэкэнд для работы с данными и авторизация. Но суть, думаю, ясна: различия в подходе фундаментальны.

Феномен номер 3. Все ненавидят JS, но все равно на нем пишут. Ненавистью к нему пропитана документация ко всем библиотекам и фреймворкам, что вы сможете найти, но все всё равно на нем пишут. Поэтому, они написали на нем Node.js и все его плагины :) Хотя, как мы с вами уже выяснили, это не совсем правда: для разработки используются "улученные" версии JS, которые уже вполне годятся для промышленного использования.

Феномен номер 4. Командная строка. Как видите, круг полностью замкнулся. Мы вернулись не только к архаичным стандартам, но и подходам. Понятно, что компилятору не нужен пользовательский интерфейс и где-то там, в недрах "современных" IDE он по-прежнему вызывается из командной строки, но зачем к этому возвращаться? Все просто: командная строка - это единственный инструмент, гарантированно присутствующий на всех платформах.

Феномен номер 5. Кросс-платформенность. Независимость от платформы - это какая-то мантра, современной разработки! Нужно отметить, что в чистой "кросс-платформенности" решительно нет ничего хорошего! Все эти платформы не зря такие разные - каждой есть чем гордиться и что противопоставить конкурентам. К счастью, в современном мире речь не идет о замене ста стандартов за счет сто первого, как это было, например, с Java. Теперь принято максимально напирать на "нативный" слой и абстрагировать от платформы лишь некоторый "рантайм", которому действительно, не так важно где выполняться.

Теперь, мы вроде бы разобрались с суммой технологий, осталось понять почему за этим будущее. В основном об этом принято говорить, как о свершившемся факте и прикрываться инновационностью подходов (читай возврату к истокам). Для себя я выделил три причины почему:

Причина первая. Чтобы начать что-то делать разработчику нужно глубоко погрузиться в используемые технологии. Кто-то может сказать что это минус, но я как человек повидавший немало команд разработки могу сказать, что это плюс. Эта экосистема создает своеобразный барьер вхождения для программиста. Больше нельзя прочитать "MVC для чайников" и смело начать херачить отвратительный код в Visual Studio. Придется разбираться как все это работает на нижнем уровне, так как и программировать вы будете на нем же. Отсев слабых и ленивых разработчиков несомненно является плюсом для отрасли. Это справедливо как для компаний интеграторов, так и для конечных пользователей.

Причина вторая. Если разобраться, идея писать на JS серверный код не так безумна, как может показаться на первый взгляд. Во-первых, не нужно добавлять в стек новые языки и технологии, во-вторых, сервер ничем не хуже браузера! Если рендеринг на клиенте все равно придется делать, почему бы схожим образом не делать это на сервере? Во многом, именно поэтому за ними будущее: не нужно искать оптимальный баланс при распределении задач для обработки на сервере и клиенте. Первичный рендеринг может быть выполнен на сервере, передан клиенту и обработан им еще до того как активное содержимое будет загружено в память. Далее приложение будет выполняться уже на клиенте, а сервер, как ему и положено будет обрабатывать лишь запросы контента.

Причина третья. Этот подход стимулирует "исправление" родовых травм веб стандартов и в конечном итоге упростит жизнь нам, разработчикам. Ну а пользователям вообще насрать что за технологии мы используем.

Итак, с концепцией разобрались. Что дальше? Об этом я расскажу в следующих постах этой серии:

*Динозавр осваивает Modern Web - Часть третья (http://www.axforum.info/forums/blog.php?b=8250)*]]></description>
			<content:encoded><![CDATA[<div>Часть вторая - Что такое Modern Web и зачем он вам?<br />
<br />
Предыдущая статья серии: <a href="http://www.axforum.info/forums/blog.php?b=8248" target="_blank">Динозавр осваивает Modern Web - Часть первая</a><br />
<br />
В первой статье этого поста мы выяснили, что игнорировать веб технологии не-Microsoft более невозможно, стало быть, нужно их изучать. Более того, картина мира такова, что именно за ними будущее веба, нравится вам это, или нет. Уверен, если вы пробовали погрузиться в предметную область, вы ни раз видели высказывания вида: &quot;Смиритесь, это будущее&quot;, или &quot;Просто примите это&quot;. Увы, другого пути просто нет. Мир Modern Web настолько отличается от привычного нам, динозаврам, что проще вымереть, чем его принять.<br />
<br />
Так что же это такое? Для себя я был назвал его &quot;суммой технологий&quot;. Основой веба, как десять лет назад, так и сейчас являются три ключевых &quot;компонента&quot;:<br />
    • HTML<br />
    • CSS<br />
    • Java Script<br />
<br />
Понятно, что за это время они сколько-то изменились, но весьма несущественно. Они все так же не отвечают требованиям настоящего времени и их по-прежнему невозможно &quot;писать руками&quot;. Изменился сам подход к разработке. Если раньше все разрабатывали фреймворки, которые позволяли разработчикам максимально далеко отгородиться и абстрагироваться от фундаментальных стандартов, то сейчас идет обратный процесс: все пытаются в некотором роде улучшить то что есть, но при этом писать на исходных языках веба.<br />
<br />
Так, ключевыми элементами процесса разработки в настоящее время являются три базовых элемента:<br />
    • Менеджер пакетов<br />
    • Рендер-препроцессор<br />
    • Сборщик<br />
<br />
Обратите внимание, что тут нет ни фреймворка, ни компилятора, ни веб-сервера - ничего из того что было бы нам известно. Что для чего нужно?<br />
<br />
Менеджер пакетов используется для… загрузки, установки и публикации пакетов! Логично на нахера он нужен? Тут все просто: все придумано до нас, так что если вы собираетесь писать очередного &quot;убийцу jQuery&quot;, тогда вы, скорее всего, захотите использовать сам jQuery, или любую другую готовую библиотеку. Для этого в общем-то и нужен менеджер пакетов. Если хотите, это местный аналог NuGet.<br />
<br />
Рендер-препроцессор. Тут сложнее. Как я уже говорил, современный веб не использует абстракцию от веб-стандартов, он их улучшает. Соответственно, никто не пишет на чистом JS - это же самоубийство! Вместо этого все пишут на ES6, TypeScript, реже Dart/CoffeScript и других аналогах, которые позже нужно будет &quot;скомпилировать&quot;, или, если правильно выразится &quot;транслировать&quot; в чистый JS. Для этого и нужен Рендер-препроцессор. Попутно этот компонент может выполнять и другие задачи, например, минификацию файла, обфускацию кода и т.д.<br />
<br />
Функции сборщика еще менее понятны нам с вами. В нашем мире есть Build/Deploy и этим все сказано. Тут хер: все сам, все сам. Задача сборщика: собрать в кучу все пакеты (библиотеки и фреймворки) которые вы используете в своем проекте (а также их собственные зависимости!), прогнать и их и ваш собственный код через все необходимые препроцессоры, после чего выложить результат в нужном виде в нужное место. Тут нужно отметить, что вам, вероятно, нужен разный вид в разных средах: например, чистовая версия для продакшена должна быть минифицирована, или должна иметь другие переменные окружения и т.д.<br />
<br />
Феномен номер 2. Обратите внимание, что все эти инструменты призваны получить готовый сайт, а не его абстракцию. Тут нет какого-то среднего слоя, типа ASP, Java, PHP, который выполнится на сервере и высрет в сокет комплект из HTML+CSS+JS. Единственные исключения - это, конечно, бэкэнд для работы с данными и авторизация. Но суть, думаю, ясна: различия в подходе фундаментальны.<br />
<br />
Феномен номер 3. Все ненавидят JS, но все равно на нем пишут. Ненавистью к нему пропитана документация ко всем библиотекам и фреймворкам, что вы сможете найти, но все всё равно на нем пишут. Поэтому, они написали на нем Node.js и все его плагины :) Хотя, как мы с вами уже выяснили, это не совсем правда: для разработки используются &quot;улученные&quot; версии JS, которые уже вполне годятся для промышленного использования.<br />
<br />
Феномен номер 4. Командная строка. Как видите, круг полностью замкнулся. Мы вернулись не только к архаичным стандартам, но и подходам. Понятно, что компилятору не нужен пользовательский интерфейс и где-то там, в недрах &quot;современных&quot; IDE он по-прежнему вызывается из командной строки, но зачем к этому возвращаться? Все просто: командная строка - это единственный инструмент, гарантированно присутствующий на всех платформах.<br />
<br />
Феномен номер 5. Кросс-платформенность. Независимость от платформы - это какая-то мантра, современной разработки! Нужно отметить, что в чистой &quot;кросс-платформенности&quot; решительно нет ничего хорошего! Все эти платформы не зря такие разные - каждой есть чем гордиться и что противопоставить конкурентам. К счастью, в современном мире речь не идет о замене ста стандартов за счет сто первого, как это было, например, с Java. Теперь принято максимально напирать на &quot;нативный&quot; слой и абстрагировать от платформы лишь некоторый &quot;рантайм&quot;, которому действительно, не так важно где выполняться.<br />
<br />
Теперь, мы вроде бы разобрались с суммой технологий, осталось понять почему за этим будущее. В основном об этом принято говорить, как о свершившемся факте и прикрываться инновационностью подходов (читай возврату к истокам). Для себя я выделил три причины почему:<br />
<br />
Причина первая. Чтобы начать что-то делать разработчику нужно глубоко погрузиться в используемые технологии. Кто-то может сказать что это минус, но я как человек повидавший немало команд разработки могу сказать, что это плюс. Эта экосистема создает своеобразный барьер вхождения для программиста. Больше нельзя прочитать &quot;MVC для чайников&quot; и смело начать херачить отвратительный код в Visual Studio. Придется разбираться как все это работает на нижнем уровне, так как и программировать вы будете на нем же. Отсев слабых и ленивых разработчиков несомненно является плюсом для отрасли. Это справедливо как для компаний интеграторов, так и для конечных пользователей.<br />
<br />
Причина вторая. Если разобраться, идея писать на JS серверный код не так безумна, как может показаться на первый взгляд. Во-первых, не нужно добавлять в стек новые языки и технологии, во-вторых, сервер ничем не хуже браузера! Если рендеринг на клиенте все равно придется делать, почему бы схожим образом не делать это на сервере? Во многом, именно поэтому за ними будущее: не нужно искать оптимальный баланс при распределении задач для обработки на сервере и клиенте. Первичный рендеринг может быть выполнен на сервере, передан клиенту и обработан им еще до того как активное содержимое будет загружено в память. Далее приложение будет выполняться уже на клиенте, а сервер, как ему и положено будет обрабатывать лишь запросы контента.<br />
<br />
Причина третья. Этот подход стимулирует &quot;исправление&quot; родовых травм веб стандартов и в конечном итоге упростит жизнь нам, разработчикам. Ну а пользователям вообще насрать что за технологии мы используем.<br />
<br />
Итак, с концепцией разобрались. Что дальше? Об этом я расскажу в следующих постах этой серии:<br />
<br />
<b><a href="http://www.axforum.info/forums/blog.php?b=8250" target="_blank">Динозавр осваивает Modern Web - Часть третья</a></b></div>

]]></content:encoded>
			<dc:creator>Артем Enot Грунин</dc:creator>
			<guid isPermaLink="true">//axforum.info/forums/blog.php?b=8249</guid>
		</item>
		<item>
			<title>Динозавр осваивает Modern Web - Часть первая</title>
			<link>//axforum.info/forums/blog.php?b=8248</link>
			<pubDate>Fri, 06 Oct 2017 08:27:01 GMT</pubDate>
			<description><![CDATA[Часть первая - История вопроса

Иногда, если лет десять игнорировать развитие веб технологий, можно обнаружить что ты немного отстал от жизни. Я, как и большинство моих коллег и знакомых по отрасли, долго сидел за пазухой у Microsoft, поэтому изредка и с недоверием поглядывал за тем что творится снаружи. Если бы я тогда взялся, кратко обозначить мою позицию относительно происходящего, я бы закричал что-то вроде: "Карл! Да они там все ебанулись! Они написали сервер на джава скрипт!". Какое-то время такая позиция вполне себя оправдывала, так как изучать эти модные технологии не было никакого смысла - их просто негде было применить в текущем технологическом стеке.

В те годы Microsoft вела священную войну со всеми, кто не Microsoft, и на каждую модную технологию язычников приходился хотя бы один одобренный церковью аналог. Кроме того, сами модные технологии не заслуживали особого доверия. Каждый месяц что-то одно безнадежно устаревало, и на замену ему приходила пара новых фреймворков. Многие из них развивались во все стороны сразу, имея с десяток несовместимых версий от разных авторов. Терять на них время не было ни нужды, ни возможности. 

Так что же теперь изменилось, спросите вы, и я отвечу. Нас предали, Карл… Теперь мы с ними союзники… Нет больше сил альянса и независимых, есть только Google, который все еще пытается захватить мир, но и на него всем уже давно наплевать. Microsoft, же наоборот, стала водить опасную дружбу с опен сорс сообществом и интегрировать его технологии в свои продукты (не без участия самого сообщества). Во многом это стало возможным благодаря тому, что сами модные технологии за это время подросли, созрели, обзавелись хорошей документацией и перебили своих слабых конкурентов. Стало можно говорить о какой-никакой стандартизации.

К счастью для нас, речь пока не идет о том, чтобы писать плагины, или активности процессов на JS, или запускать CRM на Node под Линукс, но в части веб разработки процесс, несомненно, пошел. В веб-интерфейсе CRM официально применяются Knockout и jQuery (не лучший пример Modern Web). Microsoft заявила, что в следующей версии CRM все скрипты будут переписаны на TypeScript, а оставшиеся ASP.NET контролы будут заменены на "современные". В одной из грядущих версий Microsoft вот-вот разродится знаменитым Custom Control Framework, который совершенно точно не будет технологией, которая базируется на сервере. Иными словами, если и начинать, то сейчас.

Так что же такое Modern Web и как это сочетается с CRM? Об этом я расскажу в следующих постах этой серии:

	  		 		 			 				  				* 					Динозавр осваивает Modern Web - Часть вторая (http://www.axforum.info/forums/blog.php?b=8249) 						 					 				*]]></description>
			<content:encoded><![CDATA[<div>Часть первая - История вопроса<br />
<br />
Иногда, если лет десять игнорировать развитие веб технологий, можно обнаружить что ты немного отстал от жизни. Я, как и большинство моих коллег и знакомых по отрасли, долго сидел за пазухой у Microsoft, поэтому изредка и с недоверием поглядывал за тем что творится снаружи. Если бы я тогда взялся, кратко обозначить мою позицию относительно происходящего, я бы закричал что-то вроде: &quot;Карл! Да они там все ебанулись! Они написали сервер на джава скрипт!&quot;. Какое-то время такая позиция вполне себя оправдывала, так как изучать эти модные технологии не было никакого смысла - их просто негде было применить в текущем технологическом стеке.<br />
<br />
В те годы Microsoft вела священную войну со всеми, кто не Microsoft, и на каждую модную технологию язычников приходился хотя бы один одобренный церковью аналог. Кроме того, сами модные технологии не заслуживали особого доверия. Каждый месяц что-то одно безнадежно устаревало, и на замену ему приходила пара новых фреймворков. Многие из них развивались во все стороны сразу, имея с десяток несовместимых версий от разных авторов. Терять на них время не было ни нужды, ни возможности. <br />
<br />
Так что же теперь изменилось, спросите вы, и я отвечу. Нас предали, Карл… Теперь мы с ними союзники… Нет больше сил альянса и независимых, есть только Google, который все еще пытается захватить мир, но и на него всем уже давно наплевать. Microsoft, же наоборот, стала водить опасную дружбу с опен сорс сообществом и интегрировать его технологии в свои продукты (не без участия самого сообщества). Во многом это стало возможным благодаря тому, что сами модные технологии за это время подросли, созрели, обзавелись хорошей документацией и перебили своих слабых конкурентов. Стало можно говорить о какой-никакой стандартизации.<br />
<br />
К счастью для нас, речь пока не идет о том, чтобы писать плагины, или активности процессов на JS, или запускать CRM на Node под Линукс, но в части веб разработки процесс, несомненно, пошел. В веб-интерфейсе CRM официально применяются Knockout и jQuery (не лучший пример Modern Web). Microsoft заявила, что в следующей версии CRM все скрипты будут переписаны на TypeScript, а оставшиеся ASP.NET контролы будут заменены на &quot;современные&quot;. В одной из грядущих версий Microsoft вот-вот разродится знаменитым Custom Control Framework, который совершенно точно не будет технологией, которая базируется на сервере. Иными словами, если и начинать, то сейчас.<br />
<br />
Так что же такое Modern Web и как это сочетается с CRM? Об этом я расскажу в следующих постах этой серии:<br />
<br />
	  		 		 			 				  				<b> 					<a href="http://www.axforum.info/forums/blog.php?b=8249" target="_blank">Динозавр осваивает Modern Web - Часть вторая</a> 						 					 				</b></div>

]]></content:encoded>
			<dc:creator>Артем Enot Грунин</dc:creator>
			<guid isPermaLink="true">//axforum.info/forums/blog.php?b=8248</guid>
		</item>
		<item>
			<title>Регистрация сборок плагинов с зависимостями</title>
			<link>//axforum.info/forums/blog.php?b=8247</link>
			<pubDate>Mon, 18 Sep 2017 14:35:13 GMT</pubDate>
			<description><![CDATA[Прежде всего, прошу сильно не бить ногами за столь избитую тему, как проблема CRM и зависимых сборок. Проблема стара как мир, казалось бы, ее решение уже найдено, а Microsoft старательно игнорирует проблему уже много лет, но... Недавно я узнал кое что новое по этому вопросу, что, вероятно будет вам интересно.

Итак, всем нам известно, что система не позволяет регистрировать сборки вместе с их зависимостями. Например, вы решили использовать в своем плагине тот же Json.NET, или другую популярную сборку. Система позволит зарегистрировать ваш плагин, но при выполнении вы получите ошибку вида "не удалось загрузить сборку Json.NET, или одну из ее зависимостей". Каноническое решение от Gonzalo Ruiz, думаю тоже всем известно: http://gonzaloruizcrm.blogspot.ru/2016/04/including-reference-assemblies-in.html. Его суть в том, чтобы использовать утиллиту ILMerge от той же Microsoft, чтобы слепить уже существующие сборки в одну. Что удобно, можно даже встроить эту процедуру в процесс сборки решения, что и демонстрирует Gonzalo.

Увы, за все приходится платить. Процесс сборки большого решения с большим числом зависимостей может занимать долгое время (до 20 минут на нашем проекте) и создает некоторые трудности с отладкой. Кроме того, мне не совсем ясна позиция Microsoft относительно этого решения: https://blogs.msdn.microsoft.com/crm/2010/11/09/how-to-reference-assemblies-from-plug-ins/. Как отмечает автор:

It isn't blocked, but it isn't supported, as an option for referencing custom assemblies.

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

Так есть ли решение лучше? И да и нет. В поисках альтернативного решения я наткнулся на следующую, довольно популярную статью Jeffrey Richter: https://blogs.msdn.microsoft.com/microsoft_press/2010/02/03/jeffrey-richter-excerpt-2-from-clr-via-c-third-edition/ и несколько реализаций предоженной им концепции. Суть предложенного решения в том, чтобы включить все зависимые сборки в целевую сборку как ресурсы - по сути так же как это делается с вложениями, манифестами, файлами локализации и т.д. и вставить в свой код небольшой сниппет, который зарегиструрует их при помощи обработчика события AppDomain.CurrentDomain.AssemblyResolve. Подход настолько прост и элегантен, что создатель ILMerge Mike Barnett даже написал (в комментарих под этим постом) что не стал бы писать ILMerge, если бы раньше знал об этом способе. В качестве бонуса, это решение позволяет использовать зависимые сборки, которые используют рефлексию или внедрение зависимости, которые могут некорректно работать при использовании ILMerge.

Как оказалось, даже есть примеры его применения в CRM: http://blog.micic.ch/net/dynamics-crm-2011-plugin-with-custom-referenced-libraries, хотя автор и не сослался на первоисточник.

Решение не обязательно использовать в лоб, лично я рекомендую использовать вот эту реализацию предложенного подхода: https://github.com/Fody/Costura. После установки, этот пакет автоматически встроится в борку проекта и на выходе вы получите то что хотели. При этом, вам даже не потребуется изменять Build Action и выполнять любые другие манипуляции!

Однако, сильнее радости было лишь разочарование. Этот подход не будет работать в "посочнице" и, соответственно, в онлайн версии. Увы, в песочнице CRM, среди всего прочего, нельзя прикасаться к событию AppDomain.CurrentDomain.AssemblyResolve: http://jerhome.blogspot.ru/2014/04/ms-crm-2011-plugin-sandbox-mode.html, что сводит на нет все усилия.

Тем не менее, решение отлично работает без изоляции на он премис. В моем случае, этого вполне достаточно. Будем надеяться, рано или поздно, Microsoft обратит внимание на проблему, и нам больше не придется выбирать, меньшее из двух зол.

*Добавлено 20.09.2017:
*Перспектива избавиться от ILMerge оказалась столь заманчива, что мне пришлось потратить еще день, чтобы окончательно развеять иллюзии. Мной двигал посыл, что если нельзя использовать событие AppDomain.CurrentDomain.AssemblyResolve, то, вероятно, должен быть другой способ "напихать" в контекст нужные сборки. В своих поисках я набрел на статью, которая объясняет как работает вся концепция загрузки сборок: https://docs.microsoft.com/en-us/dotnet/framework/deployment/best-practices-for-assembly-loading. Цитирую:

Assemblies that are loaded from byte arrays are loaded without context unless the identity of the assembly, which is established when policy is applied, matches the identity of an assembly in the global assembly cache; in that case, the assembly is loaded from the global assembly cache

А теперь фаталити:

Loading assemblies without context has the following disadvantages: Other assemblies cannot bind to assemblies that are loaded without context, unless you handle the AppDomain.AssemblyResolve event.

Именно это я и наблюдал на практике: мне удалось успешно загрузить зависимую сборку в домен, но она все равно не биндилась из основной.

Увы, у меня кончились идеи, как еще можно реализовать загрузку зависимостей не изменяя сам подход к разработке. Из "маразмов" есть мысль попробовать разместить в основной сборке только "инжектор", а сам плагин и его зависимости грузить и исполнять в отдельном домене. Иными словами, повторить то что делает Microsoft и засунуть их матрешку в свою. Скорее всего, при таком подходе я не смогу напрямую работать с OrganizationContext и вызовы между доменами придется проксировать, но это уже совсем другой пост...]]></description>
			<content:encoded><![CDATA[<div>Прежде всего, прошу сильно не бить ногами за столь избитую тему, как проблема CRM и зависимых сборок. Проблема стара как мир, казалось бы, ее решение уже найдено, а Microsoft старательно игнорирует проблему уже много лет, но... Недавно я узнал кое что новое по этому вопросу, что, вероятно будет вам интересно.<br />
<br />
Итак, всем нам известно, что система не позволяет регистрировать сборки вместе с их зависимостями. Например, вы решили использовать в своем плагине тот же Json.NET, или другую популярную сборку. Система позволит зарегистрировать ваш плагин, но при выполнении вы получите ошибку вида &quot;не удалось загрузить сборку Json.NET, или одну из ее зависимостей&quot;. Каноническое решение от Gonzalo Ruiz, думаю тоже всем известно: <a href="http://gonzaloruizcrm.blogspot.ru/2016/04/including-reference-assemblies-in.html" target="_blank">http://gonzaloruizcrm.blogspot.ru/20...mblies-in.html</a>. Его суть в том, чтобы использовать утиллиту ILMerge от той же Microsoft, чтобы слепить уже существующие сборки в одну. Что удобно, можно даже встроить эту процедуру в процесс сборки решения, что и демонстрирует Gonzalo.<br />
<br />
Увы, за все приходится платить. Процесс сборки большого решения с большим числом зависимостей может занимать долгое время (до 20 минут на нашем проекте) и создает некоторые трудности с отладкой. Кроме того, мне не совсем ясна позиция Microsoft относительно этого решения: <a href="https://blogs.msdn.microsoft.com/crm/2010/11/09/how-to-reference-assemblies-from-plug-ins/" target="_blank">https://blogs.msdn.microsoft.com/crm...from-plug-ins/</a>. Как отмечает автор:<br />
<i><br />
It isn't blocked, but it isn't supported, as an option for referencing custom assemblies.<br />
</i><br />
Иными словами, прикрыть лавочку уже нельзя, так как лазейкой активно пользуются, но альтенативные решения не были предложены. Однако ясно, что решение не вызывает угрозы безопасности системы, иначе оно не работало бы в онлайн версии.<br />
<br />
Так есть ли решение лучше? И да и нет. В поисках альтернативного решения я наткнулся на следующую, довольно популярную статью Jeffrey Richter: <a href="https://blogs.msdn.microsoft.com/microsoft_press/2010/02/03/jeffrey-richter-excerpt-2-from-clr-via-c-third-edition/" target="_blank">https://blogs.msdn.microsoft.com/mic...third-edition/</a> и несколько реализаций предоженной им концепции. Суть предложенного решения в том, чтобы включить все зависимые сборки в целевую сборку как ресурсы - по сути так же как это делается с вложениями, манифестами, файлами локализации и т.д. и вставить в свой код небольшой сниппет, который зарегиструрует их при помощи обработчика события AppDomain.CurrentDomain.AssemblyResolve. Подход настолько прост и элегантен, что создатель ILMerge Mike Barnett даже написал (в комментарих под этим постом) что не стал бы писать ILMerge, если бы раньше знал об этом способе. В качестве бонуса, это решение позволяет использовать зависимые сборки, которые используют рефлексию или внедрение зависимости, которые могут некорректно работать при использовании ILMerge.<br />
<br />
Как оказалось, даже есть примеры его применения в CRM: <a href="http://blog.micic.ch/net/dynamics-crm-2011-plugin-with-custom-referenced-libraries" target="_blank">http://blog.micic.ch/net/dynamics-cr...nced-libraries</a>, хотя автор и не сослался на первоисточник.<br />
<br />
Решение не обязательно использовать в лоб, лично я рекомендую использовать вот эту реализацию предложенного подхода: <a href="https://github.com/Fody/Costura" target="_blank">https://github.com/Fody/Costura</a>. После установки, этот пакет автоматически встроится в борку проекта и на выходе вы получите то что хотели. При этом, вам даже не потребуется изменять Build Action и выполнять любые другие манипуляции!<br />
<br />
Однако, сильнее радости было лишь разочарование. Этот подход не будет работать в &quot;посочнице&quot; и, соответственно, в онлайн версии. Увы, в песочнице CRM, среди всего прочего, нельзя прикасаться к событию AppDomain.CurrentDomain.AssemblyResolve: <a href="http://jerhome.blogspot.ru/2014/04/ms-crm-2011-plugin-sandbox-mode.html" target="_blank">http://jerhome.blogspot.ru/2014/04/m...dbox-mode.html</a>, что сводит на нет все усилия.<br />
<br />
Тем не менее, решение отлично работает без изоляции на он премис. В моем случае, этого вполне достаточно. Будем надеяться, рано или поздно, Microsoft обратит внимание на проблему, и нам больше не придется выбирать, меньшее из двух зол.<br />
<br />
<b>Добавлено 20.09.2017:<br />
</b>Перспектива избавиться от ILMerge оказалась столь заманчива, что мне пришлось потратить еще день, чтобы окончательно развеять иллюзии. Мной двигал посыл, что если нельзя использовать событие AppDomain.CurrentDomain.AssemblyResolve, то, вероятно, должен быть другой способ &quot;напихать&quot; в контекст нужные сборки. В своих поисках я набрел на статью, которая объясняет как работает вся концепция загрузки сборок: <a href="https://docs.microsoft.com/en-us/dotnet/framework/deployment/best-practices-for-assembly-loading" target="_blank">https://docs.microsoft.com/en-us/dot...sembly-loading</a>. Цитирую:<br />
<br />
<i>Assemblies that are loaded from byte arrays are loaded without context unless the identity of the assembly, which is established when policy is applied, matches the identity of an assembly in the global assembly cache; in that case, the assembly is loaded from the global assembly cache<br />
</i><br />
А теперь фаталити:<br />
<br />
<i>Loading assemblies without context has the following disadvantages: Other assemblies cannot bind to assemblies that are loaded without context, unless you handle the AppDomain.AssemblyResolve event.<br />
</i><br />
Именно это я и наблюдал на практике: мне удалось успешно загрузить зависимую сборку в домен, но она все равно не биндилась из основной.<br />
<br />
Увы, у меня кончились идеи, как еще можно реализовать загрузку зависимостей не изменяя сам подход к разработке. Из &quot;маразмов&quot; есть мысль попробовать разместить в основной сборке только &quot;инжектор&quot;, а сам плагин и его зависимости грузить и исполнять в отдельном домене. Иными словами, повторить то что делает Microsoft и засунуть их матрешку в свою. Скорее всего, при таком подходе я не смогу напрямую работать с OrganizationContext и вызовы между доменами придется проксировать, но это уже совсем другой пост...</div>

]]></content:encoded>
			<dc:creator>Артем Enot Грунин</dc:creator>
			<guid isPermaLink="true">//axforum.info/forums/blog.php?b=8247</guid>
		</item>
		<item>
			<title>Оптимизация CRM</title>
			<link>//axforum.info/forums/blog.php?b=8242</link>
			<pubDate>Thu, 09 Mar 2017 15:09:32 GMT</pubDate>
			<description><![CDATA[Вопрос быстродействия CRM (как и любой другой системы) рано или поздно возникает на любом проекте и на любом железе. Как правило, заказчик винит вас, вы вендора, вендор рекомендует провести контроль качества кода, но все вместе мы забываем о инфраструктуре. Сразу скажу, что я не большой специалист в администрировании разного рода инфраструктурных продуктов, но за годы работы чему-то все равно пришлось научиться. Если вы не наблюдаете деградации производительности системы под нагрузкой - она одинаково сильно тормозит и во время простоя, и во время работы - значит это проблема конфигурации или инфраструктуры. 

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

1. *Поднять уровень совместимости базы данных CRM*. Современные версии CRM поддерживают современные версии SQL. Тем не менее, даже на чистой инсталляции, базы организации создаются "по шаблону" и имеют Database Compatibility Level соответствующий SQL 2008. Данный факт вынуждает SQL Server работать в режиме совместимости с базой почти десятилетней давности, что негативно сказывается на производительности. Изменения уровня совместимости в свое время рекомендовалось инженерами MS Premier Field Support, однако пруф потерялся, остались только цитированные источники. Как это сделать? https://msdn.microsoft.com/en-us/library/bb933794.aspx. Версию нужно поднимать до последней. Мой опыт говорит, что изменение этого параметра дает солидный выхлоп производительности, особенно в части загрузки списков. Будьте внимательны! В сети встречаются упоминания, что CRM 2016 имеет проблемы с блокировкой для SQL 2014 
2. *Изменить параметры кэширования IIS*. В современных версиях IIS, параметр omitVaryStar должен быть включен по умолчанию, но лучше проверить. Это глобальный параметр, поэтому он влияет на все сайты развернутые на вашем сервере.  Как это сделать? http://crmtipoftheday.com/2014/11/18/if-form-load-is-slow-take-this-pill. Опыт показал, что этот параметр дает ощутимый прирост скорости открытия форм
3. *Включить сжатие AJAX и WCF трафика*. Еще один глобальный параметр IIS, который официально рекомендуется MS и Premier Field Support: https://blogs.msdn.microsoft.com/crminthefield/2014/09/02/enable-wcf-compression-ssl-to-improve-crm-2013-network-performance. Мне показалось, что загрузка формы слегка увеличилась, но не столь ощутимо, как после предыдущих настроек. Помните, что сжатие трафика сильнее нагружает фронт энд, но автоматически выключается при достижении определенного лимита загрузки памяти и процессора
4. *Включить поддержку CLR для SQL*. Хранимые CLR процедуры более производительны, чем процедуры TSQL, но поддержка CRL по умолчанию выключена из соображений безопасности. Как ее включить? https://msdn.microsoft.com/ru-ru/library/ms131048.aspx. Система должна сама подхватить изменение этого параметра и использовать CLR версии своих процедур. Данный параметр должен существенно влиять на скорость отображения диграмм и панелей мониторинга
5. Если вы используете NLB кластеризацию, вы обязательно должны *настроить SPN* так как написано в Implementation Guide! Если инсталляция делается под учетной записью с правами на создание SPN, система сама пропишет необходимые SPN для асинхронного сервиса и сервиса песочницы. Для веб приложения их всегда нужно прописывать руками. Если вы этого не сделаете, от вашего кластера будет больше вреда, чем пользы.  Почему? Без этого токен авторизации выданный одному фронт-энду не будет работать на другом. Балансировщик нагрузки распределяет запросы случайным образом, поэтому браузеру должно повезти два раза подряд обратится к одному и тому же серверу - первый раз для авторизации, а второй для загрузки веб-ресурса, например, скрипта. Современные браузеры загружают страницы в несколько потоков, что увеличивает шанс получить кромешный ад из access denied и успешных запросов и увеличить нагрузку на сеть.

Чтобы избежать проблем на клиентской части, можно поиграть тонкими настройками CRM OrgDB, такими как DisableIECompatMode: https://support.microsoft.com/en-us/kb/2691237, или указать минимальную версию IE чтобы принудительно обновились идиоты, которые не ставят обновления «потому что потом тормозит», или «потом нужно перезагружаться».

Теперь что касается обслуживания базы данных. Ей нужен хороший админ! Понимаю, что мысль не новая, но ее часто упускают. Только специально обученный человек может правильно настроить бэкапы, создать нужные индексы и т.д. К этой задаче ни в коем случае нельзя подключать разных самоделкиных со скриптами из интернета - нужен реальный специалист, к которым, кстати, я себя НЕ отношу. Что совершенно точно не нужно делать:
    • Шринковать базу, чтобы она занимала меньше места
    • Создавать все индексы, которые предложил адвайсор, или статистика
    • Резать файл лога!!!!
    • Заниматься прочей херней, в которой вы не разбираетесь

Если вы делаете что-то из этого - немедленно перестаньте и срочно ищите того, кто может все починить.

Хорошего вам быстродействия!]]></description>
			<content:encoded><![CDATA[<div>Вопрос быстродействия CRM (как и любой другой системы) рано или поздно возникает на любом проекте и на любом железе. Как правило, заказчик винит вас, вы вендора, вендор рекомендует провести контроль качества кода, но все вместе мы забываем о инфраструктуре. Сразу скажу, что я не большой специалист в администрировании разного рода инфраструктурных продуктов, но за годы работы чему-то все равно пришлось научиться. Если вы не наблюдаете деградации производительности системы под нагрузкой - она одинаково сильно тормозит и во время простоя, и во время работы - значит это проблема конфигурации или инфраструктуры. <br />
<br />
Итак, краткий перечень операций, которые обязательно нужно проделать на любом проекте и в любой конфигурации системы. Если даже это не помогает - это повод задуматься, и искать проблему в вашем решении.<br />
<br />
1. <b>Поднять уровень совместимости базы данных CRM</b>. Современные версии CRM поддерживают современные версии SQL. Тем не менее, даже на чистой инсталляции, базы организации создаются &quot;по шаблону&quot; и имеют Database Compatibility Level соответствующий SQL 2008. Данный факт вынуждает SQL Server работать в режиме совместимости с базой почти десятилетней давности, что негативно сказывается на производительности. Изменения уровня совместимости в свое время рекомендовалось инженерами MS Premier Field Support, однако пруф потерялся, остались только цитированные источники. Как это сделать? <a href="https://msdn.microsoft.com/en-us/library/bb933794.aspx" target="_blank">https://msdn.microsoft.com/en-us/library/bb933794.aspx</a>. Версию нужно поднимать до последней. Мой опыт говорит, что изменение этого параметра дает солидный выхлоп производительности, особенно в части загрузки списков. Будьте внимательны! В сети встречаются упоминания, что CRM 2016 имеет проблемы с блокировкой для SQL 2014 <br />
2. <b>Изменить параметры кэширования IIS</b>. В современных версиях IIS, параметр omitVaryStar должен быть включен по умолчанию, но лучше проверить. Это глобальный параметр, поэтому он влияет на все сайты развернутые на вашем сервере.  Как это сделать? <a href="http://crmtipoftheday.com/2014/11/18/if-form-load-is-slow-take-this-pill" target="_blank">http://crmtipoftheday.com/2014/11/18...take-this-pill</a>. Опыт показал, что этот параметр дает ощутимый прирост скорости открытия форм<br />
3. <b>Включить сжатие AJAX и WCF трафика</b>. Еще один глобальный параметр IIS, который официально рекомендуется MS и Premier Field Support: <a href="https://blogs.msdn.microsoft.com/crminthefield/2014/09/02/enable-wcf-compression-ssl-to-improve-crm-2013-network-performance" target="_blank">https://blogs.msdn.microsoft.com/crm...rk-performance</a>. Мне показалось, что загрузка формы слегка увеличилась, но не столь ощутимо, как после предыдущих настроек. Помните, что сжатие трафика сильнее нагружает фронт энд, но автоматически выключается при достижении определенного лимита загрузки памяти и процессора<br />
4. <b>Включить поддержку CLR для SQL</b>. Хранимые CLR процедуры более производительны, чем процедуры TSQL, но поддержка CRL по умолчанию выключена из соображений безопасности. Как ее включить? <a href="https://msdn.microsoft.com/ru-ru/library/ms131048.aspx" target="_blank">https://msdn.microsoft.com/ru-ru/library/ms131048.aspx</a>. Система должна сама подхватить изменение этого параметра и использовать CLR версии своих процедур. Данный параметр должен существенно влиять на скорость отображения диграмм и панелей мониторинга<br />
5. Если вы используете NLB кластеризацию, вы обязательно должны <b>настроить SPN</b> так как написано в Implementation Guide! Если инсталляция делается под учетной записью с правами на создание SPN, система сама пропишет необходимые SPN для асинхронного сервиса и сервиса песочницы. Для веб приложения их всегда нужно прописывать руками. Если вы этого не сделаете, от вашего кластера будет больше вреда, чем пользы.  Почему? Без этого токен авторизации выданный одному фронт-энду не будет работать на другом. Балансировщик нагрузки распределяет запросы случайным образом, поэтому браузеру должно повезти два раза подряд обратится к одному и тому же серверу - первый раз для авторизации, а второй для загрузки веб-ресурса, например, скрипта. Современные браузеры загружают страницы в несколько потоков, что увеличивает шанс получить кромешный ад из access denied и успешных запросов и увеличить нагрузку на сеть.<br />
<br />
Чтобы избежать проблем на клиентской части, можно поиграть тонкими настройками CRM OrgDB, такими как DisableIECompatMode: <a href="https://support.microsoft.com/en-us/kb/2691237" target="_blank">https://support.microsoft.com/en-us/kb/2691237</a>, или указать минимальную версию IE чтобы принудительно обновились идиоты, которые не ставят обновления «потому что потом тормозит», или «потом нужно перезагружаться».<br />
<br />
Теперь что касается обслуживания базы данных. Ей нужен хороший админ! Понимаю, что мысль не новая, но ее часто упускают. Только специально обученный человек может правильно настроить бэкапы, создать нужные индексы и т.д. К этой задаче ни в коем случае нельзя подключать разных самоделкиных со скриптами из интернета - нужен реальный специалист, к которым, кстати, я себя НЕ отношу. Что совершенно точно не нужно делать:<br />
    • Шринковать базу, чтобы она занимала меньше места<br />
    • Создавать все индексы, которые предложил адвайсор, или статистика<br />
    • Резать файл лога!!!!<br />
    • Заниматься прочей херней, в которой вы не разбираетесь<br />
<br />
Если вы делаете что-то из этого - немедленно перестаньте и срочно ищите того, кто может все починить.<br />
<br />
Хорошего вам быстродействия!</div>

]]></content:encoded>
			<dc:creator>Артем Enot Грунин</dc:creator>
			<guid isPermaLink="true">//axforum.info/forums/blog.php?b=8242</guid>
		</item>
		<item>
			<title>Как работает условие ожидания в асинхронном сервисе</title>
			<link>//axforum.info/forums/blog.php?b=8239</link>
			<pubDate>Thu, 09 Feb 2017 15:59:01 GMT</pubDate>
			<description><![CDATA[Недавно, мы с коллегами столкнулись с проблемой зависания ожидающего процесса. Его условие уже было выполнено, но процесс стоял на месте. Выяснилось, что перезагрузка процесса решает проблему и он идет дальше, в то время как перезагрузка асинхронного сервиса ее не решает - процессы продолжают висеть. Это показалось странным, так как нам говорили, что ожидающие процессы - это плохо, именно потому что асинхронный сервис не знает, когда выполнятся их условия, и вынужден проверять их каждый раз, когда запускается служба. Возможно так оно и было, но готов с уверенностью заявить, что теперь это точно не так.

Как выяснилось, ожидающие процессы тоже управляются событиями! Детали этого механизма будут раскрыты дальше в статье, однако, на случай если вам жалко времени, изложу его сперва кратко:
    1. Шаг ожидания содержит в себе условие, при выполнении которого процесс должен пойти дальше
    2. На основе этого условия система создает "подписку" на все изменения нужной записи
    3. При изменении записей система проверяет есть ли у нее процессы-подписчики
    4. Если такие процессы есть, система изменяет их состояние на "запущен"
    5. Далее рабочий процесс запускается и выполняет проверку шага ожидания
    6. Если условие выполнилось - подписка удаляется и процесс идет дальше. Если нет - "засыпает" снова

Ранее, я предполагал, что сбой в этом механизме может происходить если подписка "активируется" в конкурирующих дочерних процессах. В моем случае в основном процессе создавалось некое "задание" (служебная запись), жизненным циклом которого управлял другой процесс. Так было сделано потому что выполнение задания происходит в интегрированной системе и может занимать продолжительное время. Поэтому логика задания была вынесена в отдельный под-процесс, который запускается при создании задания, а основной процесс ожидает, когда задание будет выполнено - изменится его состояние. Можно предположить, что, если операции в задании выполняются быстро, и его статус быстро меняется по цепочке "ожидание-выполнение-выполнено", основной процесс может сработать и прочитать промежуточное состояние записи и поэтому не узнает, что условие было выполнено. Однако, предположение уже не кажется таким уж верным, если разобрать работу механизма в деталях...

Немного истории. Увы, не могу вспомнить какие-либо официальные источники об архитектуре рабочих процессов и асинхронного сервиса в целом. Мне встречалась только одна статья, более или менее раскрывающая данный вопрос: https://blogs.msdn.microsoft.com/crm/2009/03/25/when-do-asynchronous-jobs-fail-suspend-or-retry/, но, к сожалению, она раскрывает не все детали, или устарела (2009 год!!!). Все что мы можем знать, мы знаем на основе догадок и понимания технологий, которые лежат в основе системы.

Мы знаем, что определенные операции в системе порождают записи в таблице AsyncOperationBase. Атрибут OperationType определяет тип операции: рабочий процесс, плагин,  или что-то еще. Серверы системы, где развернута роль асинхронного сервиса, каким-то образом мониторят список заданий и распределяют их между собой для выполнения. Далее каким-то образом запускаются механизмы Windows Workflow Foundation, которые инстанцируют тело процесса из его определения в XAML (которое хранится в таблице WorkflowBase), каким-то образом инициализируют его состояние (которое хранится в атрибутах Data и WorkflowState таблицы AsyncOperationBase), после чего Фреймворк выполняет шаги процесса.

Так как все-таки работают ожидающие процессы? Чтобы ответить на этот вопрос, мне пришлось сесть с включенным SQL Profiler и разобраться. Очевидно, я не претендую на истину последней инстанции, так мой реверс инжиниринг был вполне посредственным и затрагивал только протекание конкретного процесса с конкретным условием. Как бы то ни было, мне стало немного понятнее как работает система, и, надеюсь, будет полезно кому-то еще.

Предположим, есть процесс, который стартует после создания Сделки, создает Задачу и ждет пока она не будет выполнена. Что будет происходить в системе, когда пользователь создаст Сделку?

Шаг 1. Запуск задания процесса. Каким-то образом система понимает, что у события есть обработчик и создает соответствующую запись в AsyncOperationBase. Исследование этого механизма не было моей основной задачей, так что опустим подробности как именно это делается.

Далее, спустя некоторое время, асинхронный сервис поймет, что есть новый процесс и начнет его выполнять его шаги - создает задачу.

Шаг 2. Создание подписки. В какой-то момент, процесс доходит до шага - ожидания и проверяет условие. Так как условие еще не выполнено, создается запись в таблице WorkflowWaitSubscriptionBase - это и есть наша подписка. Запись содержит:
    • Ссылку на асинхронную операцию в AsyncOperationBase
    • Идентификатор и тип записи которую "ждет" процесс - в нашем случае task.
    • Имена полей через запятую, изменения которых "ждет" процесс - в нашем случае statecode
    • Служебные атрибуты

Важный момент: подписка связывает конкретную запись в базе с инстансом процесса (заданием). В этом ее отличие, скажем, от "Шагов плагина" (Plugin Steps), которые определяют только условия запуска.

Шаг 3. Ожидание. Пока пользователь не выполнил задачу процесс находятся в состоянии "Отложен навечно". В этот момент он находится в состоянии Suspended, с датой PostponeUntil 31.12.9999. В этом состоянии он полностью игнорируется асинхронным сервисом.

Шаг 4. Срабатывание подписки. Предположим, что пользователь изменил запись, на которую был подписан процесс. В нашем случае, пусть он принял задачу в работу (еще не выполнил). При этом веб приложение делает запрос вида:

exec sp_executesql N'select distinct AsyncOperationId, WaitOnAttributeList from WorkflowWaitSubscriptionBase where EntityId = @entityId and EntityName = @entityName'
Судя по всему, эта проверка выполняется при любом изменении записи, а не только нужных полей, так как заранее не известно сколько процессов ждет чего-то от этой записи. 

Шаг 5. Возобновление процесса. Если система понимает, что совпадение найдено - есть процесс, который был подписан на изменение поля statecode, то веб-приложение делает запрос вида:

exec sp_executesql N'update WorkflowWaitSubscriptionBase
set IsModified = 1, ModifiedOn = @modifiedOn
where EntityId = @entityId
and EntityName = @entityName'
В ту же микросекунду веб-приложением выполняется второй запрос:

exec sp_executesql N'update AsyncOperationBase 
    set
        StateCode = @readyState,
        StatusCode = @readyStatus,
        ModifiedOn = @modifiedOn,
        ModifiedBy = @modifiedBy
    where
        StateCode = @suspendedState
    and AsyncOperationId in ( <AsyncOperationId> )'
С параметрами @readyState=0,@readyStatus=0. Эти состояния соответствуют запущенному процессу. Данный запрос эквивалентен нажатию на кнопку "Возобновить" на форме задания. 

Забегая вперед, скажу, что не встречал в логе запросов, которые выбирали бы записи таблицы WorkflowWaitSubscriptionBase у которых был бы установлен признак IsModified, да и вообще хоть как-то использовали этот признак. Возможно, это какая дополнительная страховка, но если и так, она не помогает в моем случае.

Обращаю внимание на то, что все что происходило в процессе до этого шага - происходило в веб приложении. Асинхронный сервис мог вообще быть отключен и это бы никак не повлияло на работу механизма на данном этапе. Этот сценарий я проверил уже без профайлера. Я отключил единственный асинхронный сервис, обновил состояние записи, после чего снова запустил сервис. Условие выполнилось и процесс пошел дальше.

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

Шаг 6. Что-то страшное. Далее начинает свою работу асинхронный сервис. Процесс интересуется своим состоянием (не знаю, чем WorkflowState отличается от Data) при помощи запроса:

exec sp_executesql N'select WorkflowState
from AsyncOperationBase
where
    AsyncOperationId = @id'
Далее какой-то запрос вычитывает все поля задачи. Возможно я плохо искал, но я не нашел запроса в нужном временном интервале, который выбирал бы только ее  состояние. Видимо это такая оптимизация, чтобы лишний раз потом ничего не вычитывать. Второй вариант: я что-то упустил, так как искал не в изолированной среде, а в производственной, где лог рябит от параллельных процессов на той же сущности.

Теперь, когда у системы есть данные о текущем статусе задачи, она первым делом… удаляет все подписки для текущего процесса!

exec sp_executesql N'delete WorkflowWaitSubscriptionBase from WorkflowWaitSubscriptionBase with(index(ndx_CascadeRelationship_Asyncoperation_workflowwaitsubscription)) where AsyncOperationId=@workflowId'
После чего ищет подписку (через 17 микросекунд, возможно это погрешность профайлера и она, все же сперва ее ищет, а потом удаляет)

exec sp_executesql N'select WaitOnAttributeList from WorkflowWaitSubscriptionBase where 
                            EntityName = @entityName and
                            EntityId = @entityId and 
                            AsyncOperationId = @asyncOperationId'
После чего снова создает, на случай если все же удалила:

exec sp_executesql N'if (select count(*) from WorkflowWaitSubscriptionBase where 
                            EntityName = @entityName and
                            EntityId = @entityId and
                            AsyncOperationId = @asyncOperationId) = 0
                        begin
                            insert into WorkflowWaitSubscriptionBase (WorkflowWaitSubscriptionId, AsyncOperationId, EntityName, EntityId, IsModified, ModifiedOn, Data, WaitOnAttributeList)
                            values (@workflowWaitSubscriptionId, @asyncOperationId, @entityName, @entityId, @isModified, @modifiedOn, @data, @waitOnAttributeList)
                        end'
После чего снова ищет:

exec sp_executesql N'select WaitOnAttributeList from WorkflowWaitSubscriptionBase where 
                            EntityName = @entityName and
                            EntityId = @entityId and 
                            AsyncOperationId = @asyncOperationId'
Потом снова обновляет подписку, но на этот раз устанавливает WaitOnAttributeList в то же значение "statecode":

exec sp_executesql N'update WorkflowWaitSubscriptionBase
                        set WaitOnAttributeList = @waitOnAttributeList
                        where EntityId = @entityId and 
                        EntityName = @entityName and 
                        AsyncOperationId = @asyncOperationId'
Шаг 7. Засыпание процесса. Где-то на прошлом этапе процесс понял, что его разбудили зря - пользователь так и не выполнил задачу. Он отработал, эффективно управлял подписками, после чего с чувством выполненного долга засыпает :

exec sp_executesql N'update AsyncOperationBase 
set 
StateCode = @newState
, StatusCode = @newStatus
, ErrorCode = @errorCode
, Message = @errorMessage
, FriendlyMessage = NULL
, PostponeUntil = @postponeUntil
, ExecutionTimeSpan =  @executionTimeSpan
, ModifiedOn = @modifiedOn
, ModifiedBy = CreatedBy
 where AsyncOperationId = @id
В этот момент он возвращается в состояние ожидания, где находится в состоянии Suspended, с датой возвращения PostponeUntil 31.12.9999.

После чего обновляет свой WorkflowState и, на всякий случай убеждается что заснул:

exec sp_executesql N'update AsyncOperationBase
set
    PostponeUntil = @postponeUntil,    
    WorkflowState = @workflowState,
    WorkflowIsBlocked = @workflowIsBlocked,
    ModifiedOn = @modifiedOn,
    ModifiedBy = CreatedBy
where
    AsyncOperationId = @id'Занавес.

Что мне таки и не удалось из всего этого понять, так это почему все-таки виснут эти блядские процессы! Судя по всему, система устроена таким образом начиная с версии CRM 2011. Это первая из известных мне версий, где появилась таблица WorkflowWaitSubscriptionBase. В свое время для нее даже выходил экстренный патч, который позже был включен в UR13: https://support.microsoft.com/en-us/help/2918320. Судя по описанию, это, как раз мой случай, но увы, проблему, похоже, полностью не исправили.

Давайте теперь выстроим некоторые тезисы:
    1. Процесс стоит, условие выполнено. Если пнуть его ногой, он поймет, что условие выполнено и пойдет дальше. Следовательно, он не сломался - он просто не обработал изменения, или не был запущен
    2. Если судить по запросам, блокировка записи на изменение делается в x.010 (десять тысячных секунды), запись в таблицу аудита - в x.013, а поиск, обновление подписки и запуск процесса - в x.017. Будем считать, что все это происходит в транзакции. Следовательно, процесс обязан был быть запущен
    3. У зависших процессов нет модифицированных подписок - только те что по дате обновления совпадают с последним временем запуска процесса. Так как активности процесса должны выполняться в транзакции, обновление флага IsModified должно быть блокировано в конкурирующем обработчике до того момента пока транзакция не будет отпущена
    4. Промежуток времени между удалением и повторным созданием подписок - 20 микросекунд. Между изменением записи и запуском самого процесса - 3,5 секунды.

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

Какие уроки можно извлечь из всего этого? Урок первый: не нужно боятся ожидающих процессов. Судя по всему, они совершенно безобидны и не могут вызвать существенных проблем с производительностью: 1-2 дополнительных запроса - это не тот ужас, с полным отказом сервиса которым меня пугали. Урок второй: нужно избегать конкурирующих изменений полей, которые используются в условиях ожидания. Совсем не факт, но, возможно, это поможет избежать зависания процессов.]]></description>
			<content:encoded><![CDATA[<div>Недавно, мы с коллегами столкнулись с проблемой зависания ожидающего процесса. Его условие уже было выполнено, но процесс стоял на месте. Выяснилось, что перезагрузка процесса решает проблему и он идет дальше, в то время как перезагрузка асинхронного сервиса ее не решает - процессы продолжают висеть. Это показалось странным, так как нам говорили, что ожидающие процессы - это плохо, именно потому что асинхронный сервис не знает, когда выполнятся их условия, и вынужден проверять их каждый раз, когда запускается служба. Возможно так оно и было, но готов с уверенностью заявить, что теперь это точно не так.<br />
<br />
Как выяснилось, ожидающие процессы тоже управляются событиями! Детали этого механизма будут раскрыты дальше в статье, однако, на случай если вам жалко времени, изложу его сперва кратко:<br />
    1. Шаг ожидания содержит в себе условие, при выполнении которого процесс должен пойти дальше<br />
    2. На основе этого условия система создает &quot;подписку&quot; на все изменения нужной записи<br />
    3. При изменении записей система проверяет есть ли у нее процессы-подписчики<br />
    4. Если такие процессы есть, система изменяет их состояние на &quot;запущен&quot;<br />
    5. Далее рабочий процесс запускается и выполняет проверку шага ожидания<br />
    6. Если условие выполнилось - подписка удаляется и процесс идет дальше. Если нет - &quot;засыпает&quot; снова<br />
<br />
Ранее, я предполагал, что сбой в этом механизме может происходить если подписка &quot;активируется&quot; в конкурирующих дочерних процессах. В моем случае в основном процессе создавалось некое &quot;задание&quot; (служебная запись), жизненным циклом которого управлял другой процесс. Так было сделано потому что выполнение задания происходит в интегрированной системе и может занимать продолжительное время. Поэтому логика задания была вынесена в отдельный под-процесс, который запускается при создании задания, а основной процесс ожидает, когда задание будет выполнено - изменится его состояние. Можно предположить, что, если операции в задании выполняются быстро, и его статус быстро меняется по цепочке &quot;ожидание-выполнение-выполнено&quot;, основной процесс может сработать и прочитать промежуточное состояние записи и поэтому не узнает, что условие было выполнено. Однако, предположение уже не кажется таким уж верным, если разобрать работу механизма в деталях...<br />
<br />
Немного истории. Увы, не могу вспомнить какие-либо официальные источники об архитектуре рабочих процессов и асинхронного сервиса в целом. Мне встречалась только одна статья, более или менее раскрывающая данный вопрос: <a href="https://blogs.msdn.microsoft.com/crm/2009/03/25/when-do-asynchronous-jobs-fail-suspend-or-retry/" target="_blank">https://blogs.msdn.microsoft.com/crm...pend-or-retry/</a>, но, к сожалению, она раскрывает не все детали, или устарела (2009 год!!!). Все что мы можем знать, мы знаем на основе догадок и понимания технологий, которые лежат в основе системы.<br />
<br />
Мы знаем, что определенные операции в системе порождают записи в таблице AsyncOperationBase. Атрибут OperationType определяет тип операции: рабочий процесс, плагин,  или что-то еще. Серверы системы, где развернута роль асинхронного сервиса, каким-то образом мониторят список заданий и распределяют их между собой для выполнения. Далее каким-то образом запускаются механизмы Windows Workflow Foundation, которые инстанцируют тело процесса из его определения в XAML (которое хранится в таблице WorkflowBase), каким-то образом инициализируют его состояние (которое хранится в атрибутах Data и WorkflowState таблицы AsyncOperationBase), после чего Фреймворк выполняет шаги процесса.<br />
<br />
Так как все-таки работают ожидающие процессы? Чтобы ответить на этот вопрос, мне пришлось сесть с включенным SQL Profiler и разобраться. Очевидно, я не претендую на истину последней инстанции, так мой реверс инжиниринг был вполне посредственным и затрагивал только протекание конкретного процесса с конкретным условием. Как бы то ни было, мне стало немного понятнее как работает система, и, надеюсь, будет полезно кому-то еще.<br />
<br />
Предположим, есть процесс, который стартует после создания Сделки, создает Задачу и ждет пока она не будет выполнена. Что будет происходить в системе, когда пользователь создаст Сделку?<br />
<br />
Шаг 1. Запуск задания процесса. Каким-то образом система понимает, что у события есть обработчик и создает соответствующую запись в AsyncOperationBase. Исследование этого механизма не было моей основной задачей, так что опустим подробности как именно это делается.<br />
<br />
Далее, спустя некоторое время, асинхронный сервис поймет, что есть новый процесс и начнет его выполнять его шаги - создает задачу.<br />
<br />
Шаг 2. Создание подписки. В какой-то момент, процесс доходит до шага - ожидания и проверяет условие. Так как условие еще не выполнено, создается запись в таблице WorkflowWaitSubscriptionBase - это и есть наша подписка. Запись содержит:<br />
    • Ссылку на асинхронную операцию в AsyncOperationBase<br />
    • Идентификатор и тип записи которую &quot;ждет&quot; процесс - в нашем случае task.<br />
    • Имена полей через запятую, изменения которых &quot;ждет&quot; процесс - в нашем случае statecode<br />
    • Служебные атрибуты<br />
<br />
Важный момент: подписка связывает конкретную запись в базе с инстансом процесса (заданием). В этом ее отличие, скажем, от &quot;Шагов плагина&quot; (Plugin Steps), которые определяют только условия запуска.<br />
<br />
Шаг 3. Ожидание. Пока пользователь не выполнил задачу процесс находятся в состоянии &quot;Отложен навечно&quot;. В этот момент он находится в состоянии Suspended, с датой PostponeUntil 31.12.9999. В этом состоянии он полностью игнорируется асинхронным сервисом.<br />
<br />
Шаг 4. Срабатывание подписки. Предположим, что пользователь изменил запись, на которую был подписан процесс. В нашем случае, пусть он принял задачу в работу (еще не выполнил). При этом веб приложение делает запрос вида:<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">exec sp_executesql N<span style="color: red">'select distinct AsyncOperationId, WaitOnAttributeList from WorkflowWaitSubscriptionBase where EntityId = @entityId and EntityName = @entityName'</span></pre></div>Судя по всему, эта проверка выполняется при любом изменении записи, а не только нужных полей, так как заранее не известно сколько процессов ждет чего-то от этой записи. <br />
<br />
Шаг 5. Возобновление процесса. Если система понимает, что совпадение найдено - есть процесс, который был подписан на изменение поля statecode, то веб-приложение делает запрос вида:<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">exec sp_executesql N<span style="color: red">'update WorkflowWaitSubscriptionBase
set IsModified = 1, ModifiedOn = @modifiedOn
where EntityId = @entityId
and EntityName = @entityName'</span></pre></div>В ту же микросекунду веб-приложением выполняется второй запрос:<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">exec sp_executesql N<span style="color: red">'update AsyncOperationBase 
    set
        StateCode = @readyState,
        StatusCode = @readyStatus,
        ModifiedOn = @modifiedOn,
        ModifiedBy = @modifiedBy
    where
        StateCode = @suspendedState
    and AsyncOperationId in ( &lt;AsyncOperationId&gt; )'</span></pre></div>С параметрами @readyState=0,@readyStatus=0. Эти состояния соответствуют запущенному процессу. Данный запрос эквивалентен нажатию на кнопку &quot;Возобновить&quot; на форме задания. <br />
<br />
Забегая вперед, скажу, что не встречал в логе запросов, которые выбирали бы записи таблицы WorkflowWaitSubscriptionBase у которых был бы установлен признак IsModified, да и вообще хоть как-то использовали этот признак. Возможно, это какая дополнительная страховка, но если и так, она не помогает в моем случае.<br />
<br />
Обращаю внимание на то, что все что происходило в процессе до этого шага - происходило в веб приложении. Асинхронный сервис мог вообще быть отключен и это бы никак не повлияло на работу механизма на данном этапе. Этот сценарий я проверил уже без профайлера. Я отключил единственный асинхронный сервис, обновил состояние записи, после чего снова запустил сервис. Условие выполнилось и процесс пошел дальше.<br />
<br />
Важный момент! То, что все выше перечисленное происходило в контексте веб приложения, справедливо только для текущего примера, так как предполагается, что для создания записей использовалось веб-приложение. В общем случае, интересующая нас запись могла быть создана в другом бизнес-процессе. В этом случае, система проделала бы те же самые операции, но уже в контексте асинхронного сервиса.<br />
<br />
Шаг 6. Что-то страшное. Далее начинает свою работу асинхронный сервис. Процесс интересуется своим состоянием (не знаю, чем WorkflowState отличается от Data) при помощи запроса:<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">exec sp_executesql N<span style="color: red">'select WorkflowState
from AsyncOperationBase
where
    AsyncOperationId = @id'</span></pre></div>Далее какой-то запрос вычитывает все поля задачи. Возможно я плохо искал, но я не нашел запроса в нужном временном интервале, который выбирал бы только ее  состояние. Видимо это такая оптимизация, чтобы лишний раз потом ничего не вычитывать. Второй вариант: я что-то упустил, так как искал не в изолированной среде, а в производственной, где лог рябит от параллельных процессов на той же сущности.<br />
<br />
Теперь, когда у системы есть данные о текущем статусе задачи, она первым делом… удаляет все подписки для текущего процесса!<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">exec sp_executesql N<span style="color: red">'delete WorkflowWaitSubscriptionBase from WorkflowWaitSubscriptionBase with(index(ndx_CascadeRelationship_Asyncoperation_workflowwaitsubscription)) where AsyncOperationId=@workflowId'</span></pre></div>После чего ищет подписку (через 17 микросекунд, возможно это погрешность профайлера и она, все же сперва ее ищет, а потом удаляет)<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">exec sp_executesql N<span style="color: red">'select WaitOnAttributeList from WorkflowWaitSubscriptionBase where 
                            EntityName = @entityName and
                            EntityId = @entityId and 
                            AsyncOperationId = @asyncOperationId'</span></pre></div>После чего снова создает, на случай если все же удалила:<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">exec sp_executesql N<span style="color: red">'if (select count(*) from WorkflowWaitSubscriptionBase where 
                            EntityName = @entityName and
                            EntityId = @entityId and
                            AsyncOperationId = @asyncOperationId) = 0
                        begin
                            insert into WorkflowWaitSubscriptionBase (WorkflowWaitSubscriptionId, AsyncOperationId, EntityName, EntityId, IsModified, ModifiedOn, Data, WaitOnAttributeList)
                            values (@workflowWaitSubscriptionId, @asyncOperationId, @entityName, @entityId, @isModified, @modifiedOn, @data, @waitOnAttributeList)
                        end'</span></pre></div>После чего снова ищет:<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">exec sp_executesql N<span style="color: red">'select WaitOnAttributeList from WorkflowWaitSubscriptionBase where 
                            EntityName = @entityName and
                            EntityId = @entityId and 
                            AsyncOperationId = @asyncOperationId'</span></pre></div>Потом снова обновляет подписку, но на этот раз устанавливает WaitOnAttributeList в то же значение &quot;statecode&quot;:<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">exec sp_executesql N<span style="color: red">'update WorkflowWaitSubscriptionBase
                        set WaitOnAttributeList = @waitOnAttributeList
                        where EntityId = @entityId and 
                        EntityName = @entityName and 
                        AsyncOperationId = @asyncOperationId'</span></pre></div>Шаг 7. Засыпание процесса. Где-то на прошлом этапе процесс понял, что его разбудили зря - пользователь так и не выполнил задачу. Он отработал, эффективно управлял подписками, после чего с чувством выполненного долга засыпает :<br />
<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">exec sp_executesql N<span style="color: red">'update AsyncOperationBase 
set 
StateCode = @newState
, StatusCode = @newStatus
, ErrorCode = @errorCode
, Message = @errorMessage
, FriendlyMessage = NULL
, PostponeUntil = @postponeUntil
, ExecutionTimeSpan =  @executionTimeSpan
, ModifiedOn = @modifiedOn
, ModifiedBy = CreatedBy
 where AsyncOperationId = @id</span></pre></div>В этот момент он возвращается в состояние ожидания, где находится в состоянии Suspended, с датой возвращения PostponeUntil 31.12.9999.<br />
<br />
После чего обновляет свой WorkflowState и, на всякий случай убеждается что заснул:<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">exec sp_executesql N<span style="color: red">'update AsyncOperationBase
set
    PostponeUntil = @postponeUntil,    
    WorkflowState = @workflowState,
    WorkflowIsBlocked = @workflowIsBlocked,
    ModifiedOn = @modifiedOn,
    ModifiedBy = CreatedBy
where
    AsyncOperationId = @id'</span></pre></div>Занавес.<br />
<br />
Что мне таки и не удалось из всего этого понять, так это почему все-таки виснут эти блядские процессы! Судя по всему, система устроена таким образом начиная с версии CRM 2011. Это первая из известных мне версий, где появилась таблица WorkflowWaitSubscriptionBase. В свое время для нее даже выходил экстренный патч, который позже был включен в UR13: <a href="https://support.microsoft.com/en-us/help/2918320" target="_blank">https://support.microsoft.com/en-us/help/2918320</a>. Судя по описанию, это, как раз мой случай, но увы, проблему, похоже, полностью не исправили.<br />
<br />
Давайте теперь выстроим некоторые тезисы:<br />
    1. Процесс стоит, условие выполнено. Если пнуть его ногой, он поймет, что условие выполнено и пойдет дальше. Следовательно, он не сломался - он просто не обработал изменения, или не был запущен<br />
    2. Если судить по запросам, блокировка записи на изменение делается в x.010 (десять тысячных секунды), запись в таблицу аудита - в x.013, а поиск, обновление подписки и запуск процесса - в x.017. Будем считать, что все это происходит в транзакции. Следовательно, процесс обязан был быть запущен<br />
    3. У зависших процессов нет модифицированных подписок - только те что по дате обновления совпадают с последним временем запуска процесса. Так как активности процесса должны выполняться в транзакции, обновление флага IsModified должно быть блокировано в конкурирующем обработчике до того момента пока транзакция не будет отпущена<br />
    4. Промежуток времени между удалением и повторным созданием подписок - 20 микросекунд. Между изменением записи и запуском самого процесса - 3,5 секунды.<br />
<br />
Исходя из этого, гипотеза о конкурирующих изменениях уже не выглядит такой правдоподобной. Если я правильно понимаю, как работают транзакции, тут либо были бы IsModified, подписки, или ошибки, но всего это не происходит. Более правдоподобным кажется некий глюк, который приводит к тому, что процесс вообще не запускается при каких-то условиях, и эта ошибка не отражается в журналах самих процессов. Например, произошла блокировка, или откат транзакции. Увы, мне так и не удалось поймать момент зависания с включенной трассировкой. <br />
<br />
Какие уроки можно извлечь из всего этого? Урок первый: не нужно боятся ожидающих процессов. Судя по всему, они совершенно безобидны и не могут вызвать существенных проблем с производительностью: 1-2 дополнительных запроса - это не тот ужас, с полным отказом сервиса которым меня пугали. Урок второй: нужно избегать конкурирующих изменений полей, которые используются в условиях ожидания. Совсем не факт, но, возможно, это поможет избежать зависания процессов.</div>

]]></content:encoded>
			<dc:creator>Артем Enot Грунин</dc:creator>
			<guid isPermaLink="true">//axforum.info/forums/blog.php?b=8239</guid>
		</item>
		<item>
			<title>Ошибка расчета целей при использовании вычисляемых полей</title>
			<link>//axforum.info/forums/blog.php?b=8228</link>
			<pubDate>Wed, 02 Nov 2016 07:11:46 GMT</pubDate>
			<description><![CDATA[Всем привет. Недавно обнаружил любопытнейший баг в функционале расчета Целей (Goals). В свое время цели появились в CRM 2011 как некая киллер-фича, которую все давно ждали и очень хотели. До этого они были доступны как "акселератор" для CRM 4.0, но, конечно же уступали по функционалу своей будущей версии из-за немощности функционала расширения CRM. Бытует мнение, что даже, ныне почти забытый, функционал диаграмм (Charts) был реализован исключительно с целью визуализации этих целей.

Увы, в цели все быстро наигрались: оказалось, что простая агрегация не может снять все задачи построения KPI предприятия и функционал был предан забвению. Формы Целей и сопутствующих объектов не были обновлены до версии 2013 и далее, равно как и не получил развитие сам функционал.

Но это так - история, а в чем же проблема? Проблема в том, что, судя по всему, для расчета значения метрики цели, система выгребает *все поля объекта* на котором выполняется агрегация (сведение) каждого показателя, а не только те, которые участвуют в расчете! Обычно это влияет только на производительность, но если у вашего объекта есть больше 10 вычисляемых полей, например, расчетных полей, или полей свертки, то неизбежно нарушается ограничение на количество вычисляемых полей в запросе. 

Забавно, но на форме цели эта ошибка отразится в крайне корявом (хотя поддающемся расшифровке виде).
Вложение 369 (//axforum.info/forums/attachment.php?attachmentid=369)
Если перейти к связанным записям в области навигации, ошибка будет более читаема
Вложение 370 (//axforum.info/forums/attachment.php?attachmentid=370)]]></description>
			<content:encoded><![CDATA[<div>Всем привет. Недавно обнаружил любопытнейший баг в функционале расчета Целей (Goals). В свое время цели появились в CRM 2011 как некая киллер-фича, которую все давно ждали и очень хотели. До этого они были доступны как &quot;акселератор&quot; для CRM 4.0, но, конечно же уступали по функционалу своей будущей версии из-за немощности функционала расширения CRM. Бытует мнение, что даже, ныне почти забытый, функционал диаграмм (Charts) был реализован исключительно с целью визуализации этих целей.<br />
<br />
Увы, в цели все быстро наигрались: оказалось, что простая агрегация не может снять все задачи построения KPI предприятия и функционал был предан забвению. Формы Целей и сопутствующих объектов не были обновлены до версии 2013 и далее, равно как и не получил развитие сам функционал.<br />
<br />
Но это так - история, а в чем же проблема? Проблема в том, что, судя по всему, для расчета значения метрики цели, система выгребает <b>все поля объекта</b> на котором выполняется агрегация (сведение) каждого показателя, а не только те, которые участвуют в расчете! Обычно это влияет только на производительность, но если у вашего объекта есть больше 10 вычисляемых полей, например, расчетных полей, или полей свертки, то неизбежно нарушается ограничение на количество вычисляемых полей в запросе. <br />
<br />
Забавно, но на форме цели эта ошибка отразится в крайне корявом (хотя поддающемся расшифровке виде).<br />
<a href="//axforum.info/forums/blog_attachment.php?attachmentid=369&amp;d=1478070658" rel="Lightbox" id="attachment369" ><img src="//axforum.info/forums/blog_attachment.php?attachmentid=369&amp;thumb=1&amp;d=1478070658" class="thumbnail" border="0" alt="Нажмите на изображение для увеличения
Название: ^741B195FF122E0FBE980212537BD0BE292BD6DD5605B94AA27^pimgpsh_mobile_save_distr.jpg
Просмотров: 4817
Размер:	91.7 Кб
ID:	369" style="margin: 2px" /></a><br />
Если перейти к связанным записям в области навигации, ошибка будет более читаема<br />
<a href="//axforum.info/forums/blog_attachment.php?attachmentid=370&amp;d=1478070658" rel="Lightbox" id="attachment370" ><img src="//axforum.info/forums/blog_attachment.php?attachmentid=370&amp;thumb=1&amp;d=1478070658" class="thumbnail" border="0" alt="Нажмите на изображение для увеличения
Название: ^5BB12085E7C18DC27D39ED6425A0A15A63EA6A71F6F0C52E85^pimgpsh_mobile_save_distr.jpg
Просмотров: 4971
Размер:	15.5 Кб
ID:	370" style="margin: 2px" /></a></div>

]]></content:encoded>
			<dc:creator>Артем Enot Грунин</dc:creator>
			<guid isPermaLink="true">//axforum.info/forums/blog.php?b=8228</guid>
		</item>
		<item>
			<title>Особенности выборки по Id и ObjectId в OrganizationSericeContext</title>
			<link>//axforum.info/forums/blog.php?b=8221</link>
			<pubDate>Fri, 28 Oct 2016 06:39:00 GMT</pubDate>
			<description><![CDATA[Недавно я (впрочем не я первый) обнаружил интересный баг при работе с "контекстом" в стандартной клиентской сборке CRM. Представители "старой школы", такие как я, не привыкли пользоваться подобными инструментами, однако использование LINQ синтаксиса запросов несомненно востребовано современными программистами.

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

                using (OrganizationServiceContext context = new OrganizationServiceContext(proxy))
                {
                    var account = context.CreateQuery<Account>()
                        .Where(a => a.Name != null)
                        .Select(a => new Account() { Id = a.Id }).FirstOrDefault();
 
                    Console.Out.WriteLine("Id = a.Id {0}", account.Attributes.Count);
                }
Сюрпризом будет вывод программы. Было вычитано 57 атрибутов! Иными словами, все атрибуты, а не только те что я заказывал.

При использовании классического подхода к выборке данных из системы посредством QueryBase, мы в обязательном порядке должны указать ожидаемый набор атрибутов в CollumnSet. Тем не менее, система имеет свои представления касательно того что будет возвращено клиенту. В наборе атрибутов каждой запрошенной записи, в обязательном порядке будет ее идентификатор (и ряд других системных полей), например, *accountid*, но не будет всех полей, значения которых оказались пустыми. Важно понимать, что OrganizationSericeContext не создает под собой чего-то принципиально нового: наш LINQ запрос будет транслирован LINQ-провайдером в стандартный запрос-наследник QueryBase (по слухам генерируется Fetch-запрос). Однако, он, почему-то генерирует не пустой CollumnSet а CollumnSet(true), который вынужден вернуть все атрибуты.

Идем дальше! Давайте попробуем использовать немного другой запрос:

                using (OrganizationServiceContext context = new OrganizationServiceContext(proxy))
                {
                    var account = context.CreateQuery<Account>()
                        .Where(a => a.Name != null)
                        .Select(a => new Account() { AccountId = AccountId }).FirstOrDefault();
 
                    Console.Out.WriteLine("Id = a.AccountId.Value {0}", account.Attributes.Count);
                }
В результате мы получим 1 атрибут, как и договаривались. Для генерации классов раннего связывания использовалась стандартная утилита CrmSvcUtil 7.1.0001.3108 (4.0.30319.42000 – версия указанная в начале файла с кодом). При этом, если мы посмотрим на определение свойств *AccountId* и *Id* в генерированном коде, мы не увидим значимых отличий: 
*accountid*

[Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("accountid")]
public System.Nullable<System.Guid> AccountId

[Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("accountid")]
public override System.Guid Id
Оба свойства маппированы на один и тот же атрибут, следовательно должны транслироваться в одинаковые запросы при преобразовании лямбда выражения в Fetch запрос. Тем не менее, разница на лицо.

Попробовав все возможные комбинации *Id *и *AccountId* в запросе, мы получим следующий вывод программы:


AccountId = a.Id 57
Id = a.Id 57
Id = a.AccountId.Value 1
AccountId = a.AccountId 1
Иными словами, важно чтобы в правой части выражения всегда был *AccountId*.

Мне не удалось получить какой-либо внятный ответ от производственной группы посредством возможностей MVP канала. Однако выяснилось, что проблема известная, хотя и не критическая: http://crmtipoftheday.com/2014/09/09/bad-id-bad/.

Второй интересный момент. Вы всегда должны заказывать AccountId в выражении *select*, иначе это свойство не будет инициализировано в записи Account. Технически, его значение будет где-то там - внутри контекста, но использовать его вы не сможете.

Предупрежден - значит вооружен. Не жалейте буквы и пишите полные названия идентификаторов!]]></description>
			<content:encoded><![CDATA[<div>Недавно я (впрочем не я первый) обнаружил интересный баг при работе с &quot;контекстом&quot; в стандартной клиентской сборке CRM. Представители &quot;старой школы&quot;, такие как я, не привыкли пользоваться подобными инструментами, однако использование LINQ синтаксиса запросов несомненно востребовано современными программистами.<br />
<br />
В чем, собственно суть? Предположим, вы хотите получить <b>только</b> идентификаторы записей, которые удовлетворяют некоторому условию. Упрощенно, ваш запрос будет выглядеть примерно вот так<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">                using (OrganizationServiceContext context = <span style="color: blue">new</span> OrganizationServiceContext(proxy))
                {
                    var account = context.CreateQuery&lt;Account&gt;()
                        .<span style="color: blue">Where</span>(a =&gt; a.Name != <span style="color: blue">null</span>)
                        .<span style="color: blue">Select</span>(a =&gt; <span style="color: blue">new</span> Account() { Id = a.Id }).FirstOrDefault();
 
                    Console.Out.WriteLine(<span style="color: red">&quot;Id = a.Id {0}&quot;</span>, account.Attributes.<span style="color: blue">Count</span>);
                }</pre></div>Сюрпризом будет вывод программы. Было вычитано 57 атрибутов! Иными словами, все атрибуты, а не только те что я заказывал.<br />
<br />
При использовании классического подхода к выборке данных из системы посредством QueryBase, мы в обязательном порядке должны указать ожидаемый набор атрибутов в CollumnSet. Тем не менее, система имеет свои представления касательно того что будет возвращено клиенту. В наборе атрибутов каждой запрошенной записи, в обязательном порядке будет ее идентификатор (и ряд других системных полей), например, <b>accountid</b>, но не будет всех полей, значения которых оказались пустыми. Важно понимать, что OrganizationSericeContext не создает под собой чего-то принципиально нового: наш LINQ запрос будет транслирован LINQ-провайдером в стандартный запрос-наследник QueryBase (по слухам генерируется Fetch-запрос). Однако, он, почему-то генерирует не пустой CollumnSet а CollumnSet(true), который вынужден вернуть все атрибуты.<br />
<br />
Идем дальше! Давайте попробуем использовать немного другой запрос:<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">                using (OrganizationServiceContext context = <span style="color: blue">new</span> OrganizationServiceContext(proxy))
                {
                    var account = context.CreateQuery&lt;Account&gt;()
                        .<span style="color: blue">Where</span>(a =&gt; a.Name != <span style="color: blue">null</span>)
                        .<span style="color: blue">Select</span>(a =&gt; <span style="color: blue">new</span> Account() { AccountId = AccountId }).FirstOrDefault();
 
                    Console.Out.WriteLine(<span style="color: red">&quot;Id = a.AccountId.Value {0}&quot;</span>, account.Attributes.<span style="color: blue">Count</span>);
                }</pre></div>В результате мы получим 1 атрибут, как и договаривались. Для генерации классов раннего связывания использовалась стандартная утилита CrmSvcUtil 7.1.0001.3108 (4.0.30319.42000 – версия указанная в начале файла с кодом). При этом, если мы посмотрим на определение свойств <b>AccountId</b> и <b>Id</b> в генерированном коде, мы не увидим значимых отличий: <br />
<b>accountid</b><br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">[Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute(<span style="color: red">&quot;accountid&quot;</span>)]
<span style="color: blue">public</span> System.Nullable&lt;System.Guid&gt; AccountId

[Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute(<span style="color: red">&quot;accountid&quot;</span>)]
<span style="color: blue">public</span> override System.Guid Id</pre></div>Оба свойства маппированы на один и тот же атрибут, следовательно должны транслироваться в одинаковые запросы при преобразовании лямбда выражения в Fetch запрос. Тем не менее, разница на лицо.<br />
<br />
Попробовав все возможные комбинации <b>Id </b>и <b>AccountId</b> в запросе, мы получим следующий вывод программы:<br />
<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">AccountId = a.Id 57
Id = a.Id 57
Id = a.AccountId.Value 1
AccountId = a.AccountId 1</pre></div>Иными словами, важно чтобы в правой части выражения всегда был <b>AccountId</b>.<br />
<br />
Мне не удалось получить какой-либо внятный ответ от производственной группы посредством возможностей MVP канала. Однако выяснилось, что проблема известная, хотя и не критическая: <a href="http://crmtipoftheday.com/2014/09/09/bad-id-bad/" target="_blank">http://crmtipoftheday.com/2014/09/09/bad-id-bad/</a>.<br />
<br />
Второй интересный момент. Вы всегда должны заказывать AccountId в выражении <b>select</b>, иначе это свойство не будет инициализировано в записи Account. Технически, его значение будет где-то там - внутри контекста, но использовать его вы не сможете.<br />
<br />
Предупрежден - значит вооружен. Не жалейте буквы и пишите полные названия идентификаторов!</div>

]]></content:encoded>
			<dc:creator>Артем Enot Грунин</dc:creator>
			<guid isPermaLink="true">//axforum.info/forums/blog.php?b=8221</guid>
		</item>
		<item>
			<title>MVP 5 лет</title>
			<link>//axforum.info/forums/blog.php?b=8220</link>
			<pubDate>Thu, 27 Oct 2016 05:04:17 GMT</pubDate>
			<description><![CDATA[Череда ошибок, бездействия и раздолбайства пару месяцев назад привели меня к получению юбилейной пятой "шайбы" на статуэтке. В последние годы моя активность на ресурсе несомненно падает. Виной тому постоянная занятость и меньшая вовлеченность в исследование новых возможностей системы. Кроме того, с большинством из вас мы либо работаем вместе, либо уже давно перешли на неформальное общение в других ресурсах. Тем не менее, я по-прежнему горд быть частью CRM сообщества и носить звание MVP. Спасибо вам всем за поддержку и ваши вопросы, без которых я вряд ли смог бы утолить свои аппетиты в исследовании нового.

Вложение 368 (//axforum.info/forums/attachment.php?attachmentid=368)]]></description>
			<content:encoded><![CDATA[<div>Череда ошибок, бездействия и раздолбайства пару месяцев назад привели меня к получению юбилейной пятой &quot;шайбы&quot; на статуэтке. В последние годы моя активность на ресурсе несомненно падает. Виной тому постоянная занятость и меньшая вовлеченность в исследование новых возможностей системы. Кроме того, с большинством из вас мы либо работаем вместе, либо уже давно перешли на неформальное общение в других ресурсах. Тем не менее, я по-прежнему горд быть частью CRM сообщества и носить звание MVP. Спасибо вам всем за поддержку и ваши вопросы, без которых я вряд ли смог бы утолить свои аппетиты в исследовании нового.<br />
<br />
<a href="//axforum.info/forums/blog_attachment.php?attachmentid=368&amp;d=1477544619" rel="Lightbox" id="attachment368" ><img src="//axforum.info/forums/blog_attachment.php?attachmentid=368&amp;thumb=1&amp;d=1477544619" class="thumbnail" border="0" alt="Нажмите на изображение для увеличения
Название: WP_20161003_22_56_45_Pro.jpg
Просмотров: 1174
Размер:	287.8 Кб
ID:	368" style="margin: 2px" /></a></div>

]]></content:encoded>
			<dc:creator>Артем Enot Грунин</dc:creator>
			<guid isPermaLink="true">//axforum.info/forums/blog.php?b=8220</guid>
		</item>
		<item>
			<title>Право на добавление пользователя в группу</title>
			<link>//axforum.info/forums/blog.php?b=8171</link>
			<pubDate>Thu, 29 Jan 2015 11:22:58 GMT</pubDate>
			<description><![CDATA[Недавно я столкнулся со странным ограничением системы по правам доступа. Для выполнения различных функций бизнес-администрирования, мной была создана специальная роль "Бизнес администратор", которой были предоставлены права выполнять все операции с пользовательскими объектами, но без возможности настройки системы. Выяснилось, что эта роль не может управлять членством в группах (*team*) в CRM. Судя по MSDN (https://msdn.microsoft.com/en-us/library/gg328573.aspx) для вызова сообщения *AddMembersTeam *достаточно привилегии на изменение группы *prvWriteTeam*, которая была у данного пользователя. Отладка показала, что кроме этого, пользователю дополнительно нужны:

* ВСЕ привилегии объекта Процесс (*WorkFlow*)
* ВСЕ привилегии объекта Сеанс диалогового окна (*WorkFlowSession*, так же встречается как "ProcessSession" и "DialogSession")
* Чтение (*Read*), Запись (*Write*), "Присоединение к" (*AppendTo*) и Назначение (*Assign*) для объекта Системное задание (*AsyncOperation*)

Уровень доступа (глубина привилегий) должна быть достаточной для выполнения операции в подразделении к которому принадлежит группа безопасности.

Другой удивительной особенностью является, отсутствие возможности гибко настроить доступ группе, или поделиться правами (*share*) c кем-то из пользователей. Какие функции выполняют в данной операции системные процессы, и почему они выполняются под пользователем, остается только догадываться.]]></description>
			<content:encoded><![CDATA[<div>Недавно я столкнулся со странным ограничением системы по правам доступа. Для выполнения различных функций бизнес-администрирования, мной была создана специальная роль &quot;Бизнес администратор&quot;, которой были предоставлены права выполнять все операции с пользовательскими объектами, но без возможности настройки системы. Выяснилось, что эта роль не может управлять членством в группах (<b>team</b>) в CRM. Судя по <a href="https://msdn.microsoft.com/en-us/library/gg328573.aspx" target="_blank">MSDN</a> для вызова сообщения <b>AddMembersTeam </b>достаточно привилегии на изменение группы <b>prvWriteTeam</b>, которая была у данного пользователя. Отладка показала, что кроме этого, пользователю дополнительно нужны:<br />
<ul><li>ВСЕ привилегии объекта Процесс (<b>WorkFlow</b>)</li>
<li>ВСЕ привилегии объекта Сеанс диалогового окна (<b>WorkFlowSession</b>, так же встречается как &quot;ProcessSession&quot; и &quot;DialogSession&quot;)</li>
<li>Чтение (<b>Read</b>), Запись (<b>Write</b>), &quot;Присоединение к&quot; (<b>AppendTo</b>) и Назначение (<b>Assign</b>) для объекта Системное задание (<b>AsyncOperation</b>)</li>
</ul>Уровень доступа (глубина привилегий) должна быть достаточной для выполнения операции в подразделении к которому принадлежит группа безопасности.<br />
<br />
Другой удивительной особенностью является, отсутствие возможности гибко настроить доступ группе, или поделиться правами (<b>share</b>) c кем-то из пользователей. Какие функции выполняют в данной операции системные процессы, и почему они выполняются под пользователем, остается только догадываться.</div>

]]></content:encoded>
			<dc:creator>Артем Enot Грунин</dc:creator>
			<guid isPermaLink="true">//axforum.info/forums/blog.php?b=8171</guid>
		</item>
		<item>
			<title>Управление подписками на отчеты SSRS в режиме интеграции с SharePoint</title>
			<link>//axforum.info/forums/blog.php?b=8146</link>
			<pubDate>Wed, 13 Aug 2014 06:47:21 GMT</pubDate>
			<description><![CDATA[Сегодня выяснилось, что одна из наших систем отправляет пользователям лишние уведомления, которые были настроены через подписки службы отчетов SSRS. По неизвестной причине, через интерфейс SharePoint эти подписки видно не было, но зато они явно присутствовали в базе ReportingService и для них работали соответствующие задания SQL Agent. На всякий случай, я не стал удалять их из базы, а поискал другой выход. В итоге подписки удалось обнаружить и удалить через PowerShell: Use PowerShell to Change and List Reporting Services Subscription Owners and Run a Subscription (http://msdn.microsoft.com/en-us/library/dn747196%28v=sql.110%29.aspx). Для удаления подписки следует использовать команду

$rs2010.DeleteSubscription("SubscriptionID");
которая не описана в приведенной выше статье. Увы, не было времени разбираться, почему удаленные через интерфейс подписки пропали из зоны видимости, но продолжили исправно работать. Если кто-то обладает подобной информацией - пожалуйста поделитесь в комментариях.]]></description>
			<content:encoded><![CDATA[<div>Сегодня выяснилось, что одна из наших систем отправляет пользователям лишние уведомления, которые были настроены через подписки службы отчетов SSRS. По неизвестной причине, через интерфейс SharePoint эти подписки видно не было, но зато они явно присутствовали в базе ReportingService и для них работали соответствующие задания SQL Agent. На всякий случай, я не стал удалять их из базы, а поискал другой выход. В итоге подписки удалось обнаружить и удалить через PowerShell: <a href="http://msdn.microsoft.com/en-us/library/dn747196%28v=sql.110%29.aspx" target="_blank">Use PowerShell to Change and List Reporting Services Subscription Owners and Run a Subscription</a>. Для удаления подписки следует использовать команду<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">$rs2010.DeleteSubscription(<span style="color: red">&quot;SubscriptionID&quot;</span>);</pre></div>которая не описана в приведенной выше статье. Увы, не было времени разбираться, почему удаленные через интерфейс подписки пропали из зоны видимости, но продолжили исправно работать. Если кто-то обладает подобной информацией - пожалуйста поделитесь в комментариях.</div>

]]></content:encoded>
			<dc:creator>Артем Enot Грунин</dc:creator>
			<guid isPermaLink="true">//axforum.info/forums/blog.php?b=8146</guid>
		</item>
		<item>
			<title>Авторизация в CRM 2011 через Firefox и Chrome</title>
			<link>//axforum.info/forums/blog.php?b=8145</link>
			<pubDate>Mon, 28 Jul 2014 09:11:52 GMT</pubDate>
			<description><![CDATA[Всем доброго времени суток! Понимаю, что пишу об этом не первый, однако часть информации оказалась для меня новой, возможно пригодится и остальным. Итак, как настроить автоматический вход в CRM в Microsoft Internet Explorer, Mozilla Firefox и Google Chrome?

*Шаг первый. Доменная авторизация в разных браузерах.*
Я думаю, все вы сталкивались с проблемой, когда IE задалбывает пользователя окном ввода логина-пароля и при этом отказывается их принимать. Если вам известна причина такого поведения, пожалуйста, отпишитесь в комментариях, метод же лечения, думаю, известен всем: необходимо добавить узел CRM в "доверенные узлы", или в "местную интрасеть". Этой операции должно быть достаточно, чтобы авторизация заработала не только в IE, но и в Chrome, так как последний использует те же самые системные настройки, что и IE. С Firefox немного сложнее. Не смотря на то, что Firefox поддерживает NTLM, в графическом интерфейсе нет возможности указать перечень доверенных узлов. Проблема решается установкой дополнений, или правкой системных настроек: http://support.microsoft.com/kb/2786247

*Шаг второй. Доменная авторизация через ADFS.*
При использовании федерации, авторизация в CRM отрабатывает по несколько иному принципу. Все изложенное выше вполне справедливо, однако, в доверенные узлы нужно добавлять уже не адрес самой системы, а адрес вашего ADFS сервера, так как авторизация будет происходить непосредственно на нем. В данном случае, этого должно быть достаточно для IE и Firefox. В Firefox мне даже не пришлось ничего менять, видимо доверие сайту CRM распространяется и на те сайты, куда он перенаправляет запрос авторизации. Сложности возникли только с Chrome. К счастью, проблема известная и лечится выключением расширенной защиты в параметрах сайта ADFS: http://www.powerobjects.com/blog/2012/11/02/adfs-and-single-sing-o-cross-browser/. Существует официальная инструкция от Microsoft (http://support.microsoft.com/kb/2709891/en-us?sd=rss), которая рекомендует отключать расширенную защиту не на сервере ADFS, а на каждом конкретном компьютере, но это, на мой взгляд, маразм, так как заранее не известно, какой из браузеров решит выбрать пользователь завтра. В любом случае, уверен что ваш ADFS сервер, если он у вас вообще есть, был развернут исключительно для CRM и вряд ли в ближайшее время будет использован для чего бы то ни было еще. Поэтому, угроза от отключения расширенной защиты минимальна.

Удачного администрирования!]]></description>
			<content:encoded><![CDATA[<div>Всем доброго времени суток! Понимаю, что пишу об этом не первый, однако часть информации оказалась для меня новой, возможно пригодится и остальным. Итак, как настроить автоматический вход в CRM в Microsoft Internet Explorer, Mozilla Firefox и Google Chrome?<br />
<br />
<b>Шаг первый. Доменная авторизация в разных браузерах.</b><br />
Я думаю, все вы сталкивались с проблемой, когда IE задалбывает пользователя окном ввода логина-пароля и при этом отказывается их принимать. Если вам известна причина такого поведения, пожалуйста, отпишитесь в комментариях, метод же лечения, думаю, известен всем: необходимо добавить узел CRM в &quot;доверенные узлы&quot;, или в &quot;местную интрасеть&quot;. Этой операции должно быть достаточно, чтобы авторизация заработала не только в IE, но и в Chrome, так как последний использует те же самые системные настройки, что и IE. С Firefox немного сложнее. Не смотря на то, что Firefox поддерживает NTLM, в графическом интерфейсе нет возможности указать перечень доверенных узлов. Проблема решается установкой дополнений, или правкой системных настроек: <a href="http://support.microsoft.com/kb/2786247" target="_blank">http://support.microsoft.com/kb/2786247</a><br />
<br />
<b>Шаг второй. Доменная авторизация через ADFS.</b><br />
При использовании федерации, авторизация в CRM отрабатывает по несколько иному принципу. Все изложенное выше вполне справедливо, однако, в доверенные узлы нужно добавлять уже не адрес самой системы, а адрес вашего ADFS сервера, так как авторизация будет происходить непосредственно на нем. В данном случае, этого должно быть достаточно для IE и Firefox. В Firefox мне даже не пришлось ничего менять, видимо доверие сайту CRM распространяется и на те сайты, куда он перенаправляет запрос авторизации. Сложности возникли только с Chrome. К счастью, проблема известная и лечится выключением расширенной защиты в параметрах сайта ADFS: <a href="http://www.powerobjects.com/blog/2012/11/02/adfs-and-single-sing-o-cross-browser/" target="_blank">http://www.powerobjects.com/blog/201...cross-browser/</a>. Существует <a href="http://support.microsoft.com/kb/2709891/en-us?sd=rss" target="_blank">официальная инструкция от Microsoft</a>, которая рекомендует отключать расширенную защиту не на сервере ADFS, а на каждом конкретном компьютере, но это, на мой взгляд, маразм, так как заранее не известно, какой из браузеров решит выбрать пользователь завтра. В любом случае, уверен что ваш ADFS сервер, если он у вас вообще есть, был развернут исключительно для CRM и вряд ли в ближайшее время будет использован для чего бы то ни было еще. Поэтому, угроза от отключения расширенной защиты минимальна.<br />
<br />
Удачного администрирования!</div>

]]></content:encoded>
			<dc:creator>Артем Enot Грунин</dc:creator>
			<guid isPermaLink="true">//axforum.info/forums/blog.php?b=8145</guid>
		</item>
		<item>
			<title>Ошибки доступа к хранимым функциям и процедурам CRM</title>
			<link>//axforum.info/forums/blog.php?b=8144</link>
			<pubDate>Mon, 21 Jul 2014 08:52:40 GMT</pubDate>
			<description>В одном из своих прошлых постов я уже писал об этой известной, в общем-то, проблеме: Ошибка доступа к процедуре p_GetCrmUserId при запуске отчета (http://www.axforum.info/forums/blog.php?b=400).

Причина ее возникновения неизвестна, однако существует поверье, что она возникает при использовании доменной учетной записи для запуска службы SSRS и может возникать при изменении оной после инсталляции системы. Примечательно, что если просто попробовать выдать новой учетной записи те же права что и старой, ошибка не уйдет. В прошлый раз я не нашел иного способа ее устранения, кроме выдачи прав на конкретные процедуры вместо всей базы. Позже выяснилось, что ошибка имеет свойство возвращаться и... поражать все новые функции. Помимо упомянутой выше, проблема стала происходить при обращении к старой-доброй функции времен CRM 3.0 fn_GetFormatStrings. Техподдержка MS пока не ответила ничего вразумительного, зато в ходе экспериментов я нашел решение:

*Проблема лечится импортом базы организации.* Очевидно, в ходе этого процесса происходит выдача прав учетным записям служб и ошибка уходит. Починка сервера, или коннектора для служб отчетов, увы не помогает. К сожалению, импорт базы тоже не проходит бесследно и у пользователей могут возникнуть проблемы с сохраненными представлениями, или конфигурацией Outlook, но... Все же это решение, хотя бы на какое-то время, пока MS не устранит проблему - я все еще не теряю надежды добить техподдержку.</description>
			<content:encoded><![CDATA[<div>В одном из своих прошлых постов я уже писал об этой известной, в общем-то, проблеме: <a href="http://www.axforum.info/forums/blog.php?b=400" target="_blank">Ошибка доступа к процедуре p_GetCrmUserId при запуске отчета</a>.<br />
<br />
Причина ее возникновения неизвестна, однако существует поверье, что она возникает при использовании доменной учетной записи для запуска службы SSRS и может возникать при изменении оной после инсталляции системы. Примечательно, что если просто попробовать выдать новой учетной записи те же права что и старой, ошибка не уйдет. В прошлый раз я не нашел иного способа ее устранения, кроме выдачи прав на конкретные процедуры вместо всей базы. Позже выяснилось, что ошибка имеет свойство возвращаться и... поражать все новые функции. Помимо упомянутой выше, проблема стала происходить при обращении к старой-доброй функции времен CRM 3.0 fn_GetFormatStrings. Техподдержка MS пока не ответила ничего вразумительного, зато в ходе экспериментов я нашел решение:<br />
<br />
<b>Проблема лечится импортом базы организации.</b> Очевидно, в ходе этого процесса происходит выдача прав учетным записям служб и ошибка уходит. Починка сервера, или коннектора для служб отчетов, увы не помогает. К сожалению, импорт базы тоже не проходит бесследно и у пользователей могут возникнуть проблемы с сохраненными представлениями, или конфигурацией Outlook, но... Все же это решение, хотя бы на какое-то время, пока MS не устранит проблему - я все еще не теряю надежды добить техподдержку.</div>

]]></content:encoded>
			<dc:creator>Артем Enot Грунин</dc:creator>
			<guid isPermaLink="true">//axforum.info/forums/blog.php?b=8144</guid>
		</item>
		<item>
			<title>Отключенные пользователи ошибочно добавляются в группу ReportingGroup после импорта организации</title>
			<link>//axforum.info/forums/blog.php?b=8143</link>
			<pubDate>Fri, 18 Jul 2014 07:18:06 GMT</pubDate>
			<description>Сегодня обнаружил интересный баг. После импорта организации, отключенные пользователи попадают в группу ReportingGroup. Потенциально это может приводить к проблемам безопасности, если, конечно, эти люди не уволены и выключены в AD.

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

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

Будьте бдительны!</description>
			<content:encoded><![CDATA[<div>Сегодня обнаружил интересный баг. После импорта организации, отключенные пользователи попадают в группу ReportingGroup. Потенциально это может приводить к проблемам безопасности, если, конечно, эти люди не уволены и выключены в AD.<br />
<br />
В моем случае это привело только к небольшим негативным последствиям: я использую эту группу для формирования списка рассылки сообщений всем пользователям CRM. Это очень удобно, так как система сама актуализирует эту группу при добавлении пользователя, изменении маппинга пользователя с AD и при его отключении.<br />
<br />
Увы, повторно выключить уже отключенных пользователей не помогает. Для того чтобы система актуализировала группу требуется сперва их включить, а после выключить. Впрочем, проще почистить группу руками.<br />
<br />
Будьте бдительны!</div>

]]></content:encoded>
			<dc:creator>Артем Enot Грунин</dc:creator>
			<guid isPermaLink="true">//axforum.info/forums/blog.php?b=8143</guid>
		</item>
		<item>
			<title>Как использовать адрес сервера отчетов в гиперссылках</title>
			<link>//axforum.info/forums/blog.php?b=8142</link>
			<pubDate>Wed, 09 Jul 2014 15:58:22 GMT</pubDate>
			<description><![CDATA[В одном из своих отчетов я формирую гиппер сылку для открытия формы записи. Проблема в том, что отчет разворачивается не в CRM, где для этого есть специальный параметр CRM_URL, а в SharePoint. 

Впрочем, это не мешает создать такой параметр самостоятельно. В моем случае SSRS работает в режиме интеграции SharePoint, так что адрес сервера равен адресу сайта SP, поэтому адрес узла можно получить через глобальную переменную Globals!ReportServerUrl. Единственная проблема - это то, что адрес SSRS в режиме интеграции имеет формат:

Код:
---------
http://<Server Name>/_vti_bin/ReportServer
---------
Впрочем, нужную нам часть несложно получить при помощи VB функции, которую придется встроить в отчет:

    Public Function GetHostUrl(ByRef uri As String) As String
        If String.IsNullOrEmpty(uri) Then
            Return String.Empty
        Else
            Return New Uri(uri).GetLeftPart(UriPartial.Authority)
        End If
    End Function
При ее написании выяснился интересный нюанс. Даже если вы задаете адрес сервера отчетов в настройках проекта, при отладке в студии значение параметра Globals!ReportServerUrl все равно будет Nothing. В принципе, это не страшно, для формирования значения параметра можно использовать выражение вида:

=IIF(String.IsNullOrEmpty(Globals!ReportServerUrl), "http://test server uri", Code.GetHostUrl(Globals!ReportServerUrl))
Но вот тут-то и кроется коварство SSRS. Как выяснилось, функция IIF не реализует короткое замыкание, поэтому обе части выражения - и та что соответствует истине и та часть, которая соответствует ложному результату, будут выполнены в любом случае. Иными словами, если Globals!ReportServerUrl = Nothing наш код все равно выполнится, хотя результат не будет использован. Как выяснилось (http://social.msdn.microsoft.com/Forums/sqlserver/en-US/822918cb-2138-4a4f-af9a-c0e8e70f319e/ssrs-iif), MS это делает в целях улучшения производительности. Именно поэтому, я делаю в коде проверку входного параметра на NULL, в противном случае код валился бы с ошибкой при отладке в студии.

Будьте бдительны!]]></description>
			<content:encoded><![CDATA[<div>В одном из своих отчетов я формирую гиппер сылку для открытия формы записи. Проблема в том, что отчет разворачивается не в CRM, где для этого есть специальный параметр CRM_URL, а в SharePoint. <br />
<br />
Впрочем, это не мешает создать такой параметр самостоятельно. В моем случае SSRS работает в режиме интеграции SharePoint, так что адрес сервера равен адресу сайта SP, поэтому адрес узла можно получить через глобальную переменную Globals!ReportServerUrl. Единственная проблема - это то, что адрес SSRS в режиме интеграции имеет формат:<br />
<div class="xpp"><div class="smallfont xpp_title">Код:</div><pre class="alt2 xpp_code">http://&lt;Server Name&gt;/_vti_bin/ReportServer</pre></div>Впрочем, нужную нам часть несложно получить при помощи VB функции, которую придется встроить в отчет:<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">    <span style="color: blue">Public</span> Function GetHostUrl(<span style="color: blue">ByRef</span> uri As String) As String
        <span style="color: blue">If</span> String.IsNullOrEmpty(uri) Then
            <span style="color: blue">Return</span> String.Empty
        Else
            <span style="color: blue">Return</span> <span style="color: blue">New</span> Uri(uri).GetLeftPart(UriPartial.Authority)
        End If
    End Function</pre></div>При ее написании выяснился интересный нюанс. Даже если вы задаете адрес сервера отчетов в настройках проекта, при отладке в студии значение параметра Globals!ReportServerUrl все равно будет Nothing. В принципе, это не страшно, для формирования значения параметра можно использовать выражение вида:<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">=IIF(String.IsNullOrEmpty(Globals!ReportServerUrl), <span style="color: red">&quot;http://test server uri&quot;</span>, Code.GetHostUrl(Globals!ReportServerUrl))</pre></div>Но вот тут-то и кроется коварство SSRS. Как выяснилось, функция IIF не реализует короткое замыкание, поэтому обе части выражения - и та что соответствует истине и та часть, которая соответствует ложному результату, будут выполнены в любом случае. Иными словами, если Globals!ReportServerUrl = Nothing наш код все равно выполнится, хотя результат не будет использован. <a href="http://social.msdn.microsoft.com/Forums/sqlserver/en-US/822918cb-2138-4a4f-af9a-c0e8e70f319e/ssrs-iif" target="_blank">Как выяснилось</a>, MS это делает в целях улучшения производительности. Именно поэтому, я делаю в коде проверку входного параметра на NULL, в противном случае код валился бы с ошибкой при отладке в студии.<br />
<br />
Будьте бдительны!</div>

]]></content:encoded>
			<dc:creator>Артем Enot Грунин</dc:creator>
			<guid isPermaLink="true">//axforum.info/forums/blog.php?b=8142</guid>
		</item>
		<item>
			<title>Поиск проблемы подключения Outlook клиента</title>
			<link>//axforum.info/forums/blog.php?b=8140</link>
			<pubDate>Tue, 24 Jun 2014 07:24:48 GMT</pubDate>
			<description>Нашел замечательную статью по диагностике проблем подключения Outlook клиента: http://crmbook.powerobjects.com/system-administration/outlook-client-installation-and-configuration/dynamics-crm-outlook-client-troubleshooting/

В моем случае, оказалось, что на подменном ноуте, который я использовал для тестирования IFD публикации наружу, сбилось системное время. Логи же говорили: Credentials Required... Вот так хорошая статья может спасти целый день поисков решения</description>
			<content:encoded><![CDATA[<div>Нашел замечательную статью по диагностике проблем подключения Outlook клиента: <a href="http://crmbook.powerobjects.com/system-administration/outlook-client-installation-and-configuration/dynamics-crm-outlook-client-troubleshooting/" target="_blank">http://crmbook.powerobjects.com/syst...oubleshooting/</a><br />
<br />
В моем случае, оказалось, что на подменном ноуте, который я использовал для тестирования IFD публикации наружу, сбилось системное время. Логи же говорили: Credentials Required... Вот так хорошая статья может спасти целый день поисков решения</div>

]]></content:encoded>
			<dc:creator>Артем Enot Грунин</dc:creator>
			<guid isPermaLink="true">//axforum.info/forums/blog.php?b=8140</guid>
		</item>
		<item>
			<title>Проблема внутренней (доменной) авторизации в CRM 2011/2013 при включенном IFD</title>
			<link>//axforum.info/forums/blog.php?b=8139</link>
			<pubDate>Fri, 20 Jun 2014 08:31:49 GMT</pubDate>
			<description>Недавно я столкнулся с интересным глюком при настройке Internet Faced Deployment в CRM 2011 (аналогичный опыт есть и с 2013).

Настройка выполнялась строго по официальной инструкции (ныне включена в состав Implementation Guide), однако на финальном шаге - настройке IFD возникала проблема: переставала работать авторизация по внутреннему адресу системы.

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

Различные системы, например CRM и SharePoint, используют для реализации задачи различные подходы. В случае с SharePoint, для внешнего доступа, как правило, создается отдельный веб-сайт со своими настройками, который подключен к той же базе что и основной. Далее, через специальное решение типа Reverse Proxy он публикуется во внешнюю сеть. При этом рекомендуется использовать для внутреннего и внешнего доступа одно и то же имя.

В случае с CRM, *использование одного и того же имени для внутреннего и внешнего доступа невозможно*. Так как веб приложение у CRM одно, а не два, система понимает откуда идет запрос на основании запрашиваемого URL. Дополнительно, отличаются сами форматы этих адресов. Для внутреннего доступа используется формат:

Код:
---------
https://server.domain/oranization
---------
а для внешнего:

Код:
---------
https://oranization.server.domain
---------
Иными словами, если URL не совпал с внутренним, система парсит его как внешний, так что все что соответствует маске *.domain система пытается парсить как имя организации.

Теперь к практике. Внутреннее имя системы (вне зависимости от того собираетесь вы использовать IFD/Claims авторизацию, или нет) задается в параметрах развертывания системы, которые доступны через Deployment Manager:

Вложение 350 (//axforum.info/forums/attachment.php?attachmentid=350)

Прошу вас обратить внимание, что *по умолчанию в них указан порт*. В данном случае это порт по умолчанию.

Начиная с CRM 2011, IFD конфигурация возможна только при использовании HTTPS, поэтому адреса служб будут выглядеть как-то так:

CRM.FIXRM.COM:443
Если писать их по аналогии с адресами по умолчанию.

Вот тут-то и кроется опасность: *при включении и настройке IFD, нельзя указывать порт по умолчанию 443 во внутреннем имени сервиса*.

Порт требуется указывать, только если система развернута на не стандартном порту, например:

CRM.FIXRM.COM:444
В противном случае,порт *требуется не указывать*:

Вложение 349 (//axforum.info/forums/attachment.php?attachmentid=349)

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

Если в вашей конфигурации уже есть эта проблема, не забудьте после выполнения настройки перезагрузить IIS на сервере CRM и обновить метаданные конечных точек в консоли ADFS.</description>
			<content:encoded><![CDATA[<div>Недавно я столкнулся с интересным глюком при настройке Internet Faced Deployment в CRM 2011 (аналогичный опыт есть и с 2013).<br />
<br />
Настройка выполнялась строго по официальной инструкции (ныне включена в состав Implementation Guide), однако на финальном шаге - настройке IFD возникала проблема: переставала работать авторизация по внутреннему адресу системы.<br />
<br />
Для тех кто не погружен в предметную область, я дам некоторые комментарии. Исторически сложилось, что публикация внутренних ресурсов во внешнюю сеть - это всегда геморрой. Причин тому несколько. В основном - интегрированные внутренние системы, взаимодействие с которыми может быть затруднено в случае доступа снаружи, и авторизация, которая внутри и снаружи может выполняться по разному (опять же в контексте работы с интегрированными системами).<br />
<br />
Различные системы, например CRM и SharePoint, используют для реализации задачи различные подходы. В случае с SharePoint, для внешнего доступа, как правило, создается отдельный веб-сайт со своими настройками, который подключен к той же базе что и основной. Далее, через специальное решение типа Reverse Proxy он публикуется во внешнюю сеть. При этом рекомендуется использовать для внутреннего и внешнего доступа одно и то же имя.<br />
<br />
В случае с CRM, <b>использование одного и того же имени для внутреннего и внешнего доступа невозможно</b>. Так как веб приложение у CRM одно, а не два, система понимает откуда идет запрос на основании запрашиваемого URL. Дополнительно, отличаются сами форматы этих адресов. Для внутреннего доступа используется формат:<br />
<div class="xpp"><div class="smallfont xpp_title">Код:</div><pre class="alt2 xpp_code">https://server.domain/oranization</pre></div>а для внешнего:<br />
<div class="xpp"><div class="smallfont xpp_title">Код:</div><pre class="alt2 xpp_code">https://oranization.server.domain</pre></div>Иными словами, если URL не совпал с внутренним, система парсит его как внешний, так что все что соответствует маске *.domain система пытается парсить как имя организации.<br />
<br />
Теперь к практике. Внутреннее имя системы (вне зависимости от того собираетесь вы использовать IFD/Claims авторизацию, или нет) задается в параметрах развертывания системы, которые доступны через Deployment Manager:<br />
<br />
<a href="//axforum.info/forums/blog_attachment.php?attachmentid=350&amp;d=1403258089" rel="Lightbox" id="attachment350" ><img src="//axforum.info/forums/blog_attachment.php?attachmentid=350&amp;thumb=1&amp;d=1403258089" class="thumbnail" border="0" alt="Нажмите на изображение для увеличения
Название: internal address.png
Просмотров: 16207
Размер:	14.1 Кб
ID:	350" style="margin: 2px" /></a><br />
<br />
Прошу вас обратить внимание, что <b>по умолчанию в них указан порт</b>. В данном случае это порт по умолчанию.<br />
<br />
Начиная с CRM 2011, IFD конфигурация возможна только при использовании HTTPS, поэтому адреса служб будут выглядеть как-то так:<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">CRM.FIXRM.COM:443</pre></div>Если писать их по аналогии с адресами по умолчанию.<br />
<br />
Вот тут-то и кроется опасность: <b>при включении и настройке IFD, нельзя указывать порт по умолчанию 443 во внутреннем имени сервиса</b>.<br />
<br />
Порт требуется указывать, только если система развернута на не стандартном порту, например:<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">CRM.FIXRM.COM:444</pre></div>В противном случае,порт <b>требуется не указывать</b>:<br />
<br />
<a href="//axforum.info/forums/blog_attachment.php?attachmentid=349&amp;d=1403252427" rel="Lightbox" id="attachment349" ><img src="//axforum.info/forums/blog_attachment.php?attachmentid=349&amp;thumb=1&amp;d=1403252427" class="thumbnail" border="0" alt="Нажмите на изображение для увеличения
Название: correct internal address.png
Просмотров: 16141
Размер:	14.7 Кб
ID:	349" style="margin: 2px" /></a><br />
<br />
Если же вы повторите мою ошибку, внутренний адрес будет восприниматься некорректно: система будет пытаться авторизовать пользователя через форму, при этом имя сервера будет восприниматься как имя организации.<br />
<br />
Если в вашей конфигурации уже есть эта проблема, не забудьте после выполнения настройки перезагрузить IIS на сервере CRM и обновить метаданные конечных точек в консоли ADFS.</div>

]]></content:encoded>
			<dc:creator>Артем Enot Грунин</dc:creator>
			<guid isPermaLink="true">//axforum.info/forums/blog.php?b=8139</guid>
		</item>
		<item>
			<title>Ошибка 0x80040237 при отслеживании почты в CRM 2011 Client for Outlook</title>
			<link>//axforum.info/forums/blog.php?b=8130</link>
			<pubDate>Thu, 29 May 2014 06:21:43 GMT</pubDate>
			<description>Традиционно, наиболее глючным, недокументированным и сложным в отладке компонентом CRM всех версий, является клиент для Outlook. Скорее всего, это связано с родословной самого MS Office, которая уходит корнями в глубокую дремучую древность COM технологии. 

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

Вложение 347 (//axforum.info/forums/attachment.php?attachmentid=347)

а в журнале Windows появится запись:

---Цитата---
An error occurred while promoting a Microsoft CRM e-mail message.  Restart Microsoft Outlook and try again. HR=0x80040237. Context=. Function=CMailItemHelper::HrPromoteMailItemInCrm. Line=1833.
---Конец цитаты---
Расследование показало, что в некоторых случаях ошибка может быть вызвана иными причинами: http://community.dynamics.com/crm/f/117/t/122862.aspx, но так и не позволило выяснить, какую запись система считает дубликатом и почему.

К счастью, я имею привычку мониторить тонкие параметры настройки системы (http://support.microsoft.com/kb/2691237) после выхода каждого нового пакета обновления. Выяснилось, что в UR15 появились новые недокументированные параметры AllowPromoteDuplicates и SecuritySettingForEmail (к сожалению, они не описаны в статье выше).  Активация первого параметра решила проблему в моем случае.

Мораль: рекомендую всем, после выхода каждого нового пакета обновления, качать свежую версию OrgDBTool и запускать ее с ключом Retrieve на своей системе. Это позволит вам быть в курсе нововведений, которые техподдержка MS реализует для крупных (как правило) заказчиков, но не спешит делиться ими с широким кругом пользователей.</description>
			<content:encoded><![CDATA[<div>Традиционно, наиболее глючным, недокументированным и сложным в отладке компонентом CRM всех версий, является клиент для Outlook. Скорее всего, это связано с родословной самого MS Office, которая уходит корнями в глубокую дремучую древность COM технологии. <br />
<br />
Иногда, впрочем, ошибки вызваны не зависанием и падениями плагина, а странными ограничениями самой бизнес логики решения. В частности, было замечено, что иногда система не позволяет отслеживать письма с одинаковой темой, что, мягко говоря, странно. Пользователь при этом получит следующую ошибку:<br />
<br />
<a href="//axforum.info/forums/blog_attachment.php?attachmentid=347&amp;d=1401343261" rel="Lightbox" id="attachment347" ><img src="//axforum.info/forums/blog_attachment.php?attachmentid=347&amp;thumb=1&amp;d=1401343261" class="thumbnail" border="0" alt="Нажмите на изображение для увеличения
Название: dup.png
Просмотров: 1512
Размер:	22.3 Кб
ID:	347" style="margin: 2px" /></a><br />
<br />
а в журнале Windows появится запись:<br />
<div class="q">
	<div class="smallfont q_title">Цитата:</div>
	<div class="alt2 q_body">
		
			An error occurred while promoting a Microsoft CRM e-mail message.  Restart Microsoft Outlook and try again. HR=0x80040237. Context=. Function=CMailItemHelper::HrPromoteMailItemInCrm. Line=1833.
		
	</div>
</div>Расследование показало, что в некоторых случаях ошибка может быть вызвана иными причинами: <a href="http://community.dynamics.com/crm/f/117/t/122862.aspx" target="_blank">http://community.dynamics.com/crm/f/117/t/122862.aspx</a>, но так и не позволило выяснить, какую запись система считает дубликатом и почему.<br />
<br />
К счастью, я имею привычку мониторить <a href="http://support.microsoft.com/kb/2691237" target="_blank">тонкие параметры настройки системы</a> после выхода каждого нового пакета обновления. Выяснилось, что в UR15 появились новые недокументированные параметры AllowPromoteDuplicates и SecuritySettingForEmail (к сожалению, они не описаны в статье выше).  Активация первого параметра решила проблему в моем случае.<br />
<br />
Мораль: рекомендую всем, после выхода каждого нового пакета обновления, качать свежую версию OrgDBTool и запускать ее с ключом Retrieve на своей системе. Это позволит вам быть в курсе нововведений, которые техподдержка MS реализует для крупных (как правило) заказчиков, но не спешит делиться ими с широким кругом пользователей.</div>

]]></content:encoded>
			<dc:creator>Артем Enot Грунин</dc:creator>
			<guid isPermaLink="true">//axforum.info/forums/blog.php?b=8130</guid>
		</item>
		<item>
			<title>Ошибка прайслиста в Возможной сделке</title>
			<link>//axforum.info/forums/blog.php?b=6412</link>
			<pubDate>Thu, 13 Feb 2014 05:58:44 GMT</pubDate>
			<description><![CDATA[Вчера меня посетила давняя проблема CRM - на форме Продукта для возможной сделки перестали фильтроваться продукты входящие в прайс. Проблема широко известна и сохраняется уже не в первой версии системы. Расследование показало, что виной всему может служить любая попытка настроить лукап продукта: разрешить поиск, или добавить обработчик изменения продукта (на моей форме, он автоматически подставляет единицу измерения по умолчанию). В результате, форма меняет значения полей *DefaultViewId* (Представление по умолчанию) и *AvailableViewIds* (Список доступных представлений).
  
  Существует мнение, что если удалить обработчики событий, или отключить поиск, то ошибка исправится. В моем случае, это не сработало. Решение проблемы описано в этом блоге: http://crmandsharepoint.blogspot.com.au/2012/05/view-products-in-parent-price-list-is.html
  
  Последовательность действий:
  
1. Создайте новое решение и      включите в него объект Продукт для возможной сделки.  Можете использовать свое текущее      решение, тогда вам просто потребуется больше времени
2. Экспортируйте его как      неуправляемое, распакуйте архив и откройте в редакторе файл кастомизаций *customizations.xml*.
3. Простой автозаменой, замените      все вхождения строки "{8BA625B2-6A2A-4735-BAB2-0C74AE8442A4}" на      "{BCC509EE-1444-4A95-AED2-128EFD85FFD5}". Это статичные      системные идентификаторы, поэтому они общие для всех систем. Не нужно      искать их в базе, как это советует автор цитированного поста.
4. Запакуйте все как было и      импортируйте решение обратно в систему

  В моем случае система заработала полностью: сохранилась и фильтрация продуктов по прайсу и поиск продуктов (с учетом фильтра) и обработчик события.
  
  Если у вас планируются какие-то сложные доработки связанные с этой формой, возможно лучшим решением будет динамически подключать обработчики событий из кода-обработчика OnLoad.]]></description>
			<content:encoded><![CDATA[<div>Вчера меня посетила давняя проблема CRM - на форме Продукта для возможной сделки перестали фильтроваться продукты входящие в прайс. Проблема широко известна и сохраняется уже не в первой версии системы. Расследование показало, что виной всему может служить любая попытка настроить лукап продукта: разрешить поиск, или добавить обработчик изменения продукта (на моей форме, он автоматически подставляет единицу измерения по умолчанию). В результате, форма меняет значения полей <b>DefaultViewId</b> (Представление по умолчанию) и <b>AvailableViewIds</b> (Список доступных представлений).<br />
  <br />
  Существует мнение, что если удалить обработчики событий, или отключить поиск, то ошибка исправится. В моем случае, это не сработало. Решение проблемы описано в этом блоге: <a href="http://crmandsharepoint.blogspot.com.au/2012/05/view-products-in-parent-price-list-is.html" target="_blank">http://crmandsharepoint.blogspot.com...e-list-is.html</a><br />
  <br />
  Последовательность действий:<br />
  <ol style="list-style-type: decimal"><li>Создайте новое решение и      включите в него объект Продукт для возможной сделки.  Можете использовать свое текущее      решение, тогда вам просто потребуется больше времени</li>
<li>Экспортируйте его как      неуправляемое, распакуйте архив и откройте в редакторе файл кастомизаций <b>customizations.xml</b>.</li>
<li>Простой автозаменой, замените      все вхождения строки &quot;{8BA625B2-6A2A-4735-BAB2-0C74AE8442A4}&quot; на      &quot;{BCC509EE-1444-4A95-AED2-128EFD85FFD5}&quot;. Это статичные      системные идентификаторы, поэтому они общие для всех систем. Не нужно      искать их в базе, как это советует автор цитированного поста.</li>
<li>Запакуйте все как было и      импортируйте решение обратно в систему</li>
</ol>  В моем случае система заработала полностью: сохранилась и фильтрация продуктов по прайсу и поиск продуктов (с учетом фильтра) и обработчик события.<br />
  <br />
  Если у вас планируются какие-то сложные доработки связанные с этой формой, возможно лучшим решением будет динамически подключать обработчики событий из кода-обработчика OnLoad.</div>

]]></content:encoded>
			<dc:creator>Артем Enot Грунин</dc:creator>
			<guid isPermaLink="true">//axforum.info/forums/blog.php?b=6412</guid>
		</item>
		<item>
			<title>Кросбраузерный CRM. Последняя капля</title>
			<link>//axforum.info/forums/blog.php?b=6402</link>
			<pubDate>Thu, 28 Nov 2013 08:22:55 GMT</pubDate>
			<description><![CDATA[Вчера пролилась последняя капля моей крови в борьбе за кросбраузерность моего решения на CRM… В одном из своих прошлых постов этой серии Кросбраузерный CRM. Первая кровь... (http://www.axforum.info/forums/blog.php?b=391)  я писал о врожденных изъянах InternetExplorer при работе с XPath.

Выяснилось, что объект XMLHttpRequest в IE10+ стал возвращать наивный XML вместо, MSXML как это было ранее. В результате у него отпали MS методы selectNodes, но почему-то так и не вырос стандартный метод evaluate. Различные рекомендуемые альтернативы, такие как Wicked Good XPath не принесли желаемого результата. Мне так не удалось заставить работать с этой библиотекой существующий код под FireFox - XPath запрос не возвращает результаты.

К счастью, выяснилось, что все же существует включаемая обратная совместимость, позволяющая вернуть selectSingleNode на его историческую родину. Для этого, объекту XMLHttpRequest необходимо задать свойство responseType = "msxml-document". Интересный нюанс заключается в том, что XMLHttpRequest готов сожрать этот параметр не в любой момент, а только в некоторых из состояний. Более того, в разных версиях реализации, он может попытаться сблевать некорректно заданный responseType. В итоге, ваш код должен выглядеть как-то так:

        var req = new XMLHttpRequest();
        
        req.open("POST", this._SoapPath(), true)
        // Затычка для IE10+
        if (typeof (document.evaluate) == "undefined")
        {
            try { req.responseType = "msxml-document"; } catch (e) {}
        }
Возвращенный MSXML документ, действительно реализует старые добрые функции, однако, есть еще один сюрприз. Выяснилось, что в новой редакции, старые методы внезапно прониклись поддержкой XML нейспейсов, в чем не были замечены раньше. В итоге, обрабатывать результат теперь тоже приходится по-другому:

    function selectSingleNode(xmlDoc, elementPath, node)
    {
        if (xmlDoc.evaluate)
        {
            function nsResolver(prefix)
            {
                var ns = {
                    "s": "http://schemas.xmlsoap.org/soap/envelope/",
                    "i": "http://www.w3.org/2001/XMLSchema-instance",
                    "a": "http://schemas.microsoft.com/xrm/2011/Contracts",
                    "b": "http://schemas.microsoft.com/xrm/2011/Contracts",
                    "c": "http://schemas.datacontract.org/2004/07/System.Collections.Generic",
                    "d": "http://www.w3.org/2001/XMLSchema"
                };
                return ns[prefix] || null;
            }

            var nodes = xmlDoc.evaluate(elementPath, node || xmlDoc, nsResolver, XPathResult.ANY_TYPE, null);
            var results = nodes.iterateNext();

            return results;
        }
        else
        {
            try
            {
                var ns = "xmlns:s='http://schemas.xmlsoap.org/soap/envelope/' ";
                ns += "xmlns:i='http://www.w3.org/2001/XMLSchema-instance' ";
                ns += "xmlns:a='http://schemas.microsoft.com/xrm/2011/Contracts' ";
                ns += "xmlns:b='http://schemas.microsoft.com/xrm/2011/Contracts' ";
                ns += "xmlns:c='http://schemas.datacontract.org/2004/07/System.Collections.Generic' ";
                ns += "xmlns:d='http://www.w3.org/2001/XMLSchema' ";

                xmlDoc.setProperty("SelectionNamespaces", ns);
                return (node || xmlDoc).selectSingleNode(elementPath);

            }
            catch (e)
            {
                throw new Error("No XPath Support");
            }
        }

    }
Мораль: будь проклят тот день, когда я связался с XPath. Удачного кодинга

Полезные ссылки:
http://stackoverflow.com/questions/13521554/xpath-in-internet-explorer-10-gone
http://blogs.msdn.com/b/ie/archive/2012/07/19/xmlhttprequest-responsexml-in-ie10-release-preview.aspx]]></description>
			<content:encoded><![CDATA[<div>Вчера пролилась последняя капля моей крови в борьбе за кросбраузерность моего решения на CRM… В одном из своих прошлых постов этой серии <a href="http://www.axforum.info/forums/blog.php?b=391" target="_blank">Кросбраузерный CRM. Первая кровь...</a>  я писал о врожденных изъянах InternetExplorer при работе с XPath.<br />
<br />
Выяснилось, что объект XMLHttpRequest в IE10+ стал возвращать наивный XML вместо, MSXML как это было ранее. В результате у него отпали MS методы selectNodes, но почему-то так и не вырос стандартный метод evaluate. Различные рекомендуемые альтернативы, такие как Wicked Good XPath не принесли желаемого результата. Мне так не удалось заставить работать с этой библиотекой существующий код под FireFox - XPath запрос не возвращает результаты.<br />
<br />
К счастью, выяснилось, что все же существует включаемая обратная совместимость, позволяющая вернуть selectSingleNode на его историческую родину. Для этого, объекту XMLHttpRequest необходимо задать свойство responseType = &quot;msxml-document&quot;. Интересный нюанс заключается в том, что XMLHttpRequest готов сожрать этот параметр не в любой момент, а только в некоторых из состояний. Более того, в разных версиях реализации, он может попытаться сблевать некорректно заданный responseType. В итоге, ваш код должен выглядеть как-то так:<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">        var req = <span style="color: blue">new</span> XMLHttpRequest();
        
        req.open(<span style="color: red">&quot;POST&quot;</span>, this._SoapPath(), <span style="color: blue">true</span>)
        <span style="color: green">// Затычка для IE10+
</span>        <span style="color: blue">if</span> (typeof (document.evaluate) == <span style="color: red">&quot;undefined&quot;</span>)
        {
            <span style="color: blue">try</span> { req.responseType = <span style="color: red">&quot;msxml-document&quot;</span>; } <span style="color: blue">catch</span> (e) {}
        }</pre></div>Возвращенный MSXML документ, действительно реализует старые добрые функции, однако, есть еще один сюрприз. Выяснилось, что в новой редакции, старые методы внезапно прониклись поддержкой XML нейспейсов, в чем не были замечены раньше. В итоге, обрабатывать результат теперь тоже приходится по-другому:<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">    function selectSingleNode(xmlDoc, elementPath, node)
    {
        <span style="color: blue">if</span> (xmlDoc.evaluate)
        {
            function nsResolver(prefix)
            {
                var ns = {
                    <span style="color: red">&quot;s&quot;</span>: <span style="color: red">&quot;http://schemas.xmlsoap.org/soap/envelope/&quot;</span>,
                    <span style="color: red">&quot;i&quot;</span>: <span style="color: red">&quot;http://www.w3.org/2001/XMLSchema-instance&quot;</span>,
                    <span style="color: red">&quot;a&quot;</span>: <span style="color: red">&quot;http://schemas.microsoft.com/xrm/2011/Contracts&quot;</span>,
                    <span style="color: red">&quot;b&quot;</span>: <span style="color: red">&quot;http://schemas.microsoft.com/xrm/2011/Contracts&quot;</span>,
                    <span style="color: red">&quot;c&quot;</span>: <span style="color: red">&quot;http://schemas.datacontract.org/2004/07/System.Collections.Generic&quot;</span>,
                    <span style="color: red">&quot;d&quot;</span>: <span style="color: red">&quot;http://www.w3.org/2001/XMLSchema&quot;</span>
                };
                <span style="color: blue">return</span> ns[prefix] || <span style="color: blue">null</span>;
            }

            var nodes = xmlDoc.evaluate(elementPath, node || xmlDoc, nsResolver, XPathResult.ANY_TYPE, <span style="color: blue">null</span>);
            var results = nodes.iterateNext();

            <span style="color: blue">return</span> results;
        }
        else
        {
            try
            {
                var ns = <span style="color: red">&quot;xmlns:s='http://schemas.xmlsoap.org/soap/envelope/' &quot;</span>;
                ns += <span style="color: red">&quot;xmlns:i='http://www.w3.org/2001/XMLSchema-instance' &quot;</span>;
                ns += <span style="color: red">&quot;xmlns:a='http://schemas.microsoft.com/xrm/2011/Contracts' &quot;</span>;
                ns += <span style="color: red">&quot;xmlns:b='http://schemas.microsoft.com/xrm/2011/Contracts' &quot;</span>;
                ns += <span style="color: red">&quot;xmlns:c='http://schemas.datacontract.org/2004/07/System.Collections.Generic' &quot;</span>;
                ns += <span style="color: red">&quot;xmlns:d='http://www.w3.org/2001/XMLSchema' &quot;</span>;

                xmlDoc.setProperty(<span style="color: red">&quot;SelectionNamespaces&quot;</span>, ns);
                <span style="color: blue">return</span> (node || xmlDoc).selectSingleNode(elementPath);

            }
            <span style="color: blue">catch</span> (e)
            {
                <span style="color: blue">throw</span> <span style="color: blue">new</span> Error(<span style="color: red">&quot;No XPath Support&quot;</span>);
            }
        }

    }</pre></div>Мораль: будь проклят тот день, когда я связался с XPath. Удачного кодинга<br />
<br />
Полезные ссылки:<br />
<a href="http://stackoverflow.com/questions/13521554/xpath-in-internet-explorer-10-gone" target="_blank">http://stackoverflow.com/questions/1...plorer-10-gone</a><br />
<a href="http://blogs.msdn.com/b/ie/archive/2012/07/19/xmlhttprequest-responsexml-in-ie10-release-preview.aspx" target="_blank">http://blogs.msdn.com/b/ie/archive/2...e-preview.aspx</a></div>

]]></content:encoded>
			<dc:creator>Артем Enot Грунин</dc:creator>
			<guid isPermaLink="true">//axforum.info/forums/blog.php?b=6402</guid>
		</item>
		<item>
			<title>Полезная доработка JS библотеки SDK.REST</title>
			<link>//axforum.info/forums/blog.php?b=429</link>
			<pubDate>Thu, 07 Nov 2013 06:47:22 GMT</pubDate>
			<description><![CDATA[Сегодня поучаствовал в одном из обсуждений (http://axforum.info/forums/showthread.php?t=49276) на форуме и вспомнил, что задолжал общественности одну полезную доработку стандартной JS библиотеки в составе SDK, SDK.REST.js.

Для интерпретации JSON результата библиотека использует функцию

_dateReviver: function (key, value)
    {
        ///<summary>
        /// Private function to convert matching string values to Date objects.
        ///</summary>
        ///<param name="key" type="String">
        /// The key used to identify the object property
        ///</param>
        ///<param name="value" type="String">
        /// The string value representing a date
        ///</param>
        var a;
        if (typeof value === 'string')
        {
            a = /Date\(([-+]?\d+)\)/.exec(value);
            if (a)
            {
                return new Date(parseInt(value.replace("/Date(", "").replace(")/", ""), 10));
            }
        }
        return value;
    }
К ней у меня нет претензий, однако почему бы не ограничиваться только датами? Есть и другие типы, которые по недосмотру разработчиков по разному устроены в JS и .NET API. Для того чтобы прозрачнее интерпретировать JSON результат я использовал следующую функцию:

_xrmTypeReviver: function (key, value)
    {
        ///<summary>
        /// Private function to convert matching string values to Date objects.
        /// agrunin: функция дополнена для преобразования EntityReference в lookup понятный Xrm.Page
        ///</summary>
        ///<param name="key" type="String">
        /// The key used to identify the object property
        ///</param>
        ///<param name="value" type="String">
        /// The string value representing a date or EntityReference
        ///</param>
        var a;
        if (typeof value === 'string')
        {
            a = /Date\(([-+]?\d+)\)/.exec(value);
            if (a)
            {
                return new Date(parseInt(value.replace("/Date(", "").replace(")/", ""), 10));
            }
        }
        else if (value != null && typeof value == 'object')
        {
            if (value["__metadata"] != undefined)
            {
                var type = value["__metadata"].type;
                switch (type)
                {
                    case "Microsoft.Crm.Sdk.Data.Services.EntityReference":
                        if (value.Id == null)
                        {
                            // Возвращаем null вместо пустого объекта
                            return null;
                        }
                        else
                        {
                            return { id: value.Id, entityType: value.LogicalName, name: value.Name };
                        }
                    case "Microsoft.Crm.Sdk.Data.Services.Money":
                    case "Microsoft.Crm.Sdk.Data.Services.OptionSetValue":
                        return value.Value;
                    default:
                        return value;
                }
            }
        }
        return value;
    }
Она позволяет приводить EntityReference к JS объекту Lookup, а так же извлекает примитивное значение из объектов Money и OptionSetValue. Отмечу так же, что мой обработчик возвращает null вместо пустого EntityReference, как это делает стандартная библиотека.]]></description>
			<content:encoded><![CDATA[<div>Сегодня поучаствовал в <a href="http://axforum.info/forums/showthread.php?t=49276" target="_blank">одном из обсуждений</a> на форуме и вспомнил, что задолжал общественности одну полезную доработку стандартной JS библиотеки в составе SDK, SDK.REST.js.<br />
<br />
Для интерпретации JSON результата библиотека использует функцию<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">_dateReviver: function (key, value)
    {
        <span style="color: green">///&lt;summary&gt;
</span>        <span style="color: green">/// Private function to convert matching string values to Date objects.
</span>        <span style="color: green">///&lt;/summary&gt;
</span>        <span style="color: green">///&lt;param name=&quot;key&quot; type=&quot;String&quot;&gt;
</span>        <span style="color: green">/// The key used to identify the object property
</span>        <span style="color: green">///&lt;/param&gt;
</span>        <span style="color: green">///&lt;param name=&quot;value&quot; type=&quot;String&quot;&gt;
</span>        <span style="color: green">/// The string value representing a date
</span>        <span style="color: green">///&lt;/param&gt;
</span>        var a;
        <span style="color: blue">if</span> (typeof value === <span style="color: red">'string'</span>)
        {
            a = /Date\(([-+]?\d+)\)/.exec(value);
            <span style="color: blue">if</span> (a)
            {
                <span style="color: blue">return</span> <span style="color: blue">new</span> <span style="color: blue">Date</span>(parseInt(value.replace(<span style="color: red">&quot;/Date(&quot;</span>, <span style="color: red">&quot;&quot;</span>).replace(<span style="color: red">&quot;)/&quot;</span>, <span style="color: red">&quot;&quot;</span>), 10));
            }
        }
        <span style="color: blue">return</span> value;
    }</pre></div>К ней у меня нет претензий, однако почему бы не ограничиваться только датами? Есть и другие типы, которые по недосмотру разработчиков по разному устроены в JS и .NET API. Для того чтобы прозрачнее интерпретировать JSON результат я использовал следующую функцию:<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">_xrmTypeReviver: function (key, value)
    {
        <span style="color: green">///&lt;summary&gt;
</span>        <span style="color: green">/// Private function to convert matching string values to Date objects.
</span>        <span style="color: green">/// agrunin: функция дополнена для преобразования EntityReference в lookup понятный Xrm.Page
</span>        <span style="color: green">///&lt;/summary&gt;
</span>        <span style="color: green">///&lt;param name=&quot;key&quot; type=&quot;String&quot;&gt;
</span>        <span style="color: green">/// The key used to identify the object property
</span>        <span style="color: green">///&lt;/param&gt;
</span>        <span style="color: green">///&lt;param name=&quot;value&quot; type=&quot;String&quot;&gt;
</span>        <span style="color: green">/// The string value representing a date or EntityReference
</span>        <span style="color: green">///&lt;/param&gt;
</span>        var a;
        <span style="color: blue">if</span> (typeof value === <span style="color: red">'string'</span>)
        {
            a = /Date\(([-+]?\d+)\)/.exec(value);
            <span style="color: blue">if</span> (a)
            {
                <span style="color: blue">return</span> <span style="color: blue">new</span> <span style="color: blue">Date</span>(parseInt(value.replace(<span style="color: red">&quot;/Date(&quot;</span>, <span style="color: red">&quot;&quot;</span>).replace(<span style="color: red">&quot;)/&quot;</span>, <span style="color: red">&quot;&quot;</span>), 10));
            }
        }
        <span style="color: blue">else</span> <span style="color: blue">if</span> (value != <span style="color: blue">null</span> &amp;&amp; typeof value == <span style="color: red">'object'</span>)
        {
            <span style="color: blue">if</span> (value[<span style="color: red">&quot;__metadata&quot;</span>] != undefined)
            {
                var type = value[<span style="color: red">&quot;__metadata&quot;</span>].type;
                <span style="color: blue">switch</span> (type)
                {
                    <span style="color: blue">case</span> <span style="color: red">&quot;Microsoft.Crm.Sdk.Data.Services.EntityReference&quot;</span>:
                        <span style="color: blue">if</span> (value.Id == <span style="color: blue">null</span>)
                        {
                            <span style="color: green">// Возвращаем null вместо пустого объекта
</span>                            <span style="color: blue">return</span> <span style="color: blue">null</span>;
                        }
                        else
                        {
                            <span style="color: blue">return</span> { id: value.Id, entityType: value.LogicalName, name: value.Name };
                        }
                    <span style="color: blue">case</span> <span style="color: red">&quot;Microsoft.Crm.Sdk.Data.Services.Money&quot;</span>:
                    <span style="color: blue">case</span> <span style="color: red">&quot;Microsoft.Crm.Sdk.Data.Services.OptionSetValue&quot;</span>:
                        <span style="color: blue">return</span> value.Value;
                    <span style="color: blue">default</span>:
                        <span style="color: blue">return</span> value;
                }
            }
        }
        <span style="color: blue">return</span> value;
    }</pre></div>Она позволяет приводить EntityReference к JS объекту Lookup, а так же извлекает примитивное значение из объектов Money и OptionSetValue. Отмечу так же, что мой обработчик возвращает null вместо пустого EntityReference, как это делает стандартная библиотека.</div>

]]></content:encoded>
			<dc:creator>Артем Enot Грунин</dc:creator>
			<guid isPermaLink="true">//axforum.info/forums/blog.php?b=429</guid>
		</item>
		<item>
			<title>Как создать звонок или встречу для кастомного объекта</title>
			<link>//axforum.info/forums/blog.php?b=421</link>
			<pubDate>Wed, 28 Aug 2013 10:46:41 GMT</pubDate>
			<description><![CDATA[В одном из своих прошлых постов, я писал про сходства и отличия стандартных и кастомных типов действий: CRM 2011 Custom Activitys - Особенности (http://www.axforum.info/forums/blog.php?b=330). Тогда я коснулся только верхнеуровневых объектов действий, но не затрагивал такой интересный "подобъект" как "Стороны действия" (activity party). Что такое стороны действия, вы можете подробно почитать в SDK. Если кратко, все действия (кроме задачи) связывают несколько сторон, или участников. Например, действие «Электронная почта» имеет «Получателей» и «Отправителя», встреча имеет «Участников» и  «Организатора», и т.д. Все разновидности участников называются Сторонами действия. Кроме того, стороны привязываются к особым системным типам полей множественного выбора "PartyList". Это и есть те самые поля "Получатель" (почта), "Обязательные участники" (встреча) и пр.
  
  Впрочем, оставим этот ликбез и перейдем, собственно, к задаче. Недавно мой хороший знакомый и недавний коллега по MS Андрей Слепицкий обратился ко мне с вопросом: можно ли использовать кастомные объекты как стороны действия? Например, мы ввели в систему отдельную сущность Партнер и хотим позвать партнера на встречу. Сперва задача показалась мне простой: в чем проблема - разрешить действия в настройках объекта?:
  
  Вложение 341 (//axforum.info/forums/attachment.php?attachmentid=341)

  Однако, все оказалось сложнее. К сожалению, система позволяет выбор кастомного объекта только в поле "В отношении", но не в других полях, таких как "Обязательные участники" встречи. Небольшое исключение составляет форма Электронной почты. Если отметить вторую галочку: Sending e-mail, система позволит выбирать кастомный объект в поле "Получатель" (to) на форме электронной почты. Но как же быть с остальными полями?
  
  Для того чтобы исследовать феномен, я решил провести зловещий эксперимент. Для этого я модифицировал пример создания встречи из SDK Sample: Book an Appointment (http://msdn.microsoft.com/en-us/library/gg334289.aspx) чтобы добавить в список участников встречи свой кастомный объект:
  
              
              String crmconnection = "Server=http://crm/FixRM";
              CrmConnection connection = CrmConnection.Parse(crmconnection);
              OrganizationService service = new OrganizationService(connection);
  
              WhoAmIRequest userRequest = new WhoAmIRequest();
              WhoAmIResponse userResponse = (WhoAmIResponse) service.Execute(userRequest);
  
              // Create the ActivityParty instance.
              ActivityParty me = new ActivityParty
              {
                  PartyId = new EntityReference(SystemUser.EntityLogicalName, userResponse.UserId)
              };
  
              ActivityParty customparty= new ActivityParty
              {
                  //
                  PartyId = new EntityReference("fixrm_customparty", new Guid("6793AD07-E70E-E311-8E30-080027004A52"))
              };
  
              // Create the appointment instance.
              Appointment appointment = new Appointment
              {
                  Subject = "Test Appointment",
                  Description = "Test Appointment created using the BookRequest Message.",
                  ScheduledStart = DateTime.Now.AddHours(1),
                  ScheduledEnd = DateTime.Now.AddHours(2),
                  Location = "Office",
                  RequiredAttendees = new ActivityParty[] { me, customparty},
                  Organizer = new ActivityParty[] { me }
              };
  
              // Use the Book request message.
              Guid id = service.Create(appointment);  p.s. Для простоты тут я использую метод Create а не Book. 
  
  Результат эксперимента показал следующее:
  
* Встречу МОЖНО создать с      кастомным участником!
* НУЖНО включить для      объекта Sending e-mail, иначе вы получите ошибку "Invalid party type      code" при попытке добавить      его как участника
* НЕ ТРЕБУЕТСЯ      разрешать активности (Activities) для объекта, чтобы можно было добавить      кастомного участника

  Последний факт меня несколько удивил, с другой стороны, это, возможно, логично.
  
  Иными словами, использовать кастомные объекты в действиях можно, единственная проблема - это как-то добавить эту возможность в пользовательский интерфейс. И вот тут начинается небольшой, но грязный unsupport… Ниже к посту приложено неуправляемое решение, которое реализует искомую функциональность.
  
  Вложение 342 (//axforum.info/forums/attachment.php?attachmentid=342)
  
  Трюк заключается в том чтобы средствами JS DOM изменить атрибуты lookuptypes, lookuptypeIcons и lookuptypenames у нужного lookup контрола при загрузке формы. Как это часто бывает с ансаппортом, непонятно на что влияет последний атрибут - все работает и без него, однако его я для порядка тоже привожу в соответствие.
  
  if (typeof (FixRM) == "undefined")
  { FixRM = { __namespace: true }; }
  
  /*
  Events
  FixRM.CustomActivityParty.AddPartyTypeOnLoad
  */
  
  FixRM.CustomActivityParty = {
      AddPartyTypeOnLoad: function (settings)
      {
          for (var setting in settings)
          {
              var parties = settings[setting];
  
              for (var i = 0; i < parties.length; i++)
              {
                  var party = parties[i];
  
                  this.AddPartyType(setting, party.otc, party.schema, party.schemaName);
  
                  if (party.isDefault == true)
                  {
                      this.SetDefaultParty(setting, party.otc, party.DefaultViewId);
                  }
              }
          }
      },
  
      AddPartyType: function (name, otc, schema, schemaName)
      {
          function ApendAttributeValue(node, name, separator, value)
          {
              var attribute = node.getAttribute(name);
              var attributeValues = attribute.split(separator);
              attributeValues.push(value);
  
              attribute = attributeValues.join(separator);
              node.setAttribute(name, attribute);
          }
  
          var lookup = document.getElementById(name);
          if (lookup && lookup.attributes)
          {
              ApendAttributeValue(lookup, "lookuptypes", ",", otc);
  
              var icoPath = Xrm.Page.context.prependOrgName("/_Common/icon.aspx?cache=1&iconType=GridIcon&objectTypeCode=" + otc);
              ApendAttributeValue(lookup, "lookuptypeIcons", ":", icoPath);
  
              if (schema && schemaName)
              {
                  var lookuptypename = schema + ":" + otc + ":" + schemaName;
                  ApendAttributeValue(lookup, "lookuptypenames", ",", lookuptypename);
              }
          }
      },
  
      SetDefaultParty: function (name, otc, view)
      {
          var lookup = document.getElementById(name);
          if (lookup && lookup.attributes)
          {
              lookup.setAttribute("defaulttype", otc);
              if (view)
              {
                  lookup.setAttribute("defaultViewId", view);
              }
          }
      },
  
      __namespace: true
  };
  Второй неприятный момент заключается в том, что данный функционал активно использует числовой ObjectTypeCode, который, вообще-то deprecated и в следующих версиях должен быть полностью заменен на строковое свойство LogicalName. Исходя из этого, опасно кодировать подобную функциональность непосредственно в тексте программы. Для того чтобы облегчить переносимость, реализация принимает параметры из настроек обработчика события формы:
  
Вложение 343 (//axforum.info/forums/attachment.php?attachmentid=343)
  
  Настройки задаются как строка JSON:
  {
  имя поля1: [ массив добавляемых типов участников ],
  имя поля2: [ массив добавляемых типов участников ]
}
    Сами типы участники задаются в формате:
  {
  otc: <числовой код объекта>,
  schema: <логическое имя объекта>,
  schemaName: <имя схемы (так же имя датасета объекта при доступе через REST)>,
  isDefault: <сделать ли добавляемый тип типом по умолчанию при открытии окна лукапа. Опциональный параметр>,
  DefaultViewId: <представление по умолчанию при открытии окна лукапа. Опциональный параметр, можно не менять даже если изменен предыдущий>
}
  Приятно удивил тот факт, что система автоматически приводит текст JSON параметра к JS объекту, поэтому нет необходимости парсить его самостоятельно.
  
  *Заключение
*Ни я ни Андрей не смогли выяснить, является ли это решение поддерживаемым хотя бы частично. Визуальная часть - не поддерживается абсолютно! Остается вопрос, можно ли делать такие вещи через вызовы SDK. На мой взгляд это ограничение - не более чем недосмотр разработчиков. Время покажет!]]></description>
			<content:encoded><![CDATA[<div>В одном из своих прошлых постов, я писал про сходства и отличия стандартных и кастомных типов действий: <a href="http://www.axforum.info/forums/blog.php?b=330" target="_blank">CRM 2011 Custom Activitys - Особенности</a>. Тогда я коснулся только верхнеуровневых объектов действий, но не затрагивал такой интересный &quot;подобъект&quot; как &quot;Стороны действия&quot; (activity party). Что такое стороны действия, вы можете подробно почитать в SDK. Если кратко, все действия (кроме задачи) связывают несколько сторон, или участников. Например, действие «Электронная почта» имеет «Получателей» и «Отправителя», встреча имеет «Участников» и  «Организатора», и т.д. Все разновидности участников называются Сторонами действия. Кроме того, стороны привязываются к особым системным типам полей множественного выбора &quot;PartyList&quot;. Это и есть те самые поля &quot;Получатель&quot; (почта), &quot;Обязательные участники&quot; (встреча) и пр.<br />
  <br />
  Впрочем, оставим этот ликбез и перейдем, собственно, к задаче. Недавно мой хороший знакомый и недавний коллега по MS Андрей Слепицкий обратился ко мне с вопросом: можно ли использовать кастомные объекты как стороны действия? Например, мы ввели в систему отдельную сущность Партнер и хотим позвать партнера на встречу. Сперва задача показалась мне простой: в чем проблема - разрешить действия в настройках объекта?:<br />
  <br />
  <a href="//axforum.info/forums/blog_attachment.php?attachmentid=341&amp;d=1377686298" rel="Lightbox" id="attachment341" ><img src="//axforum.info/forums/blog_attachment.php?attachmentid=341&amp;thumb=1&amp;d=1377686298" class="thumbnail" border="0" alt="Нажмите на изображение для увеличения
Название: Settings.png
Просмотров: 2884
Размер:	15.5 Кб
ID:	341" style="margin: 2px" /></a><br />
<br />
  Однако, все оказалось сложнее. К сожалению, система позволяет выбор кастомного объекта только в поле &quot;В отношении&quot;, но не в других полях, таких как &quot;Обязательные участники&quot; встречи. Небольшое исключение составляет форма Электронной почты. Если отметить вторую галочку: Sending e-mail, система позволит выбирать кастомный объект в поле &quot;Получатель&quot; (to) на форме электронной почты. Но как же быть с остальными полями?<br />
  <br />
  Для того чтобы исследовать феномен, я решил провести зловещий эксперимент. Для этого я модифицировал пример создания встречи из SDK <a href="http://msdn.microsoft.com/en-us/library/gg334289.aspx" target="_blank">Sample: Book an Appointment</a> чтобы добавить в список участников встречи свой кастомный объект:<br />
  <br />
              <div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">              String crmconnection = <span style="color: red">&quot;Server=http://crm/FixRM&quot;</span>;
              CrmConnection connection = CrmConnection.Parse(crmconnection);
              OrganizationService service = <span style="color: blue">new</span> OrganizationService(connection);
  
              WhoAmIRequest userRequest = <span style="color: blue">new</span> WhoAmIRequest();
              WhoAmIResponse userResponse = (WhoAmIResponse) service.Execute(userRequest);
  
              <span style="color: green">// Create the ActivityParty instance.
</span>              ActivityParty me = <span style="color: blue">new</span> ActivityParty
              {
                  PartyId = <span style="color: blue">new</span> EntityReference(SystemUser.EntityLogicalName, userResponse.UserId)
              };
  
              ActivityParty customparty= <span style="color: blue">new</span> ActivityParty
              {
                  <span style="color: green">//
</span>                  PartyId = <span style="color: blue">new</span> EntityReference(<span style="color: red">&quot;fixrm_customparty&quot;</span>, <span style="color: blue">new</span> Guid(<span style="color: red">&quot;6793AD07-E70E-E311-8E30-080027004A52&quot;</span>))
              };
  
              <span style="color: green">// Create the appointment instance.
</span>              Appointment appointment = <span style="color: blue">new</span> Appointment
              {
                  Subject = <span style="color: red">&quot;Test Appointment&quot;</span>,
                  Description = <span style="color: red">&quot;Test Appointment created using the BookRequest Message.&quot;</span>,
                  ScheduledStart = DateTime.Now.AddHours(1),
                  ScheduledEnd = DateTime.Now.AddHours(2),
                  Location = <span style="color: red">&quot;Office&quot;</span>,
                  RequiredAttendees = <span style="color: blue">new</span> ActivityParty[] { me, customparty},
                  Organizer = <span style="color: blue">new</span> ActivityParty[] { me }
              };
  
              <span style="color: green">// Use the Book request message.
</span>              Guid id = service.Create(appointment);</pre></div>  p.s. Для простоты тут я использую метод Create а не Book. <br />
  <br />
  Результат эксперимента показал следующее:<br />
  <ul><li>Встречу МОЖНО создать с      кастомным участником!</li>
<li>НУЖНО включить для      объекта Sending e-mail, иначе вы получите ошибку &quot;Invalid party type      code&quot; при попытке добавить      его как участника</li>
<li>НЕ ТРЕБУЕТСЯ      разрешать активности (Activities) для объекта, чтобы можно было добавить      кастомного участника</li>
</ul>  Последний факт меня несколько удивил, с другой стороны, это, возможно, логично.<br />
  <br />
  Иными словами, использовать кастомные объекты в действиях можно, единственная проблема - это как-то добавить эту возможность в пользовательский интерфейс. И вот тут начинается небольшой, но грязный unsupport… Ниже к посту приложено неуправляемое решение, которое реализует искомую функциональность.<br />
  <br />
  <a href="//axforum.info/forums/blog_attachment.php?attachmentid=342&amp;d=1377686298" rel="Lightbox" id="attachment342" ><img src="//axforum.info/forums/blog_attachment.php?attachmentid=342&amp;thumb=1&amp;d=1377686298" class="thumbnail" border="0" alt="Нажмите на изображение для увеличения
Название: appointment.png
Просмотров: 2767
Размер:	93.1 Кб
ID:	342" style="margin: 2px" /></a><br />
  <br />
  Трюк заключается в том чтобы средствами JS DOM изменить атрибуты lookuptypes, lookuptypeIcons и lookuptypenames у нужного lookup контрола при загрузке формы. Как это часто бывает с ансаппортом, непонятно на что влияет последний атрибут - все работает и без него, однако его я для порядка тоже привожу в соответствие.<br />
  <br />
  <div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code"><span style="color: blue">if</span> (typeof (FixRM) == <span style="color: red">&quot;undefined&quot;</span>)
  { FixRM = { __namespace: <span style="color: blue">true</span> }; }
  
  <span style="color: green">/*
  Events
  FixRM.CustomActivityParty.AddPartyTypeOnLoad
  */</span>
  
  FixRM.CustomActivityParty = {
      AddPartyTypeOnLoad: function (settings)
      {
          <span style="color: blue">for</span> (var <span style="color: blue">setting</span> in settings)
          {
              var parties = settings[<span style="color: blue">setting</span>];
  
              <span style="color: blue">for</span> (var i = 0; i &lt; parties.length; i++)
              {
                  var party = parties[i];
  
                  this.AddPartyType(<span style="color: blue">setting</span>, party.otc, party.schema, party.schemaName);
  
                  <span style="color: blue">if</span> (party.isDefault == <span style="color: blue">true</span>)
                  {
                      this.SetDefaultParty(<span style="color: blue">setting</span>, party.otc, party.DefaultViewId);
                  }
              }
          }
      },
  
      AddPartyType: function (name, otc, schema, schemaName)
      {
          function ApendAttributeValue(node, name, separator, value)
          {
              var attribute = node.getAttribute(name);
              var attributeValues = attribute.split(separator);
              attributeValues.push(value);
  
              attribute = attributeValues.<span style="color: blue">join</span>(separator);
              node.setAttribute(name, attribute);
          }
  
          var lookup = document.getElementById(name);
          <span style="color: blue">if</span> (lookup &amp;&amp; lookup.attributes)
          {
              ApendAttributeValue(lookup, <span style="color: red">&quot;lookuptypes&quot;</span>, <span style="color: red">&quot;,&quot;</span>, otc);
  
              var icoPath = Xrm.Page.context.prependOrgName(<span style="color: red">&quot;/_Common/icon.aspx?cache=1&amp;iconType=GridIcon&amp;objectTypeCode=&quot;</span> + otc);
              ApendAttributeValue(lookup, <span style="color: red">&quot;lookuptypeIcons&quot;</span>, <span style="color: red">&quot;:&quot;</span>, icoPath);
  
              <span style="color: blue">if</span> (schema &amp;&amp; schemaName)
              {
                  var lookuptypename = schema + <span style="color: red">&quot;:&quot;</span> + otc + <span style="color: red">&quot;:&quot;</span> + schemaName;
                  ApendAttributeValue(lookup, <span style="color: red">&quot;lookuptypenames&quot;</span>, <span style="color: red">&quot;,&quot;</span>, lookuptypename);
              }
          }
      },
  
      SetDefaultParty: function (name, otc, view)
      {
          var lookup = document.getElementById(name);
          <span style="color: blue">if</span> (lookup &amp;&amp; lookup.attributes)
          {
              lookup.setAttribute(<span style="color: red">&quot;defaulttype&quot;</span>, otc);
              <span style="color: blue">if</span> (view)
              {
                  lookup.setAttribute(<span style="color: red">&quot;defaultViewId&quot;</span>, view);
              }
          }
      },
  
      __namespace: true
  };</pre></div>Второй неприятный момент заключается в том, что данный функционал активно использует числовой ObjectTypeCode, который, вообще-то deprecated и в следующих версиях должен быть полностью заменен на строковое свойство LogicalName. Исходя из этого, опасно кодировать подобную функциональность непосредственно в тексте программы. Для того чтобы облегчить переносимость, реализация принимает параметры из настроек обработчика события формы:<br />
  <br />
<a href="//axforum.info/forums/blog_attachment.php?attachmentid=343&amp;d=1377686487" rel="Lightbox" id="attachment343" ><img src="//axforum.info/forums/blog_attachment.php?attachmentid=343&amp;thumb=1&amp;d=1377686487" class="thumbnail" border="0" alt="Нажмите на изображение для увеличения
Название: event.png
Просмотров: 2831
Размер:	28.3 Кб
ID:	343" style="margin: 2px" /></a><br />
  <br />
  Настройки задаются как строка JSON:<br />
  <div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">{
   1: [     ],
   2: [     ]
}</pre></div>  Сами типы участники задаются в формате:<br />
  <div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">{
  otc: &lt;  &gt;,
  schema: &lt;  &gt;,
  schemaName: &lt;  (        REST)&gt;,
  isDefault: &lt;          .  &gt;,
  DefaultViewId: &lt;      .  ,       &gt;
}</pre></div>Приятно удивил тот факт, что система автоматически приводит текст JSON параметра к JS объекту, поэтому нет необходимости парсить его самостоятельно.<br />
  <br />
  <b>Заключение<br />
</b>Ни я ни Андрей не смогли выяснить, является ли это решение поддерживаемым хотя бы частично. Визуальная часть - не поддерживается абсолютно! Остается вопрос, можно ли делать такие вещи через вызовы SDK. На мой взгляд это ограничение - не более чем недосмотр разработчиков. Время покажет!</div>


<!-- attachments -->
	<div style="margin-top:10px">

		
		
		
		
			<fieldset class="fieldset">
				<legend>Вложения</legend>
				<table cellpadding="0" cellspacing="3" border="0">
				<tr>
	<td><img class="inlineimg" src="http://axforum.info//img.axforum.info/attach/zip.gif" alt="Тип файла: zip" width="16" height="16" border="0" style="vertical-align:baseline" /></td>
	<td><a href="//axforum.info/forums/blog_attachment.php?attachmentid=344&amp;d=1377687412">FixRMCustomActivityParty_1_0_0_0.zip</a> (21.0 Кб, 2578 просмотров)</td>
</tr>
				</table>
			</fieldset>
		

	</div>
<!-- / attachments -->
]]></content:encoded>
			<dc:creator>Артем Enot Грунин</dc:creator>
			<guid isPermaLink="true">//axforum.info/forums/blog.php?b=421</guid>
		</item>
		<item>
			<title>Ошибка в диаграммах (charts) при использовании группировки по дате</title>
			<link>//axforum.info/forums/blog.php?b=419</link>
			<pubDate>Fri, 23 Aug 2013 07:21:00 GMT</pubDate>
			<description><![CDATA[Недавно я столкнулся с ошибкой при отображении некоторых (в том числе стандартных!) диаграмм: 
  
  Вложение 340 (//axforum.info/forums/attachment.php?attachmentid=340)
  
  Сперва я грешил на кривые запросы или раздачу прав доступа, но позже выяснилось, что проблема наблюдается только на производственном сервере. Я отловил запрос, который запускается при прорисовке диаграммы при помощи SQL Profiler и запустил его выполнение вручную. Результат превзошел все мои ожидания:
  
---Цитата---
An error occurred in the Microsoft .NET Framework while trying to load assembly id 65536. The server may be running out of resources, or the assembly may not be trusted with PERMISSION_SET = EXTERNAL_ACCESS or UNSAFE. Run the query again, or check documentation to see how to solve the assembly trust issues. For more information about this error: 
  System.IO.FileLoadException: Could not load file or assembly 'microsoft.crm.sqlclr.helper, Version=5.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' or one of its dependencies. Exception from HRESULT: 0x80FC80F1
  System.IO.FileLoadException: 
     at System.Reflection.RuntimeAssembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, RuntimeAssembly locationHint, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks)
     at System.Reflection.RuntimeAssembly.InternalLoadAssemblyName(AssemblyName assemblyRef, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection, Boolean suppressSecurityChecks)
     at System.Reflection.RuntimeAssembly.InternalLoad(String assemblyString, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection)
     at System.Reflection.Assembly.Load(String assemblyString)
---Конец цитаты---
Старая добрая ошибка от хранимых CLR процедур, о которой я уже писал недавно: Проблемы с SSIS после обновлений .NET (http://www.axforum.info/forums/blog.php?b=412). Вы могли бы спросить, причем же тут CLR? Ответ хранится в запросе к базе: в нем используются CLR функции, например fn_GetFiscalPeriodAndYearCLR. Однако радоваться было рано! Это оказалась не известная мне проблема несовпадения версий, так как в GAC нет сборки microsoft.crm.sqlclr.helper! Проблема уходит глубоко в неисследованную мной тему безопасности SQL Server: http://support.microsoft.com/kb/918040.
  
  Решение заключается в том чтобы прописать базе корректного dbowner и сделать ее trustworthy. Сразу скажу, что   владелец и так был вполне себе корректный, и все рабочие базы ни разу не trustworthy. Как бы там ни было, приведенный в статье базы знаний запрос решил проблему:
  
  ALTER DATABASE Org_MSCRM SET trustworthy ON
  
USE Org_MSCRM
EXEC sp_changedbowner 'DOMAIN\login'  Попытка выключить trustworthy приведет к возвращению ошибки]]></description>
			<content:encoded><![CDATA[<div>Недавно я столкнулся с ошибкой при отображении некоторых (в том числе стандартных!) диаграмм: <br />
  <br />
  <img src="//axforum.info/forums/blog_attachment.php?attachmentid=340&amp;d=1377155859" border="0" alt="Название: charterror.png
Просмотров: 115502

Размер: 7.7 Кб" style="margin: 2px" /><br />
  <br />
  Сперва я грешил на кривые запросы или раздачу прав доступа, но позже выяснилось, что проблема наблюдается только на производственном сервере. Я отловил запрос, который запускается при прорисовке диаграммы при помощи SQL Profiler и запустил его выполнение вручную. Результат превзошел все мои ожидания:<br />
  <div class="q">
	<div class="smallfont q_title">Цитата:</div>
	<div class="alt2 q_body">
		
			An error occurred in the Microsoft .NET Framework while trying to load assembly id 65536. The server may be running out of resources, or the assembly may not be trusted with PERMISSION_SET = EXTERNAL_ACCESS or UNSAFE. Run the query again, or check documentation to see how to solve the assembly trust issues. For more information about this error: <br />
  System.IO.FileLoadException: Could not load file or assembly 'microsoft.crm.sqlclr.helper, Version=5.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' or one of its dependencies. Exception from HRESULT: 0x80FC80F1<br />
  System.IO.FileLoadException: <br />
     at System.Reflection.RuntimeAssembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, RuntimeAssembly locationHint, StackCrawlMark&amp; stackMark, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks)<br />
     at System.Reflection.RuntimeAssembly.InternalLoadAssemblyName(AssemblyName assemblyRef, Evidence assemblySecurity, StackCrawlMark&amp; stackMark, Boolean forIntrospection, Boolean suppressSecurityChecks)<br />
     at System.Reflection.RuntimeAssembly.InternalLoad(String assemblyString, Evidence assemblySecurity, StackCrawlMark&amp; stackMark, Boolean forIntrospection)<br />
     at System.Reflection.Assembly.Load(String assemblyString)
		
	</div>
</div>Старая добрая ошибка от хранимых CLR процедур, о которой я уже писал недавно: <a href="http://www.axforum.info/forums/blog.php?b=412" target="_blank">Проблемы с SSIS после обновлений .NET</a>. Вы могли бы спросить, причем же тут CLR? Ответ хранится в запросе к базе: в нем используются CLR функции, например fn_GetFiscalPeriodAndYearCLR. Однако радоваться было рано! Это оказалась не известная мне проблема несовпадения версий, так как в GAC нет сборки microsoft.crm.sqlclr.helper! Проблема уходит глубоко в неисследованную мной тему безопасности SQL Server: <a href="http://support.microsoft.com/kb/918040" target="_blank">http://support.microsoft.com/kb/918040</a>.<br />
  <br />
  Решение заключается в том чтобы прописать базе корректного dbowner и сделать ее trustworthy. Сразу скажу, что   владелец и так был вполне себе корректный, и все рабочие базы ни разу не trustworthy. Как бы там ни было, приведенный в статье базы знаний запрос решил проблему:<br />
  <br />
  <div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">ALTER DATABASE Org_MSCRM SET trustworthy ON
  
USE Org_MSCRM
EXEC sp_changedbowner <span style="color: red">'DOMAIN\login'</span></pre></div>  Попытка выключить trustworthy приведет к возвращению ошибки</div>

]]></content:encoded>
			<dc:creator>Артем Enot Грунин</dc:creator>
			<guid isPermaLink="true">//axforum.info/forums/blog.php?b=419</guid>
		</item>
		<item>
			<title>Автоматическое удаление завершенных асинхронных заданий</title>
			<link>//axforum.info/forums/blog.php?b=418</link>
			<pubDate>Thu, 22 Aug 2013 03:00:00 GMT</pubDate>
			<description><![CDATA[Вы могли обратить внимание, на опцию "Автоматически удалять завершенные задания бизнес-процесса" на форме Процесса в CRM 2011:
  
  Вложение 336 (//axforum.info/forums/attachment.php?attachmentid=336)
  
  Надо отметить, что место для этого поля выбрано не самое удачное, неудивительно что многие его не замечают. 
  
  Аналогичная опция есть и в Plugin Registration Tool (доступна для только асинхронных плагинов):
  
  Вложение 337 (//axforum.info/forums/attachment.php?attachmentid=337)
  
  Однако, ее, почему-то нет на форме регистрации шага в CRM Developer Toolkit:
  
  Вложение 338 (//axforum.info/forums/attachment.php?attachmentid=338)

Честно говоря, я вообще не понимаю, почему разработчики не использовали готовые формы и код Plugin Registration Tool при разработке CRM Developer Toolkit. Но, к счастью, такая опция в нем все равно поддерживается!
  
  Для этого необходимо отредактировать файл RegisterFile.crmregister и в нужные узлы <Step/> добавить атрибут AsyncAutoDelete="true":
  
  Вложение 339 (//axforum.info/forums/attachment.php?attachmentid=339)
  
  При этом ничего страшного, если вы установите этот атрибут для синхронного плагина - ошибки не будет.
  
  p.s. Ручная правка этого конфига достаточно часто бывает полезна, в том числе чтобы избавиться от дуратских авто генерируемых имен плагинов и описаний. К сожалению, версия CRM Developer Toolkit для VS 2012, в отличие от версии для VS 2010 не производит валидацию конфига по схеме, поэтому подсказок при вводе не будет.]]></description>
			<content:encoded><![CDATA[<div>Вы могли обратить внимание, на опцию &quot;Автоматически удалять завершенные задания бизнес-процесса&quot; на форме Процесса в CRM 2011:<br />
  <br />
  <a href="//axforum.info/forums/blog_attachment.php?attachmentid=336&amp;d=1377075215" rel="Lightbox" id="attachment336" ><img src="//axforum.info/forums/blog_attachment.php?attachmentid=336&amp;thumb=1&amp;d=1377075215" class="thumbnail" border="0" alt="Нажмите на изображение для увеличения
Название: Workflow.png
Просмотров: 4233
Размер:	54.1 Кб
ID:	336" style="margin: 2px" /></a><br />
  <br />
  Надо отметить, что место для этого поля выбрано не самое удачное, неудивительно что многие его не замечают. <br />
  <br />
  Аналогичная опция есть и в Plugin Registration Tool (доступна для только асинхронных плагинов):<br />
  <br />
  <a href="//axforum.info/forums/blog_attachment.php?attachmentid=337&amp;d=1377075215" rel="Lightbox" id="attachment337" ><img src="//axforum.info/forums/blog_attachment.php?attachmentid=337&amp;thumb=1&amp;d=1377075215" class="thumbnail" border="0" alt="Нажмите на изображение для увеличения
Название: RegTool.png
Просмотров: 4604
Размер:	17.3 Кб
ID:	337" style="margin: 2px" /></a><br />
  <br />
  Однако, ее, почему-то нет на форме регистрации шага в CRM Developer Toolkit:<br />
  <br />
  <a href="//axforum.info/forums/blog_attachment.php?attachmentid=338&amp;d=1377075275" rel="Lightbox" id="attachment338" ><img src="//axforum.info/forums/blog_attachment.php?attachmentid=338&amp;thumb=1&amp;d=1377075275" class="thumbnail" border="0" alt="Нажмите на изображение для увеличения
Название: Toolkit.png
Просмотров: 4476
Размер:	49.4 Кб
ID:	338" style="margin: 2px" /></a><br />
<br />
Честно говоря, я вообще не понимаю, почему разработчики не использовали готовые формы и код Plugin Registration Tool при разработке CRM Developer Toolkit. Но, к счастью, такая опция в нем все равно поддерживается!<br />
  <br />
  Для этого необходимо отредактировать файл RegisterFile.crmregister и в нужные узлы &lt;Step/&gt; добавить атрибут AsyncAutoDelete=&quot;true&quot;:<br />
  <br />
  <a href="//axforum.info/forums/blog_attachment.php?attachmentid=339&amp;d=1377075275" rel="Lightbox" id="attachment339" ><img src="//axforum.info/forums/blog_attachment.php?attachmentid=339&amp;thumb=1&amp;d=1377075275" class="thumbnail" border="0" alt="Нажмите на изображение для увеличения
Название: Config.png
Просмотров: 4499
Размер:	15.5 Кб
ID:	339" style="margin: 2px" /></a><br />
  <br />
  При этом ничего страшного, если вы установите этот атрибут для синхронного плагина - ошибки не будет.<br />
  <br />
  p.s. Ручная правка этого конфига достаточно часто бывает полезна, в том числе чтобы избавиться от дуратских авто генерируемых имен плагинов и описаний. К сожалению, версия CRM Developer Toolkit для VS 2012, в отличие от версии для VS 2010 не производит валидацию конфига по схеме, поэтому подсказок при вводе не будет.</div>

]]></content:encoded>
			<dc:creator>Артем Enot Грунин</dc:creator>
			<guid isPermaLink="true">//axforum.info/forums/blog.php?b=418</guid>
		</item>
		<item>
			<title>Агрегированные Fetch запросы могут не вернуть данных</title>
			<link>//axforum.info/forums/blog.php?b=417</link>
			<pubDate>Wed, 21 Aug 2013 06:29:23 GMT</pubDate>
			<description><![CDATA[Недавно я посмотрел статистику выполнения по своим плагинам и обнаружил что один из них иногда валится с ошибкой:

SELECT
    [plugintypeidname],
    [averageexecutetimeinmilliseconds],
    [executecount],
    [failurecount],
    [failurepercent]
FROM FilteredPluginTypeStatistic
Порыскав в логах системных заданий я выяснил что в некоторых случаях плагин валится с ошибкой "given key was not present in the dictionary", что казалось странным, так как казалось что все необходимые проверки я выполнил. Беда крылась в получении значения агрегата в Fetch запросе. Рассмотрим пример из SDK:

                    string estimatedvalue_avg = @" 
                    <fetch distinct='false' mapping='logical' aggregate='true'> 
                        <entity name='opportunity'> 
                           <attribute name='estimatedvalue' alias='estimatedvalue_avg' aggregate='avg' /> 
                        </entity> 
                    </fetch>";

                    EntityCollection estimatedvalue_avg_result = _serviceProxy.RetrieveMultiple(new FetchExpression(estimatedvalue_avg));

                    foreach (var c in estimatedvalue_avg_result.Entities)
                    {
                        decimal aggregate1 = ((Money)((AliasedValue)c["estimatedvalue_avg"]).Value).Value;
                        System.Console.WriteLine("Average estimated value: " + aggregate1);

                    }
В данном случае не принципиально для чего к коде цикл - запрос все равно вернет только 1 запись Entity. У возвращенного объекта будет единственный атрибут estimatedvalue_avg, который за каким-то хреном завернут в объект AliasedValue. Так вот момент заключается в том, что атрибуты-агригаты, равно как и все прочие столбцы, возвращаются системой только в том случае, если они содержат данные! В противном случае, такого столбца в возвращенном объекте не будет. Если запустить этот пример на пустой базе, вы получите ошибку.]]></description>
			<content:encoded><![CDATA[<div>Недавно я посмотрел статистику выполнения по своим плагинам и обнаружил что один из них иногда валится с ошибкой:<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">SELECT
    [plugintypeidname],
    [averageexecutetimeinmilliseconds],
    [executecount],
    [failurecount],
    [failurepercent]
<span style="color: blue">FROM</span> FilteredPluginTypeStatistic</pre></div>Порыскав в логах системных заданий я выяснил что в некоторых случаях плагин валится с ошибкой &quot;given key was not present in the dictionary&quot;, что казалось странным, так как казалось что все необходимые проверки я выполнил. Беда крылась в получении значения агрегата в Fetch запросе. Рассмотрим пример из SDK:<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">                    string estimatedvalue_avg = @<span style="color: red">&quot; 
                    &lt;fetch distinct='false' mapping='logical' aggregate='true'&gt; 
                        &lt;entity name='opportunity'&gt; 
                           &lt;attribute name='estimatedvalue' alias='estimatedvalue_avg' aggregate='avg' /&gt; 
                        &lt;/entity&gt; 
                    &lt;/fetch&gt;&quot;</span>;

                    EntityCollection estimatedvalue_avg_result = _serviceProxy.RetrieveMultiple(<span style="color: blue">new</span> FetchExpression(estimatedvalue_avg));

                    foreach (var c in estimatedvalue_avg_result.Entities)
                    {
                        decimal aggregate1 = ((Money)((AliasedValue)c[<span style="color: red">&quot;estimatedvalue_avg&quot;</span>]).Value).Value;
                        System.Console.WriteLine(<span style="color: red">&quot;Average estimated value: &quot;</span> + aggregate1);

                    }</pre></div>В данном случае не принципиально для чего к коде цикл - запрос все равно вернет только 1 запись Entity. У возвращенного объекта будет единственный атрибут estimatedvalue_avg, который за каким-то хреном завернут в объект AliasedValue. Так вот момент заключается в том, что атрибуты-агригаты, равно как и все прочие столбцы, возвращаются системой только в том случае, если они содержат данные! В противном случае, такого столбца в возвращенном объекте не будет. Если запустить этот пример на пустой базе, вы получите ошибку.</div>

]]></content:encoded>
			<dc:creator>Артем Enot Грунин</dc:creator>
			<guid isPermaLink="true">//axforum.info/forums/blog.php?b=417</guid>
		</item>
		<item>
			<title>Как опредлить кому назначена запись: пользователю или команде в рабочем процессе (workflow)</title>
			<link>//axforum.info/forums/blog.php?b=416</link>
			<pubDate>Tue, 20 Aug 2013 05:05:56 GMT</pubDate>
			<description><![CDATA[Недавно на форуме задавался вопрос: "как отправить письмо группе? (http://www.axforum.info/forums/showthread.php?t=48167)". Разгорелся достаточно жаркий спор, где высказали много и полезного и нет, однако мы не коснулись другого вопроса: как вообще понять что запись назначили группе, а не пользователю, например? Столкнувшись недавно с такой задачей, я нашел следующее решение:
 
Вложение 335 (//axforum.info/forums/attachment.php?attachmentid=335)
 
p.s. Недавно я обнаружил в логах системы, что время от времени падает моя активность процесса, о которой я уже как-то раз писал:Мультиязычный CRM. Уведомления из рабочих процессов на языке пользователя. (http://www.axforum.info/forums/blog.php?b=404)
Оказалось что проблема связана с тем, что назначение происходит группе, а не пользователю. Чтобы этого избежать, в код была добавлена проверка, однако это не избавляет нас от необходимости проверять это условие в процессе, иначе, могут быть ошибки и в других шагах, если вы ожидаете в них что запись назначена пользователю. Например, хотите выслать ему письмо.]]></description>
			<content:encoded><![CDATA[<div>Недавно на форуме задавался вопрос: &quot;к<a href="http://www.axforum.info/forums/showthread.php?t=48167" target="_blank">ак отправить письмо группе?</a>&quot;. Разгорелся достаточно жаркий спор, где высказали много и полезного и нет, однако мы не коснулись другого вопроса: как вообще понять что запись назначили группе, а не пользователю, например? Столкнувшись недавно с такой задачей, я нашел следующее решение:<br />
 <br />
<a href="//axforum.info/forums/blog_attachment.php?attachmentid=335&amp;d=1376974949" rel="Lightbox" id="attachment335" ><img src="//axforum.info/forums/blog_attachment.php?attachmentid=335&amp;thumb=1&amp;d=1376974949" class="thumbnail" border="0" alt="Нажмите на изображение для увеличения
Название: Condition.png
Просмотров: 3777
Размер:	7.1 Кб
ID:	335" style="margin: 2px" /></a><br />
 <br />
p.s. Недавно я обнаружил в логах системы, что время от времени падает моя активность процесса, о которой я уже как-то раз писал:<a href="http://www.axforum.info/forums/blog.php?b=404" target="_blank">Мультиязычный CRM. Уведомления из рабочих процессов на языке пользователя.</a><br />
Оказалось что проблема связана с тем, что назначение происходит группе, а не пользователю. Чтобы этого избежать, в код была добавлена проверка, однако это не избавляет нас от необходимости проверять это условие в процессе, иначе, могут быть ошибки и в других шагах, если вы ожидаете в них что запись назначена пользователю. Например, хотите выслать ему письмо.</div>

]]></content:encoded>
			<dc:creator>Артем Enot Грунин</dc:creator>
			<guid isPermaLink="true">//axforum.info/forums/blog.php?b=416</guid>
		</item>
	</channel>
</rss>
