Расширенная функциональность
13
Июл
36

CRM 2011 или найди 10 отличий… Разработка, эпизод I

Облако

Облака, белогривые лошадки
Облака, что вы мчитесь без оглядки

Ну, что тут можно сказать… Разработчики теперь могут использовать в своих интересах платформу Windows Azure, чтобы разрабатывать и развертывать кастомный код для Microsoft Dynamics CRM. Это очень хорошая новость для тех (но не только), кто разрабатывает расширения для CRM 2011 Online, поскольку снимает кучу ограничений имевшихся в 4.0 версии CRM. Но размещать кастомный код в облаке можно не только для Online версии CRM, но и для любой другой. Ожидается, что будет расти доля фирм, которые переходят на CRM Online (вместо локального развертывания), с целью снижения затрат, сосредоточиться на «ядре» своего бизнеса и запустить проект внедрения гораздо быстрее.

Более подробно процесс разработки дополнений с помощью Windows Azure AppFabric, возможно, будет рассмотрен в одной из следующих статей… 🙂

JavaScript

Веб-ресурсы

Способ развертывания скриптов на формах CRM сильно изменился. Но это изменения только улучшили его! Теперь у Вас больше нет отдельных мест хранения JS-кода для различных событий форм. Весь код теперь хранится в одном месте – в Веб-ресурсах. А события формы всего лишь его вызывают! Это дает Вам возможность создавать различные фреймворки из JS-кода, т.е. записать необходимые функции в один JS-файл и использоваться в различных местах. Или Вы можете их разделить, например, по направлениям: один JS-файл для вызовов CRM Service, а другой для манипуляций на форме и т.д.

Пример #1

Рассмотрим самый простой пример: Вам нужно сложить поля A и B и поместить результата в поле C.

В CRM 4.0 Вы бы просто повесили такой скрипт на изменение полей A и B, либо на событие сохранение формы:

crmForm.all.new_fieldC.DataValue = crmForm.all.new_fieldA.DataValue + crmForm.all.new_fieldB.DataValue;

В CRM 2011 это не прокатит из-за синтаксиса и способа размещения скриптов. Поэтому…

Начнем с создания нового Веб-ресурса:

  • Перейдите Параметры — Настройки — Настроить систему – Веб-ресурсы;
  • Создайте новый Веб ресурс с типом Скрипт (JScript);
  • Нажмите кнопку Текстовый редактор и вставьте следующий скрипт в область редактирования:
    function calcAddValues(sResultField, sFieldA, sFieldB) {
        if ((Xrm.Page.getAttribute(sFieldA).getValue() != null) || (Xrm.Page.getAttribute(sFieldB).getValue() != null)) {
            Xrm.Page.getAttribute(sResultField).setValue(Xrm.Page.getAttribute(sFieldA).getValue() + Xrm.Page.getAttribute(sFieldB).getValue());
        }
        else {
            Xrm.Page.getAttribute(sResultField).setValue(null);
        }
    }
    

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

  • Нажмите OK – Сохранить и Закрыть.

Теперь мы должны «присоединить» созданный скрипт к событиям нужных полей:

  • Откройте конструктор форм нужного объекта;
  • Дважды щелкните по нужному полю, чтобы открыть его свойства;
  • На вкладке События выберите событие OnChange (тут другого и нет);
  • Разверните секцию Библиотека форм — нажмите кнопку Добавить — в диалоговом окне выберите созданный Вами Веб-ресурс (кстати Вы можете тут же его посмотреть и отредактировать). Тем самым мы присоедини Веб-ресурс к текущей форме (это нужно сделать только один раз) и он будет доступен со всех ее событиях (onLoad, onSave и onChange);
  • В разделе Обработчики события щелкните Добавить — выберите только что добавленный Веб-ресурс и укажите имя вызываемой функции calcAddValues. При этом в качестве параметров Вы можете передать в нее:
    • Контекст выполнения
    • Любые параметры через запятую

    т.к. мы суммируем поля, то перечислите через запятую имена трех полей (заключенных в кавычки), первое из которых — результирующие;

  • Аналогичным способом повесьте скрипт на событие onChange второго поля, участвующего в суммировании;
  • Опубликуйте объект и созданный Веб-ресурс и идите тестировать скрипт 🙂




Как Вы видите, использование такого подхода дает очень интересные преимущества:

  • Возможность создать универсальные функции JavaScript, которые могут быть повторно использованы на различных полях и формах различных объектов;
  • Также это позволяет сократить количество копипастов и одинаковых строк кода JS, путем создания отдельной «библиотеки» и возможности ссылаться на нее с разных форм CRM;
  • JS-код можно хранить в отдельных файлах на жестком диске и подгружать в Веб-ресурс через кнопочку Browse. С точки зрения управления разработкой, это дает возможность отказаться от структуры хранения кода (в проекте Visual Studio, например) на основе событий, а сосредоточится на хранении кода на основе выполняемых им функций.

Кончено необходимо будет привыкнуть к некоторым особенностям, таким как обязательное наличие функций (хотя бы одной), но по-моему это не сложно 🙂

Примечание

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

Пример #2

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

<JavaScriptFunction Library="$webresource:yourjavascriptfile.js" FunctionName="Display">

А вот как это используется во фрагменте XML-кода Ленты:

<CommandDefinitions>
  <CommandDefinition Id="Org.CustomEntity.Form.CommandDefinition.TestButton">
    <EnableRules>
      <EnableRule Id="Org.contact.WebClient.EnableRule" />
    </EnableRules>
    <DisplayRules>
      <DisplayRule Id="Org.contact.WebClient.DisplayRule" />
    </DisplayRules>
    <Actions>
      <JavaScriptFunction Library="$webresource:yourjavascriptfile.js" FunctionName="Display">
      </JavaScriptFunction>
    </Actions>
  </CommandDefinition>
</CommandDefinitions>

События

Формы обзавелись двумя новыми событиями…

Вкладки

Вкладки теперь обзавелись собственным событием – TabStateChange – происхоит, когда Вкладка свертывается или развертываетсянута.


iFrame

Для iFrame’ов доступно событие OnReadyStateComplete, которое указывает, что контент IFRAME загрузился.


Примечание
iFrame обновляется, когда Вкладка, на которой он находится развертывается. При этом изменения свойства src (сделанные с помощью JS) сбрасываются к исходному состоянию.

Параметры строки запроса

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


