Записки на лету

Всем привет,

Вот и релиз подоспел. Можно скачать по ссылке Dynamics 365 Server, версия 9.0

Триальный ключ на 90 дней: KKNV2-4YYK8-D8HWD-GDRMW-29YTW

Повторяю, что с выходом 9-ки также пропадет SDK как отдельный скачиваемый пакет и теперь инструментарий для разработчика будет доступен по частям и называться Developer Guide for Dynamics 365 Customer Engagement, подробнее здесь What’s new for Customer Engagement developer documentation in version 9.0.

Всем привет,
Вышла превью MS Dynamics 365 (on-premises), Version 9.0.2.
Скачать можно на сайте Dynamics Insider Program.
Регистрируетесь, заполняете анкету и, через некоторое время, станут доступны ссылки для скачивания.

С выходом 9-ки также пропадет SDK как отдельный скачиваемый пакет и теперь инструментарий для разработчика будет доступен по частям и называться Developer Guide for Dynamics 365 Customer Engagement, подробнее здесь What’s new for Customer Engagement developer documentation in version 9.0.

На днях обнаружил быстрый способ узнать EntityTypeCode по имени сущности (SchemaName/LogicalName) через WebAPI

http://crmserver/api/data/v8.1/EntityDefinitions?$select=ObjectTypeCode&$filter=SchemaName eq 'Account'

И, соответственно, наоборот имя сущности (SchemaName/LogicalName) по EntityTypeCode

http://crmserver/api/data/v8.1/EntityDefinitions?$select=SchemaName&$filter=ObjectTypeCode eq 1

Также, для получения TypeCode можно использовать JScript

var accountEntityTypeCode = Mscrm.EntityPropUtil.EntityTypeName2CodeMap["account"];

 

Наконец-то дописал свое приложение под новую платформу Universal Windows Platform.

Всем, у кого есть планшет или телефон на Windows 10 просьба опробовать.
https://www.microsoft.com/store/apps/9WZDNCRDQXSK
Буду рад любым отзывам.

С версии 2013 появилось несколько новых методов нотификации пользователя при работе на форме. Расскажу про них и в каких случаях можно их использовать.

1. Поддерживаемая версия alert().

Раньше мы использовали стандартный alert("Сообщение!");

Теперь у нас есть два варианта:

  1. Простое сообщение: Xrm.Utility.alertDialog(message,onCloseCallback);
    где onCloseCallback колбэк функция выполняемая после нажатия кнопки ОК окошка сообщения.
    Пример использования (Проверять на загрузке формы заполнение поля и если оно не заполнено то просить его заполнить и ставить в него фокус):

    function formOnLoad() {
        /// <summary>Функция, вызываемая при загрузки формы</summary>
        if (Xrm.Page.getAttribute("mobilephone") && !Xrm.Page.getAttribute("mobilephone").getValue()) {
            Xrm.Utility.alertDialog("Не заполнен мобильный телефон",
                function () {
                    Xrm.Page.getControl("mobilephone").setFocus();
                });
        }
    }

    AlertMessage

  2. Диалог подтверждения: Xrm.Utility.confirmDialog(message,yesCloseCallback,noCloseCallback);
    где yesCloseCallback колбэк функция выполняемая после нажатия кнопки ОК окошка сообщения и noCloseCallback колбэк функция выполняемая после нажатия кнопки ОК окошка сообщения.
    Пример использования (Проверять на сохранении формы заполнение поля и если оно не заполнено то предложить его заполнить и ставить в него фокус):

    function formOnSave(context) {
        /// <summary>Функция, вызываемая при сохранении формы</summary>  
        if (Xrm.Page.getAttribute("mobilephone") && !Xrm.Page.getAttribute("mobilephone").getValue()) {
            Xrm.Utility.confirmDialog("Не заполнен мобильный телефон. Заполнить?",
                // Если нажали ОК
                function () {
                    // Ставим фокус
                    Xrm.Page.getControl("mobilephone").setFocus();
                    //Отменяем сохранение
                    var eventArgs = context.getEventArgs();
                    eventArgs.preventDefault();
                },
                // Если нажали Cancel
                function () {
                    // Ничего не делаем
                });
        }
    }

    AlertDialogMessage