А чтобы передать какие-либо значения на форму нужно использовать в URL параметр extraqs:

http://<ServerUrl>/main.aspx?etn=<entity type code>&pagetype=entityrecord&extraqs=<fieldschemaname1>=<value1>&<fieldschemaname2>=<value2>

В JS это можно сделать так:

window.open("/main.aspx?etn=account&pagetype=entityrecord&extraqs="
+encodeURIComponent("name=My Account"), "_blank",
"location=no,menubar=no,status=no,toolbar=no", false);

В результате, после открытия формы Организации поле name будет заполнено значением «My Account».

Если нужно передать несколько параметров, то необходимо разделить их с помощью «&».

При этом нужно соблюдать следующие правила:

  • Вы должны закодировать передаваемые значения, чтобы исключить из них не разрешенные симыолы. Для этого Вы можете использовать функцию encodeURIComponent;
  • Имена параметров должны совпадать или включать названия полей объекта;
  • Для вставки значения в лукапы, необходимо передать три параметра: отображаемое значение, тип и GUID. При этом в именах передоваемых полей для отображаемого значения и типа нужно использовать суффиксы «name» и «type», соответсвенно. Например так:
    ownerid={B8C6E040-656E-DF11-B414-00155DB1891A}&owneridname=Mark Folkerts&owneridtype=systemuser

Синтаксис


Поддерживаемые скрипты написаннные для CRM 4.0, продолжат функционировать и в новой версии CRM. Очевидно, что они не будут затрагивать новую функциональность, такую как: мульти-формы, возможность показывать поле более одного или возможность скрывать части формы. Но все что было доступно в CRM 4.0 будет работать 🙂

В Microsoft Dynamics CRM 2011 используется новая объектная модель xrmPage для доступа к пользовательскому интерфейсу и данным на стороне клиента. Одной из серьезных причин ее введения является то, что Вы теперь должны учитывать возможность наличия более чем одной формы на объект, более чем одного экземпляра того же самого поля на одной форме и других изменения. Вот такая у нее иерархия:

П.С. Если Вы заметили, то скрытие всяких элементов на формах через JavaScript теперь является поддерживаемой кастомизацие и делается через специализированные методы (в то время как в предыдущих версиях MS CRM делалось через манипуляцию DOM-структурой).

Другая большая новизна – новая объектная модель для написания скриптов для форм. Например, вместо crmForm.all.new_foo.DataValue, теперь Вы должны писать Xrm. Page.data.entity.attributes.get(“new_fieldname”). Одной из серьезных причин введения новой объектной модели является то, что Вы теперь должны учитывать возможность наличия более чем одной формы на объект, более чем одного экземпляра того же самого поля на одной форме и других изменения. Технически crmForm.all.new_ fieldname.DataValue все еще будет работать, но это тупиковый подход, т.к. со временем она перестанет действовать, поэтому лучше сразу переходить на новую модель.

В кратце рассмотрим функции и методы CRM 2011 (как всегда с их полной версией и примерами Вы сможете ознакомитсяв SDK):

Xrm.Page.context – предоставляет методы для получения информации, определенной для организации, пользователя или параметров, которые были переданы в форму в строке запроса.
.getAuthenticationHeader() Возвращает закодированный заголовок SOAP-запроса для Веб-сервиса в стиле MSCRM 4.0.
.getCurrentTheme() Возвращает тему Outlook текущего пользователя.
.getOrgLcid() Возвращает значение LCID для основного языка организации.

Пример:

Xrm.Page.context. getOrgLcid();
.getOrgUniqueName() Возвращает уникальное имя организации.
.getQueryStringParameters() Возвращает массив пар ключ-значение переданных в строке запроса.
.getServerUrl() Возвращает базовый URL сервера.
.getUserId() Возвращает SystemUserId текущего пользователя.

Пример:

Xrm.Page.context.getUserId();
.getUserLcid() Возвращает значение LCID предпочитаемого пользователем языка.

Пример:

Xrm.Page.context.getUserLcid();
.getUserRoles() Возвращает массив значений GUID Ролей текщего пользователя.

Пример:

Xrm.Page.context.getUserRoles();
.isOutlookClient() Возвращает булево значение, указывающее, использует ли в настоящий момент Outlook клиент.

Пример:

Xrm.Page.context.isOutlookClient();
.isOutlookOnline() Возвращает Булево значение, указывающее, соединен ли пользователь с сервером при использовании Outlook клиента.

Пример:

Xrm.Page.context.isOutlookOnline();
.prependOrgName() Добавляет название организации к указанному пути.
Xrm.Page.data.entity.attributes – предоставляет методы для получения информации об атрибутах и выполнении в отношении их различных действий.
.addOnChange() Задант функцию, которая будет вызвана при изменении значения.
.fireOnChange() Вызывает (принудительно) событие OnChange для атрибута.

Пример:

Xrm.Page.getAttribute("CRMFieldSchemaName").fireOnChange();
.getAttributeType() Возвращает тип атрибута.
.getFormat() Возвращает параметры форматирования для атрибута.
.getInitialValue() Возвращает начальное значение булева атрибута или optionset.

Приминимо к: boolean, optionset.

.getIsDirty() Возвращает булево значение, указывающее, что у атрибута есть несохраненные изменения.
.getMax() Возвращает максимальное разрешенное значение (число) для атрибута.

Приминимо к: money, decimal, integer, double.

.getMin() Возвращает минимальное разрешенное значение (число) для атрибута.

Приминимо к: money, decimal, integer, double.

.getMaxLength() Возвращает максимальную длину (число) атрибута.

Приминимо к: string, memo.

.getName() Возвращает логическое имя атрибута.
.getOption() Возвращает объект опции по его значению.

Приминимо к: optionset.

.getOptions() Возвращает массив опций атрибута optionset.

Приминимо к: optionset.

.getSelectedOption() Возвращает опцию, выбранную в атрибуте optionset.

Приминимо к: optionset.

Пример:

Xrm.Page.getAttribute("Name of Picklist").getSelectedOption().text;
Xrm.Page.getAttribute("Name of Picklist").getSelectedOption().value;
.getText() Возвращает выбранную опцию для атрибута optionset.

Приминимо к: optionset.

.getParent() Возвращает тектовое значение выбранной опции для атрибута optionset.

Приминимо к: optionset.

.getPrecision() Возвращает количество цифр, разрешенных для ввода после десятичной точки.

Приминимо к: money, decimal, integer, double.

.getRequiredLevel() Возвращает текущий уровень требований для заполнения атрибута.
.getSubmitMode() Возвращает строковый индикатор, указывающий, будет ли атрибут отправлен на сервер при сохранении записи (П.С. отправляются только измененные атрибуты).
.getUserPrivilege() Возвращает массив булевых значений, указывающих, может ли пользователь создавать/читать/обновлять значение атрибута.
.getValue() Возвращает значение атрибута.

Пример:

Xrm.Page.getAttribute(CRMFieldSchemaName).getValue();
.removeOnChange() Удаляет функцию из события OnChange.
.setRequiredLevel() Устанавливает, обязательность заполнения атрибута.

Пример:
[/javascript]
Xrm.Page.getAttribute(«CRMFieldName»).setRequiredLevel(«none»);
Xrm.Page.getAttribute(«CRMFieldName»).setRequiredLevel(«required»);
Xrm.Page.getAttribute(«CRMFieldName»).setRequiredLevel(«recommended»);
[/javascript]

.setSubmitMode() Устанавливает, будут ли данные атрибута отправлены на сервер.

Пример:

Xrm.Page.getAttribute("Field Name").setSubmitMode("always");
.setValue() Устанавливает значение для атрибута.

Пример:

Xrm.Page.getAttribute("CRMFieldName").setValue(Value);
Методы элементов управления Xrm.Page.ui – предоставляют методы для изменения внешнего вида элемнтов управления и идентификации соответствующих атрибутов.
.addCustomView() Добавляет новое представление для диалогового окна лукапа.

Приминимо к: lookup.

.addOption() Добавляет опцию к optionset.

Приминимо к: optionset.

.clearOptions() Очищает все опции для optionset.

Приминимо к: optionset.

.getAttribute() Возвращает атрибут, с которым связан элемент управления.

Приминимо к: standard, lookup, optionset.

.getControlType() Возвращает тип элемента управления.
.getData() Возвращает параметры строки запроса переданных Веб-ресурсу Silverlight.

Приминимо к: Silverlight Web resources.

.getDefaultView() Возвращает ID дефолтного представления диалогового окна лукапа.

Приминимо к: lookup.

.getDisabled() Возвращает значение, указывающее, деактивирован ли элемент управления.
.getLabel() Возвращает метку элемента управления.
.getName() Возвращает имя, назначенное элементу управления.
.getParent() Возвращает ссылку на объект секции, которая содержит элемент управления.
.getSrc() Возвращает текущий URL iFrame»а.

Приминимо к: IFrame, Web resource.

.getInitialUrl() Возвращает изначальный URL iFrame»а.

Приминимо к: IFrame.

.getObject() Возвращает объект на форме, представляющей IFrame или Веб-ресурс.

Приминимо к: IFrame, Web resource.

.getVisible() Возвращает значение, указывающее, видим ли элемент управления.
.refresh() Обновляет данные, выведенные во вложенном Представлении.

Приминимо к: SubGrid.

.removeOption() Удаляет опцию из optionset.

Приминимо к: optionset.

.setData() Устанавливает параметры передаваеме в строке запроса Веб-ресурсу Silverlight

Приминимо к: Silverlight Web resources.

.setDefaultView() Устанавливает представление по умолчанию для диалогового окна лукапа.

Приминимо к: lookup.

.setDisabled() Активирует/деактивирует элемент управления.

Приминимо к: всем кроме Веб-ресурсов.

Пример:

Xrm.Page.getControl("CRMFieldName").setDisabled(false);
Xrm.Page.getControl("CRMFieldName").setDisabled(true);
.setFocus() Устанавливает фокус на элемент управлении.

Пример:

Xrm.Page.getControl("CRMFieldSchemaName").setFocus(true);
.setLabel() Задает метку для элемнта управления.

Пример:

Xrm.Page.ui.controls.get(fieldname).setLabel("New label");
.setSrc() Задает URL iFrame»а.

Приминимо к: IFrame, Web resource.

Пример:

Xrm.Page.getControl("iFrameName").setSrc("www.google.ru");
.setVisible() Скрывает/отображает элемент управления.

Пример:

Xrm.Page.ui.controls.get("CRMFieldName").setVisible(false);
Xrm.Page.ui.controls.get("CRMFieldName").setVisible(true);
Xrm.Page.data.entity – предоставляет методы для получения информации, определенной для текущей записи, метод сохранения и набор всех атрибутов, включенных в форму.
.addOnSave() Задает функцию, которая будет вызвана при сохранении записи.
.getDataXml() Возвращает строку XML, которая будет отправленв серверу при сохранении записи.
.getEntityName() Возвращает логическое имя объекта записи.
.getId() Возвраты GUID записи.

Пример:

Xrm.Page.data.entity.getId();
.getIsDirty() Возвращает булево значение, указывающее, были ли какие-либо поля на форме изменены.
.removeOnSave() Удаляет функцию из события OnSave.
.save() Сохраняет запись.

Пример:

Xrm.Page.data.entity.save();
Xrm.Page.data.entity.save("saveandclose");
Методы Xrm.Page.ui – предоставляет методы для получения информации о пользовательском интерфейсе, а также коллекции для некоторых внутренних компонентов формы.
.close() Закрывает форму.

Пример:

Xrm.Page.ui.close();
.getCurrentControl() Возвращает элемент управление, на котором в настоящий момент находится фокус.
.getFormType() Возвращает тип формы (создание, обнавление и т.д.).

Пример:

var formType= Xrm.Page.ui.getFormType();

1 – Create
2 – Update
3 – Read Only
4 – Disabled
6 – Bulk Edit

.getViewPortHeight() Возвращает высоту окна в пикселях.
.getViewPortWidth() Возвращает ширину окна в пикселях.
.refreshRibbon() Обновляет Ленту.
Xrm.Page.ui.controls представляет собой совокупность всех элементов управления присутствующих на форме. Эта коллекция содержит методы для получения или выполнения действий в отношении элементов управления на странице.
.forEach() Выполняет действие, содержащиеся в делегированной функции.
.get() Возвращает один или более элементов управления.
.getLength() Возвращает количество элементов управления в коллекции.
Xrm.Page.ui.navigation.items представляет собой совокупность всех элементов навигации на левой навигационной панели. Эта коллекция содержит методы для получения или выполнения действий по отношению к ним.
.forEach() Выполняет действие, содержащиеся в делегированной функции.
.get() Возвращает один или более элементов навигации.
.getLength() Возвращает число элементов навигации в коллекции.
Xrm.Page.ui.formSelector – содержит элементы всех форм, доступных пользователю, а также метод для получения информации о форме используемой в настоящее время.
.items() Набор всех доступных элементов формы.