2. Панель нотификации.

Тоже самое можно реализовать с помощью панели нотификации.

Xrm.Page.ui.setFormNotification(message, level, uniqueId);

где:

  • message (string) — сам текст сообщения.
  • level (string) — тип иконки, возможные варианты: «ERROR» , «WARNING», «INFO».
  • uniqueId (string) — идентификатор сообщения (используется для убирания конкретного сообщения).

При добавлении исполнении следующих строк:

Xrm.Page.ui.setFormNotification('Произошла ошибка...!', 'ERROR', 'id1');
Xrm.Page.ui.setFormNotification('Предупреждение...', 'WARNING', 'id2');
Xrm.Page.ui.setFormNotification('Для информации', 'INFORMATION', 'id3');

Мы увидим следующее.

NotificationMessages

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

Скрываются сообщения так:

Xrm.Page.ui.clearFormNotification('id1');  // Сообщение с идентификатором 'id1'

Попробуем реализовать пример выше с использованием понели нотификации. Нам понадобятся две функции, на загрузку формы formOnLoad() и на изменение поля Мобильный телефон mobilePhoneOnChange().

function formOnLoad() {
    /// <summary>Функция, вызываемая при загрузки формы</summary>
    if (Xrm.Page.getAttribute("mobilephone") && !Xrm.Page.getAttribute("mobilephone").getValue()) {
        Xrm.Page.ui.setFormNotification("Не заполнен мобильный телефон", 'WARNING', "mobilephoneAlert");
    }
}

function mobilePhoneOnChange() {
    /// <summary>Функция, вызываемая при изменении поля телефон</summary>
    if (Xrm.Page.getAttribute("mobilephone") && !Xrm.Page.getAttribute("mobilephone").getValue()) {
        Xrm.Page.ui.setFormNotification("Не заполнен мобильный телефон", 'WARNING', "mobilephoneAlert");
    } else {
        Xrm.Page.ui.clearFormNotification("mobilephoneAlert");
    }
}

Выглядеть будет так.NotificationMessages2

3. Нотификация поля.

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

Xrm.Page.getControl(fieldName).setNotification(message, uniqueId);  // Показать сообщение
Xrm.Page.getControl(fieldName).clearNotification(uniqueId);         // Скрыть сообщение

где:

  • fieldName (string) — имя поля.
  • message (string) — текст сообщния.
  • uniqueId (string) — идентификатор сообщения (используется для убирания конкретного сообщения).

Выглядит так:NotificationMessages3

Кстати, пока Вы видите такое сообщение CRM не даст Вам сохранить форму.

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

function formOnLoad() {
    /// <summary>Функция, вызываемая при загрузки формы</summary>
    if (Xrm.Page.getAttribute("mobilephone") && !Xrm.Page.getAttribute("mobilephone").getValue()) {
        Xrm.Page.getControl("mobilephone").setNotification("Не заполнен мобильный телефон", "EmptyMobilephoneAlert");
    }
    else {
        Xrm.Page.getControl("mobilephone").clearNotification("EmptyMobilephoneAlert");
    }
}

function mobilePhoneOnChange() {
    /// <summary>Функция, вызываемая при изменении поля телефон</summary>
    if (Xrm.Page.getAttribute("mobilephone")) {
        if (!Xrm.Page.getAttribute("mobilephone").getValue()) {
            Xrm.Page.getControl("mobilephone").setNotification("Не заполнен мобильный телефон", "EmptyMobilephoneAlert");
        } else {
            Xrm.Page.getControl("mobilephone").clearNotification("EmptyMobilephoneAlert");
        }
    }
}