Приминимо к: Collection.

.getCurrentItem() Возвращает текущую показанную форму.

Приминимо к: Method.

Методы коллекции Xrm.Page.ui.formSelector.items – совокупность всех форм, доступных для пользователя. Эта коллекция содержит методы для получения или выполнения действий в отношении их.
.forEach() Выполняет действие, содержащиеся в делегированной функции.
.get() Возвращает один или более roleForms.
.getLength() Возвращает количество roleForms в наборе.
Методы коллекции Xrm.Page.ui.tabs – совокупность всех вкладок присутствующих на странице. Этот сборник содержит методы для получения или выполнения действий в отношении их.
.forEach() Выполняет действие, содержащиеся в делегированной функции.
.get() Возвращает один или более вкладок.
.getLength() Возвращает количество вкладок в коллекции.
Методы коллекции Xrm.Page.data.entity.attributes – совокупность всех атрибутов присутствующих на форме. Эта коллекция содержит методы для получения или выполнения действий в отношении их.
.forEach() Выполняет действие, содержащиеся в делегированной функции.
.get() Возвращает один или более атрибутов
.getLength() Возвращает количество элементов в коллекции.
«Короткие» методы и их эквиваленты
Xrm.Page.getAttribute Xrm.Page.data.entity.attributes.get
Xrm.Page.getControl Xrm.Page.ui.controls.get
GetGlobalContext (Web Resources) Xrm.Page.context (в пределах формы)
Методы контекста переданного в функцию
.getContext() Возвращает объект Xrm.Page.context
.getDepth() Возвращает значение, определяющее порядок, в котором этот обработчик выполняется.
.getEventArgs() Возвращает объект с методами управления событием сохранения.
.getEventSource() Возвращает ссылку на объект, в котором произошло событие.
.getSharedVariable() Возвращает набор переменных используя setSharedVariable.
.setSharedVariable() Устанавливает значение переменной, которая будут использоваться после завершения текущего обработчика.

IntelliSense

JavaScript

Т.к. в CRM 2011 Javascript-код теперь стало удобно хранить в отдельных файлах, расзработчики CRM позаботились, чтобы их было удобно редактировать с помощью Intellisense в Visual Studio.

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

Итак, посмотрим как все это сделать:

  • Скачяйте и установите Microsoft Dynamics SDK (версии 5.0.3 или выше);
  • Установите расширение для Visual Studio 2010, находящееся в папке \Templates\Xrm.PageScriptProjectTemplate:
    • Для C#: XrmPageScriptDevelopmentProjectCS.vsix;
    • Для Visual Basic: XrmPageScriptDevelopmentProjectVB.vsix.
  • С этим расширением Вы можете создать в Visual Studio новый проект специально для разработки библиотеки JScript, которая взаимодействуют с объектами и методами Xrm.Page;
  • Установите в CRM управляемое Решение, расположенное по адресу:
    \Templates\ Xrm.PageScriptProjectTemplate\xrmpagescriptdevelopmentformsnapshot_1_0_0_0_managed.zipПосле этого в CRM на Ленте на вкладке Настройка появится кнопка Xrm.Page Snapshot. Эта кнопка открывает диалоговое окно, которое позволяет получить «снимок» всех доступных через Xrm.Page методов и объектов. Эти данные будут помещены в объект-переменную, которая будет инстанциорована в память чтобы VS смог задействовать IntelliSence для JavaScript.
  • Затем откройте любую форму CRM и перейдите на вкладку Настройка и щелкните по кнопке Xrm.Page Snapshot;
  • Щелкните Get Data, а затем Copy to Clipboard. При этом Вы можете «захватить» контекст выполнения или какое-либо событие сохранения. После формирования кода Вам нужно
  • В Visual Studio содайте новый проект по шаблону Xrm.Page JScript Library;
  • Когда проект откроется, в Solution Explorer раскройте папку Scripts и откройте файл PageData.js;
  • Замените все содержимое тем, что Вы скопировали в буфер обмена. Сохраниет файл PageData.js;
  • Ну и наконец… создайте новый .js файл в рамках проекта, добавьте ссылку на файл XrmPageTemplate.js:
    /// <reference path="XrmPageTemplate.js" />
    

и можете начинать писать свой код. Вы сразу заметите, что начиная вводить «Xrm» будет появляться автозавершение и подсказки 🙂




Предварительное тестирование

Библиотека XrmPageTemplate.js использует данные с PageData.js, чтобы воссоздать объект Xrm.Page. Поэтому Вы можете использовать TestPage.htm, чтобы выполнить предварительное поблочное тестирование функций Вашей библиотеки JScript, чтобы проверить, что они работают, прежде чем Вы загрузите их как Веб-ресурсы в CRM.

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

Например, если Вы используете функцию setVisible для скрытия раздела формы, Вы можете включить в TestPage.htm код, который будет использовать функцию getVisible, чтобы вывести на экран текущую состояние видимости раздела.

Для того чтобы тестовый код работатл, TestPage.htm должен включать ссылки на PageData.js, XrmPageTemplate.js и библиотеку, которую Вы тестируете.

Примечания

  • Для лучших результатов рекомедуется установить JScript Editor Extensions (http://visualstudiogallery.msdn.microsoft.com/872d27ee-38c7-4a97-98dc-0d8a431cc2ed/) для Visual Studio 2010. Эти расширение привносит некторые улучшения при редактирование JS-кода в Visual Studio. Xrm.Page Script Library Template разработан так, чтобы работать с этим расширениями для оптимального представления документации методов в Visual Studio;
  • В проекте Xrm.Page Вы найдете два примере JS-библиотек:
    • My.Library.js
    • My_Library.js

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

Лента

IntelliSense также можно задейстовать и для редактирования файла Ленты:

  • • Скачайте и разархивируйте последний SDK для CRM 2011;
  • • В Visual Studio откройте Customizations.xml и перейдите XML — Schemas;
  • • В окне XML Schemas нажмите кнопку Add;
  • • В папке \schemas выберите четыре файла с расширением .xsd:
  • o customizationssolution.xsd;
  • o три .xsd файла, имя которых начинается с «ribbon».

Ну, собственно и все… можете закастамезироваться 🙂

З.Ы. сочетание клавиш Ctrl+ Spacebar вызывает Intellisense.



SiteMap

Для Sitemap’а все то же самое по поводу IntelliSense, что и для Ленты, только подлючать нужно схему sitemap.xsd.

.Net 4.0

CRM 2011 построен на .Net Framework 4.0. Соответственно, мы сможем воспользоваться всеми его богатыми возможностями. И помните, что при разработке расширений, таких как плагины или сервилат 🙂 нужно в настройках проекта выбирать соответствующую платформу (.Net 4.0).

Windows Identity Foundation

Для многих опреаций Вам понадобится Windows Identity Foundation.

Отчеты

В CRM 2011 Вы можете писать отчеты, основанные на Fetch-xml, что позволяет Вам размещать их в Microsoft CRM Online. А т.к. Microsoft CRM Online вообще не доступен в России и разработка отчетов на основе прямого обращения к SQL серверу по прежнему поддерживается, то и интереса новому способу написания отчетов у меня нет 🙂

З.Ы. также учтите, что с Fetch-xml Вы будете иметь заведомо намного меньшие возможности для маневра нежели с мощнейшим T-SQL.

Silverlight

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

В основном Вы можете интегрировать Silverlight приложения с CRM используя две коммуникационных модели:

  • JavaScript bridge:
    • System.Windows.Browser.HtmlPage
    • System.Windows.Browser.ScriptObject
    • Xrm.Page
  • CRM WCF Services:
    • REST endpoint
    • SOAP endpoint

Ну с веб-сервисами все более мене понятно, а вот JavaScript bridge рассмотрим поподробнее. Точнее расмотрим спопобы его использования:

Вызов JavaScript функции из Silverlight

Предполижим что у нас имеется следующиая JS-библиотека:

if (typeof (MCS) == "undefined"){ MCS = {}; }  
// Создаем пространство имен для своих функций, чтобы избежать их перезаписи при совпадении имен с функциями из других библиотек. 
MCS.InteractionFunctions = {      
    performAction: function (aParameter) {          
        alert(aParameter);
    }
}

Здесь у нас имеется функция performAction, которая принимает один параметр и выводит его в алерте. З.Ы. по поводу пространств имен в JS-коде читайте статью: http://msdn.microsoft.com/en-us/library/gg309562.aspx#Y1068.

А чтобы вызвать функцию performAction из Silverlight, воспользуйтесь следующим кодом:

using System.Windows.Browser;
// Code omitted for brevity
ScriptObject interactionFunctions = HtmlPage.Window.Eval("MCS.InteractionFunctions") as ScriptObject;
if (interactionFunctions != null) {    
    interactionFunctions.Invoke("performAction", "Hello World!");
}

Здесь мы, используя HTML Bridge, получаем объект, представляющий пространство имен, содержащее функцию, которую мы хотим вызвать. А затем, используя метод Invoke вызываем функцию performAction и передаем ей строку в качестве пвараметра.

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

HtmlPage.Window.Invoke("performAction", "Hello World!");

Вызов методов Silverlight из JavaScript

Итак, что нам понадобится, чтобы вызвать какой-либо мотеод Silverlight из JavaScript:

  • В Вашем приложении Silverlight откройте файл класса, который Вы хотите предоставить для внешнего использования (в данном примере это класс MainPage) и добавьте следующий оператор using:
    using System.Windows.Browser;
    
  • Отметьте класс, который будет содержать «расшариваемые» методы как scriptable, следующим образом:
    [ScriptableType()]
    public partial class MainPage : UserControl
    {
        // Code omitted for brevity
    }
    
  • Зарегистрируйте Scriptable Object, через который будет происходить взаимодействие с Silverlight-кодом (в данном сучае регистпация происходит при зпгузке Silverlight-приложения):
    private void MainPage_Loaded(object sender, RoutedEventArgs e)
    {
        HtmlPage.RegisterScriptableObject("interactionObject", this);
    }
    
  • Чтобы представить доступ к открытому методу для JScript, отметьте его как scriptable следующим образом:
    [ScriptableMember()]
    public void FormAttributeChanged(string field, string value)
    {
        // Take some action based on the attribute that has changed and it's new value.
        MessageBox.Show("Field: " + field + "\nhas changed value to: " + value);
    }
    

Ну, и чтобы вызвать scriptable метод Silverlight из JavaScript:

  • Получите элемент формы, представляющий встроенный веб-ресурс Silverlight:
    var control = Xrm.Page.getControl('WebResource_SilverlightClient');
    
  • Получите объект Silverlight, содержавшийся в пределах элемента формы:
    var silverlightPlugin = control.getObject();
    
  • Вызовите scriptable метод Silverlight через scriptable объект (в данном примере это «interactionObject»):
    // Code for attName and attValue omitted for brevity
    silverlightPlugin.Content.interactionObject.FormAttributeChanged(attName, attValue);
    

Динамическая регистрация и обработка события onChange полей

Определите метод в Silverlight приложении, для обработки события:

[ScriptableMember()]
public void TextAttributeChanged(string field, string value) 
{

}

Следуя шагам, описанным выше в разделе «Вызов методов Silverlight из JavaScript», чтобы определить объект scriptable и метод для JavaScript.

В событии загрузки страницы в Silverlight приложения вызовите метод JavaScript, чтобы присоединить обработчик событий к onChange событию соответствующего поля (полей) CRM:

void MainPage_Loaded(object sender, RoutedEventArgs e)
{
    // Get the namespace of the function to invoke:
    ScriptObject interactionFunctions = HtmlPage.Window.Eval("MCS.InteractionFunctions") as ScriptObject;

    if (interactionFunctions != null)
    {
        interactionFunctions.Invoke("addMessageToOnChange", "mcs_name");
        interactionFunctions.Invoke("addMessageToOnChange", "mcs_description");
    }
}

В библиотеке JavaScript реализуйте функцию, которая динамически будет присоединить функцию к событию onChange соответствующего поля CRM:

addMessageToOnChange: function (field) {
    if (field == null || field == "" || field == "undefined") return;

    var attribute = Xrm.Page.data.entity.attributes.get(field);
    var tempFunc = function () {
        MCS.InteractionFunctions.textAttributeChanged(attribute);
    };

    attribute.addOnChange(tempFunc);
};

В библиотеке JavaScript реализуйте динамически присоединяемую функцию обработчика событий (в этом примере «textAttributeChanged»), которая вызывает метод Silverlight приложения:

textAttributeChanged: function (attribute) {
    if (attribute == null || attribute.getAttributeType() != "string") {
        return;
    }

    var control = Xrm.Page.getControl('WebResource_SilverlightClient');

    if (control == null) {
        return;
    }

    var silverlightPlugin = control.getObject();

    if (silverlightPlugin != null) {
        silverlightPlugin.Content.interactionObject.AttributeChanged(
            attribute.getName(),
            attribute.getValue()
        );
    }
};

Пример

Создадим, в качестве демонстрации, просто пример, который будет вытаскивать с формы CRM название Организации, менять его и сохранять форму…

  • В Visual Studio 2010 создайте проект Silverlight Application. На следующем окне, в котором автоматически создается веб-приложение для размещения Silverlight приложения, щелкниете Ок;
  • Добавьте в проект ссылку на сборку Microsoft.CSharp. А в код Default.aspx.cs такую директиву using:
    using System.Windows.Browser;
    
  • Добавьте на страницу MainPage.xaml:
    • TextBox с именем txtAccountName;
    • Две кнопки с именами btnUpdateData и btnSaveData и отображаемыми именами «Обновить» и «Сохранить», соответственно.
  • Дважды щелкните по кнопкам, чтобы создать для них методы обработки события щелчка. Затем добавьте в них такой код:
    • В метод btnSetData_Click:
      private void btnSetData_Click(object sender, EventArgs e)
      {
          //Update Account Name attribute on the form.
          dynamic xrm = (ScriptObject)HtmlPage.Window.GetProperty("Xrm");
          xrm.Page.data.entity.attributes.get("name").setValue(txtAccountName.Text); 
      }
      
    • В метод btnSaveData_Click:
      private void btnSaveData_Click(object sender, EventArgs e)
      {
          //Fire Save method to commit form changes. 
          dynamic xrm = (ScriptObject)HtmlPage.Window.GetProperty("Xrm");
          xrm.Page.data.entity.save() 	
      }
      
  • А в метод MainPage добавьте такой код:
    public void MainPage()
    {
        InitializeComponent();
        //Populate textbox with current account name value. 
        dynamic xrm = (ScriptObject)HtmlPage.Window.GetProperty("Xrm");
        txtAccountName.Text = xrm.Page.data.entity.attributes.get(“name”).getValue();
    }
    

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

  • Полностью код выглядит так:
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Animation;
    using System.Windows.Shapes;
    using System.Windows.Browser;
    
    namespace SilverlightAccountPage
    {
        public partial class MainPage : UserControl
        {
            public MainPage()
            {
                InitializeComponent();		 
                
                //Populate textbox with current account name value. 
                dynamic xrm = (ScriptObject)HtmlPage.Window.GetProperty("Xrm");
                    txtAccountName.Text = xrm.Page.data.entity.attributes.get("name").getValue();
                }
    
                private void btnSetData_Click(object sender, RoutedEventArgs e)
                {
                    //Update Account Name attribute on the form with value from textbox.
                    dynamic xrm = (ScriptObject)HtmlPage.Window.GetProperty("Xrm");
                    xrm.Page.data.entity.attributes.get("name").setValue(txtAccountName.Text); 
                }
    
                private void btnSaveData_Click(object sender, RoutedEventArgs e)
                {		 
                    //Fire Save method to commit form changes. 
                    dynamic xrm = (ScriptObject)HtmlPage.Window.GetProperty("Xrm");
                    xrm.Page.data.entity.save();
                }
            }
        }
    }
    
  • Соберите решение;
  • Создайте в CRM Веб-ресурс с типом Silverlight и именем «/ClientBin/SilverlightAccountPage.xap». Выберите для него XAP-файл созданного Silverlight приложения;
  • Откройте конструктор форм Организации, на Ленте на вкладке Вставка щелкниет Веб-ресурс. В качестве Веб-ресурса выберите только что созданный;
  • Сохраните, опубликуйте форму и можете идти тестировать 🙂