Такого же результата мы можем достигнуть создав «Бизнес-правило».AlertBusinessRule

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

 

function pbl_eb4eb395d711e61180ce00155de610f8() {
    try {
        var v0 = Xrm.Page.data.entity.attributes.get('mobilephone');
        var v2 = '';
        if ((v0 == undefined || v0 == null || v0 === ""))
        { return; }
        var v1 = v0.getValue();
        if ((v1) == undefined || (v1) == null || (v1) === "") {
            v0.controls.forEach(function (c, i) {
                c.setNotification(Mscrm.BusinessRulesScript.GetResourceString('e59db157-30ca-9aa2-05c3-d3da4ac312c0', 'Не заполнен мобильный телефон'), 'eb4eb395-d711-e611-80ce-00155de610f8SetMessageStep4');
            });
            v2 = v2 + 'eb4eb395-d711-e611-80ce-00155de610f8SetMessageStep4\x3b';
        }
        var v3 = [{ 'CId': 'mobilephone', 'SId': 'eb4eb395-d711-e611-80ce-00155de610f8SetMessageStep4' }];
        for (var i = 0; i < v3.length; i++) {
            var l1 = v3[i];
            if (v2.indexOf(l1.SId + '\x3b') === -1) {
                Xrm.Page.data.entity.attributes.get(l1.CId).controls.forEach(function (c, i) { c.clearNotification(l1.SId); });
            }
        }
    } catch (e) {
        Mscrm.BusinessRules.ErrorHandlerFactory.getHandler(e, arguments.callee).handleError();
    }
}
Mscrm.BusinessRulesScript.Initialize = function () {
    Mscrm.BusinessRulesScript.AttributesOnChangeHandlers = {};
    (function () {
        var onchangehandler = function () {
            pbl_eb4eb395d711e61180ce00155de610f8();
        };
        Mscrm.BusinessRulesScript.AttributesOnChangeHandlers['mobilephone'] = onchangehandler;
        var attributeObject = Xrm.Page.data.entity.attributes.get('mobilephone');
        if (attributeObject != null && attributeObject != undefined) {
            attributeObject.addOnChange(onchangehandler);
        }
    })(); pbl_eb4eb395d711e61180ce00155de610f8();
};

4. Всплывающая подсказка tooltip.

Также, следует помнить, что всплывающие подсказки для полей в CRM уже реализованы. По-умолчанию всплывающей подсказкой для поля является описание в настройках поля.AlertTooltipSettings

AlertTooltip

 

Вышла очередная версия MS Dynamics CRM версия 2016. Хотя кое-что в ней до сих пор от 2011 версии, диалоги например.

Но сегодня речь пойдет не об этом. А о том с какими сложностями я встретился при переносе решения с 2013/2015 на 2016.

Начнем с той части где оказалось больше всего проблем, а именно, с форм. Начиная с версии 2015 Update 1 в CRM появился новый движок рендеринга форм (подробнее здесь Microsoft Dynamics CRM Online 2015 Update 1 – New Form Rendering Engine) на котором перестал работать почти весь «Unsupported» код, который я использовал еще с версии 2011. А кому сейчас легко, клиент всегда прав и если он говорит — «Хочу так!», то приходится делать как говорят.

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

Находится она по такому пути: Параметры -> Администрирование -> Системные параметры Вкладка Общие сведения где в самом низу есть секция Использовать отображение форм предыдущих версий где есть единственный пункт Для совместимости использовать подсистему отображения форм предыдущих версий. Учтите, что это может отрицательно влиять на производительность вот напротив него ставим Да.

Перво наперво с форм пропал jQuery. И почти на каждой странице я стал получать ошибку: «ReferenceError: $ is not defined»

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

Лечится так:

 

if (typeof($) === 'undefined') {
    $ = parent.$;
}
if (typeof(jQuery) === 'undefined') {
    jQuery = parent.jQuery;
}