Отладка Веб-ресурсов Silverlight

Для того чтобы отладит Silverlight приложение размещенное в Веб-ресурсе:

  • Откройте папку содержащую сайт CRM (обычно C:\Program Files\Microsoft Dynamics CRM\CRMWeb\)
  • Создайте в ней файл clientaccesspolicy.xml с таким содержимым:
    <?xml version="1.0" encoding="utf-8"?>
        <access-policy>
            <cross-domain-access>
            <policy>
                <allow-from http-request-headers="*">
                    <domain uri="*"/>
                </allow-from>
                <grant-to>
                    <resource path="/" include-subpaths="true"/>
                </grant-to>
            </policy>
        </cross-domain-access>
    </access-policy>
    
  • Откройте Ваш Silverlight проект в Visual Studio. Для примера можете использовать SOAP-пример из SDK (http://msdn.microsoft.com/en-us/library/gg594452.aspx#Y1126), в котором Вам необходимо в классе SilverlightUtility.cs и замените следующий код в методе GetSoapService():
    Uri serviceUrl = CombineUrl(GetServerBaseUrl(), "/XRMServices/2011/Organization.svc/web");
    

    на

    Uri serviceUrl = new Uri("http:///<ORGNAME>/XRMServices/2011/Organization.svc/web");
    
  • Теперь нажмите F5 и начните отладку 🙂
Комментарии (36)
  • Warsch 13.07.2011

    .getUserRoles() — работает некорректно, к сожалению.
    Он возвращает не список текущих ролей пользователя, а, насколько я понял, список ролей, которые когда-либо были у пользователя.

  • Vladislav 13.07.2011

    2Warsch
    В 2011 появилась такая удобная возможность, как присвоение ролей рабочим группам пользователей. Возможно, getUserRoles() в вашем случае возвращает ещё и роли рабочих групп, к которым принадлежит пользователь.

  • Vlas 13.07.2011

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

  • slivka_83 13.07.2011

    Добрый день! 🙂

    А какой в этом смысл? 🙂 Ведь пользователи смогут отобрать поля с этой формы с помощью расширенного поиска или отчет сделать и т.д.

    А так можно, например, сверять владельца текущей записи с текущем пользователем и если они разные — закрывать форму 🙂

  • Михаил 13.07.2011

    Добрый день!

    Не знаю, к какому посту лучше это отнести, напишу здесь…

    Имеется проблема с электронной почтой в CRM 2011. Роутер еле-еле победили (пришлось помучиться), теперь какая-то проблема с кодировкой вложений к письму.
    Если вложение к письму (не важно какое расширение имеет файл) назвать русскими буквами и достаточно длинным (скажем, больше 20 символов — точно не вычислял), то у получателя оно превращается в кракозябру. С английскими наименованиями такого не происходит. Причем страдает не только название файла, но и сам файл в результате чего его нельзя нормально открыть.
    Скажем, вордовский файл, который при отправке имел название Образец оформления Решения об одобрении сделок по результатам электронных аукционов.doc у получателя называется так: =_utf-8_B_0KHQv9C40YHQvtC6INC00L7QutGD0LzQ.doc

    А сам вордовский файл в результате изменений начинается с =?utf-8?B?dGRDOTBZTFF2dEN5SU5HQzBZRFF0ZEN4Pz0NCiA9P3V0Zi04P0I/MFlQ?=\
    \

    =?utf-8?B?Ump0R0owTGpSaGRHQjBZOGcwTFRRdTlHUElOQ3cwTHJRdXRHQTBMWFF0?=\
    \
    =?utf-8?B?TkM0MFlMUXNOR0c/PQ0KID0/dXRmLTg/Qj8wTGpRdUM1a2IyTT0/PQ==?=»
    Content-Transfer-Encoding: base64
    Content-Disposition: attachment

    И дальше следует несколько страниц кракоязбр.

    Что это может быть? Как бороться. Гугл по этому поводу ничего сказать не может.

    P.S. Установили Rollup 3 — не помогло 🙁

  • Vladislav Osmanov 13.07.2011

    Добрый вечер. Есть такая проблема. Почему-то Email Router 2010 перестал брать в кавычки имена получателей письма. Из-за этого запятая в имени клиента-получателя считается почтовым сервером запятой разделяющей нескольких получателей (нарушается base-64 строка с именем получателя). Единственное, пока, решение — поменять формат отображения имени клиентов (убрать запятую, выбрав, например, «Имя Фамилия», вместо «Фамилия, Имя Отчество»). Но это изменение вступит в силу только для новых клиентов. Для прежних потребуется прогнать UPDATE-скрипт, вырезающий эту злосчастную запятую из поля fullname сущности контакт.

  • Vladislav Osmanov 13.07.2011

    О, проблема в именах файлов, я же выше описал немного другую, но похожую проблему.

    Каким почтовым сервером пользуетесь?

    Была похожая проблема но с полем «Тема»; для Exchange Server решалась увеличением допустимой длины значения этого поля в реестре.

  • Михаил 13.07.2011

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

    Своим почтовым сервером не пользуемся — используем POP и SMTP стороннего хостинга (Мастерхост, NIC.RU).

  • Михаил 13.07.2011

    P.S. Недавно осуществили переход на CRM 2011. На 4-ке все было просто, никаких проблем не возникало.

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

  • Михаил 13.07.2011

    Добрый день!
    Проблема так и не решена. Сталкивался ли с ней кто-нибудь еще?

  • slivka_83 13.07.2011

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

  • Lu 13.07.2011

    Добрый день.
    При импорте управляемого решения возникло следующее предупреждение: «Не удалось импортировать переведенные метки для следующих языков, т.к. они не включены для данной организации: 1033».
    В остальном импорт прошел успешно, но кнопка Xrm.Page Snapshot не появилась.

    Подскажите, пожалуйста,что это значит, насколько это плохо и, собственно, как это можно исправить?

  • slivka_83 13.07.2011

    Здрасьте 🙂

    А Вы опубликовали настройки? 🙂

  • Lu 13.07.2011

    Да, конечно:)

  • slivka_83 13.07.2011

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

  • Дмитрий 13.07.2011

    привет
    а кто-нибудь пытался обратиться к Xrm.Page.data.entity из консоли сценарий в браузере?
    из скрипта (онлоад/ончендж) все отлично работает, а вот из браузера объект всегда нулл

  • slivka_83 13.07.2011
  • Дмитрий 13.07.2011

    спасибо!

  • Дмитрий 13.07.2011

    отлично

  • Илья 13.07.2011

    Добрый день! Скажите пожалуйста, я в CRM пока новичек. Есть сущность проекты, в которую добавляются организации из возможных сделок. Необходимо в проектах просчитывать сумму предполагаемых доходов с возможных сделок, связанных с конкретным проектом. Как мне это реализовать? Заранее благодарен!

  • slivka_83 13.07.2011

    Добрый день!

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

    Что-то не совсем понял… Что значит организации из Возможных сделок и как они оттуда добавляются в проекты?

  • Илья 13.07.2011

    В возможных сделках находятся организации, которых мы рассматриваем как потенциальных партнеров.

  • slivka_83 13.07.2011

    Ну при такой постановке задачи, думаю без программирования Вам не обойтись…

  • Илья 13.07.2011

    Да, я понимаю, что нужно программировать, но я не пойму как данные с одной страницы передать как сумму полей в другую…

  • Григорий Наукович 13.07.2011

    Здесь явно что-то не так. Вам видимо надо при добавлении возможной сделки в проекту пересчитать доход проекта?
    Если это так, то я бы реализовывал это через плагин, срабатывающий либо на update возможной сделки, в тот момент, когда к ней прикрепляется проект, либо при создании связи между 2мя объектами. событие AssociateEntities. В этом случае можно либо просто добавить вашу сумму, либо пересчитать всю сумму по уже имеющимся сделкам. ТАкже следует учитывать, что суммы в уже прикрепленных объектах также могут измениться. И Вам следует изменить и все суммы выше этой.

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

  • Илья 13.07.2011

    Спасибо, за подсказку. Учту и попробую так сделать. Не подскажите какие-нибудь форумы по CRM. Заранее спасибо!

  • Григорий Наукович 13.07.2011

    http://axforum.info/

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

  • Илья 13.07.2011

    Я работаю разработчиком, а с CRM стал работать недавно. Есть ли форумы конкретно по разработке под CRM?

  • Григорий Наукович 13.07.2011

    Если Вы разработчик, то думаю с CRM Вы легко разберетесь. еще раз повторю, что Вы должны выучить написание плагинов, js скриптов и кастомных веб-сервисов. Для большинства задач этого хватает + знания БД.

    http://axforum.info/

    Очень известный форум по бизнес решениям.

  • Сергей 13.07.2011

    Парни огромная к вам просьба помогите мне сделать кнопку в «Интересах»! Готов дать n-ю сумму за помощь!

  • Григорий Наукович 13.07.2011

    А это с Вами я сегодня на форуме общался?

  • Сергей 13.07.2011

    ДА верно!

  • slivka_83 13.07.2011

    2Сергей
    Если савсем не знаете код, то эта тулзень может Вам помочь: http://mmcrm.ru/?p=660
    Только лучше поэксперементируйте где-нибудь на тестовой машине для начала!

  • Александр 13.07.2011

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

  • denis 13.07.2011

    коллеги! просветите! никак не получается зацепиться, вот и сейчас проделал все рекомендации по отладке веб части силверлайт. Всё время вылетаю на необработанное исключение в строке throw ex
    unauthorizedAccessExpection
    Invalid cross-thread access.
    вот модуль, почти полностью взятый из SDK:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Threading;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Browser;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Animation;
    using System.Windows.Shapes;
    using SilverlightApplication.CrmODataService;
    using System.Data.Services.Client;
    
    namespace SilverlightApplication
    {
        [ScriptableType()]
        public partial class MainPage : UserControl
        {
            private SynchronizationContext _syncContext;
            private LadoshkiContext _context;
            private String _serverUrl;
            public DataServiceCollection ContactCollection;
    
            public MainPage()
            {
                InitializeComponent();
                _serverUrl = "http://win-bke1njiqcdt/Ladoshki";
                _syncContext = SynchronizationContext.Current;
    
                if (!String.IsNullOrEmpty(_serverUrl))
                {
                    //Setup Context
                    _context = new LadoshkiContext(new Uri(
                        String.Format("{0}/xrmservices/2011/organizationdata.svc/web", _serverUrl), UriKind.Absolute));
    
                    //This is important because if the entity has new attributes added the code will fail.
                    _context.IgnoreMissingProperties = true;
    
                    //Trigger a blank search (get all contacts)
                    //SearchContacts(String.Empty);
                }
                else
                {
                    //No ServerUrl was found. Display an error message.
                    //Errors.Children.Add(new TextBlock()
                    //{
                    MessageBox.Show("Unable to access server url. Launch this Silverlight Web Resource from a CRM Form OR host it in a valid HTML/ASPX Web Resource with a ");
                    //});
                    //MessageArea.Visibility = System.Windows.Visibility.Visible;
                }
            }
    
            private void button1_Click(object sender, RoutedEventArgs e)
            {
                RetrieveAccounts(5);
            }
    
            private void RetrieveAccounts(int number)
            {
                try
                {
    
                    DataServiceQuery query = (DataServiceQuery)_context.ContactSet
                     .AddQueryOption("$top", number)
                     .AddQueryOption("$select", "FullName,Telephone1");
    
    
    
                    query.BeginExecute(ProcessPages, new PagingContext()
                    {
                        ServiceContext = _context,
                        Query = query,
                        PageProcessor = delegate(DataServiceCollection results)
                        {
                            try
                            {
    
                                if (null == ContactCollection)
                                {
                                    ContactCollection = new DataServiceCollection(_context);
                                    ContactCollection.Load(results);
                                }
                                else
                                {
                                    for (int i = 0; i &lt; results.Count; i++)
                                    {
                                        ContactCollection.Add(results[i]);
                                    }
                                }
    
                                ContactGrid.ItemsSource = ContactCollection;
                            }
                            catch (Exception ex)
                            {
                                _syncContext.Send(new SendOrPostCallback(showErrorDetails), ex);
                            }
    
                            return true;
                        }
                    });
    
                }
                catch (SystemException ex)
                {
                    _syncContext.Send(new SendOrPostCallback(showErrorDetails), ex);
                }
    
    
            }
    
            private static void ProcessPages(IAsyncResult result)
      {
       try
       {
        PagingContext context = (PagingContext)result.AsyncState;
    
        QueryOperationResponse response;
        if (null == context.Query)
        {
         response = (QueryOperationResponse)context.ServiceContext.EndExecute(result);
        }
        else
        {
         response = (QueryOperationResponse)context.Query.EndExecute(result);
         context.Query = null;
        }
    
        DataServiceCollection results = new DataServiceCollection(response);
    
        if (null != context.PageProcessor &amp;&amp; !context.PageProcessor(results))
        {
         //Stop processing
         return;
        }
    
        DataServiceQueryContinuation token = results.Continuation;
        if (null == token)
        {
         return;
        }
    
        context.ServiceContext.BeginExecute(token, ProcessPages, context);
       }
       catch (Exception ex)
       {
        throw ex;
       }
      }
    
      public void showErrorDetails(object ex)
      {
       //Assure the control is visible
       //this.MessageArea.Visibility = System.Windows.Visibility.Visible;
    
       //this.AccountsGrid.Visibility = System.Windows.Visibility.Collapsed;
    
    
       Exception exception = (Exception)ex;
       String type = exception.GetType().ToString();
    
       //this.Errors.Children.Add(new TextBlock() { TextWrapping = System.Windows.TextWrapping.Wrap, Text = 
          MessageBox.Show( String.Format("{0} Message: {1}", type, exception.Message) );
       //this.Errors.Children.Add(new TextBlock() { TextWrapping = System.Windows.TextWrapping.Wrap, Text = 
          MessageBox.Show( String.Format("Stack: {0}", exception.StackTrace) );
       if (exception.InnerException != null)
       {
        String exceptType = exception.InnerException.GetType().ToString();
        //this.Errors.Children.Add(new TextBlock() { TextWrapping = System.Windows.TextWrapping.Wrap, Text = 
           MessageBox.Show( String.Format("InnerException: {0} : {1}", exceptType, exception.InnerException.Message) );
       }
      }
    
     
    
     sealed class PagingContext
     {
      public DataServiceContext ServiceContext { get; set; }
    
      public DataServiceQuery Query { get; set; }
    
      public Func&lt;DataServiceCollection, bool&gt; PageProcessor { get; set; }
     }
    
    
        //это конец подумал штирлиц, пистолет был в другом кармане
    
    
        }
    }
    

    …помогите разобраться в тех местая, где я идиот…

*

code