Так же исчезла внутренняя функция Mscrm.InlineDialogUtility.createInlineDialog(), которую я использовал для создания кастомных диалогов. Пример использования такой:

 

var url = Mscrm.CrmUri.create(String.format("$webresource:{0}", "gz_/html/datainputdialog.html"));
var dlgOptions = {};
dlgOptions.param1 = "Some param";   
var url = Mscrm.CrmUri.create(String.format("$webresource:{0}", "YouWebResourceName")

Mscrm.InlineDialogUtility.createInlineDialog(url, dlgOptions, width, height, left, top, zindex);

Но, осталась функция Xrm.Internal.openDialog(), пример использования такой:

 

var url = Mscrm.CrmUri.create(String.format("$webresource:{0}", "gz_/html/datainputdialog.html"));
var dlgOptions = {};
params.sl_contact = { id: Xrm.Page.data.entity.getId(), typename: 'contact' };       
var DialogOption = new Xrm.DialogOptions;
DialogOption.width = 640; DialogOption.height = 440;
Xrm.Internal.openDialog(url.toString(), DialogOption, params, null, CallbackFunction);
function CallbackFunction(returnValue){
}

И в диалоге, если надо вернуть параметр в колбэк фунцию то это делается с помощью следующего скрипта:

 

var oReturn = {};
oReturn.smeparam = ""someparam;
Mscrm.Utilities.setReturnValue(oReturn);
 try {
    closeWindow(true);
}
catch (e) { }

И в самом веб-ресурсе необходимо добавить ссылку на ClientGlobalContext:

 

<script src="../../ClientGlobalContext.js.aspx" type="text/javascript"></script>

Еще есть у меня функция, которая пробегалась по полям формы и проверяла заполнены ли обязательные поля. Она стала возвращать false когда такого быть не должно. Исходная функция ниже:

 

function checkIfAllFieldsFilled() {
    var attributes = Xrm.Page.data.entity.attributes.get();
    for (var i = 0; i > attributes.length; i++) {
        if (attributes[i].getRequiredLevel() == "required") {
            if (attributes[i].getValue() == null) {
                return false;
            }
        }
    }
    return true;
}

Как оказалось, функция Xrm.Page.data.entity.attributes.get() теперь возвращает поля не только текущей сущности но и связных сущностей тоже, т.е. для Возможной сделки, например, я получил еще и поля связных звонков и т.д.
Решается дополнительной проверкой родителя у поля:

 

function checkIfAllFieldsFilled() {
    var isOk = true;
    Xrm.Page.data.entity.attributes.forEach(function (attribute, index) {
        if (attribute.getParent() 
            && attribute.getParent().getEntityName() === "opportunity" 
            && attribute.getRequiredLevel() === "required") {
            if (attribute.getValue() === null) {
                isOk = false;
            }
        }
    });
    return isOk;
}

Изменилась вестка гридов и объектная модель работы с ними. Про новые методы можно посмотреть здесь Grid objects and methods (client-side reference).

Соответственно, старые «Unsupported» скрипты по подучению выбранных записей не работают. А вот по фильтрации работают, надо только грид надо теперь получать через parent, как и jQuery.

Но зато теперь, получить все или выбранные записи в гриде весьма легко. 🙂

В связи со всем вышеперечисленным возникает вопрос: — «А как я пойму включены ли новые формы или нет из самого скрипта?». Ведь получается, что местами придется писать два варианта кода, для новых форм и старых.

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

 

Xrm.Internal.isTurboForm()

Это, что касается скриптов. Также, обнаружил что в CRM Online эта функция всегда возвращает false.

С плагинами и процессами проблем почти не было.

Единственное на что я пока наткнулся это ошибка «Method not found: ‘!!0[] System.Array.Empty()’.»

Появилаcь она из-за того что я обновил dll с плагинами ипользуя SDK v.8.0.2 а в нем используется .NET Framework 4.6.1.1 а при установке CRM 2016 ставится 4.6.0.

Лечится установкой .NET Framework 4.6.1.1 на сервере CRM.

Продолжение следует…

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

В поисках решения этой проблемы я нашел два решения:

Вариант первый — изменить настройки клиентов Visual Studio.

За

  • Изменение применяется один раз на клиенте и работает со всеми TFS серверами к которым подключается.
  • Оставляет возможность поставить задачу в состояние «Завершена» при чекине.

Против

  • Изменения должны быть произведены на клиенте и для каждого пользователя.

В большинстве случаев где бы Вы не инициировали CheckIn в Visual Studio Вы окажетесь на панели «Pending Changes». Это вариант изменяет выбор по умолчанию с «Resolve» на «Associate».

Для этого, всего навсего необходимо изменить запись реестра ResolveAsDefaultCheckinAction с True на False, которая находится по адресу: HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\14.0\TeamFoundation\SourceControl\BehaviorTFS-Resolve-Reg

Это для студии 2015, для версий ниже нужно поменять 14.0 на соответствующий номер.

После того как Вы изменили реестр, для того что быть уверенным, что изменения применились в студии необходимо закрыть все экземпляры студии и запустить Developer Command Prompt for VS, где выполнить следующую команду:

devenv /setup

Вариант второй — удалить действие чекина из шаблона процесса.

За

  • Изменения производятся на сервере в Team Project и не затрагивает клиентов.

Против

  • Изменения должны быть применены ко всем текущим Team Projects и потребуется обновить Process Template для будущих Team Projects.
  • Полностью удаляет поставить задачу в состояние «Завершена» при чекине.

Для этого варианта нам понадобится редактор шаблонов рабочих элементов. Я использовал TFS Power Tools, которые ставятся как аддон к Visual Studio.

Открываем студию и идем в меню TOOLS -> Process Editor -> Work Item Types -> Open WIT from Server.

TFS_Open_WIT

Открываем нужный проект и выбираем Task.

TFS_Open_WIT_2

В окне редактора рабочего элемента переходим на вкладку Workflow и ищем Transition, где есть Actions Microsoft.VSTS.Actions.Checkin.

TFS_Open_WIT-3

Кликаем ПКМ на заголовке окна и выбираем Open Details, в появившемся окне переключаемся на вкладку Actions выбираем Microsoft.VSTS.Actions.Checkin и кликаем Delete.

TFS_Open_WIT_4

В стандартном шаблоне у таска удалить надо в двух переходах и у бага в одном.

Вот, собствено, и все.

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

Оказалось, что на форме для одного из полей была включена безопасность на уровне поля (Field level security).

Для того чтобы понять так-ли это я написал следующий SQL скрипт, который показывает все поля для организации, для которых эта опция включена.

 

SELECT org.Name AS 'Organization Name'
      ,sl.UniqueName AS 'Solution Friendly Name'
    ,sl.FriendlyName AS 'Solution Name'
    ,en.Name AS 'Entity Name'
      ,fps.EntityName AS 'Entity Type Code'
      ,fps.AttributeLogicalName AS 'Field Name'
  FROM dbo.FieldPermission AS fps
  JOIN dbo.EntityView AS en
  ON fps.EntityName = en.ObjectTypeCode 
  JOIN dbo.Solution AS sl
  ON fps.SolutionId = sl.SolutionId
  JOIN dbo.Organization AS org
  ON fps.OrganizationId = org.OrganizationId

 

На днях очередной раз столкнулся с тем, что Plugin Registration Tool последней версии SDK не работает, не возможно зарегистрировать Custom Workflow или плагин от имени админа.

Проблема решается использованием Plugin Registration Tool более ранней версии SDK (6.0.4).

В связи с чем, выложил у себя все версии SDK в разделе Ресурсы MS Dynamics CRM 2013.

Наконец-то вышел релиз Visual Studio 2015 и Team Foundation Server 2015  соответственно.

О том, что новенького читаем статью на Хабре Релиз Visual Studio 2015 и .NET 4.6.