Расширенная функциональность
10
Сен
3

CRM 2016 или найди 10 отличий… Разработка, эпизод II. WebAPI

Web API это новый унифицированной программный интерфейс для MS CRM на основе протоколов OData v4, OAuth 2 и JSON. Т.к. Web API основан на открытых и очень распространенных стандартах он позволяет реализовывать сервер-серврер и клиент-серверные приложения через большое количество разнообразие языков программирования, платформ и устройств. Он придет на смену двум текущим интерфейсам CRM Organisation Service (CRM 2011 SOAP endpoint) и Organization Data Service (ODATA).

В отличие от Organization Service, больше не требуется пользоваться библиотеками CRM SDK. Web API основан на открытых стандартах.

З.Ы. CRM Organisation Service (CRM 2011 SOAP endpoint) и Organization Data Service (ODATA) будут пока доступны в текущей версии, но в последующем MS их удалит.

Стиль написания запросов сильно напомнимет старую точку ODATA (поскольку по сути Web API является эволюцией ODATA), но с гораздо большими возможностями, которые ранее требовали использования SOAP endpoint:

  • Create
  • Update
  • Delete
  • Retrieve
  • Retrieve multiple
  • Выполнение отдельных функций (которые ранее требовали использования сообщения Execute)
  • Выполнение Actions
  • Выполнение сохраненных запросов (Представлений)

В зависимости от операции необходимо использовать различные HTTP-методы:

Операция HTTP-метод
Query Data GET
Retrieve and Execute Predefined Queries GET
Create an Entity POST
Retrieve an Entity GET
Update or Upsert an Entity PATCH
Update a Single Property Value PUT
Delete an Entity DELETE
Delete a Single Property Value DELETE
Add a Reference to an Entity POST
Remove a Reference to an Entity DELETE
Change the Reference to an Entity PUT
Execute a Web API Function GET
Execute a Web API Action POST

Вы можете также использовать службу Web API Discovery вместо веб-сервиса IDiscoveryService. Web API Discovery предоставляет те же преимущества что и Web API. Эта служба позволяет Вашим приложениям определять организации, к которым относится текущий пользователь.

Конечная точка Web API имеет следующий URL: /api/data/v8.0/

После которого следует сам запрос. При этом стоит обратить внимаение как формируются названия объектов, к которым происходит обращение. Если раньше они все именовались по одному шаблону: Entity + Set, то теперь их названия больше похожи на человеческие. Так, если имя Вашего объекта заканчивается на s, sh, ch, x, или z, то в конце добавляется «es». Иначе – «s».

Например:

  • /api/data/v8.0/accounts
  • /api/data/v8.0/new_matrixes

Для возвращения результат также появились отличия:

  • В отличие от запросов oData, Web API возвращает результаты в формате JSON.
  • oData возвращал максимум 50 записей за раз. WEB API не ограничен этим значением, и Вы можете в запросе указать количество записей, которое хотите вернуть.

Для составления сами запросов Вы можете использовать визуальный редактор: https://crmrestbuilder.codeplex.com

Аутентификация

Поскольку Web API доступен с различных платформ и языков программирования, то для его использования необходима аутентификции (в отличии от старой точки oData, которая выполнялась только из CRM). И тут есть три различных сценария:

  • JavaScript в HTML веб-ресурсах или в Ribbon-командах: в этом случае Вы не должны включать в запрос какой-либо код аутентификации – пользователь уже аутентифицирован приложением.
  • Отдельно развернутое приложение: Вы должны включить в запрс учетные данные пользователя.
  • CRM Online или IFD -развертывание: для аутентификации необходимо использовать OAuth.

Ограничения Web API

https://msdn.microsoft.com/en-us/library/mt628816.aspx

А теперь посмотрим, как с ним работать. А т.к. Web API позволяет работать из различных языков программирования, то и примеры у нас будут и из JS и из C#…

JavaScript

Имперсонализация вызова под другим пользователем

Ранее Вы могли выполнять код от имени другого Пользователя либо с помощью плагина (используя опцию Run As), либо с помощью параметра CallerId при объявлении OrganizationServiceProxy.

С Web API мы теперь можем делать это и через JS:

var organizationUrl = Xrm.Page.context.getClientUrl(); 
var query = "accounts"; 
var req = new XMLHttpRequest(); 
req.open("POST", organizationUrl + "/api/data/v8.0/" + query, true);
req.setRequestHeader("Accept", "application/json"); 
req.setRequestHeader("Content-Type", "application/json; charset=utf-8"); 
req.setRequestHeader("OData-MaxVersion", "4.0"); 
req.setRequestHeader("OData-Version", "4.0"); 
// GUID пользователя 
req.setRequestHeader("MSCRMCallerID", "28888439-85AF-E511-80EB-3863BB35AD90"); 
req.onreadystatechange = function () { 
    if (this.readyState == 4) { 
        req.onreadystatechange = null; 
        if (this.status == 200) { 
            // to do
        } else { 
            var error = JSON.parse(this.response).error; 
            alert(error.message); 
        } 
    } 
};
req.send(window.JSON.stringify({ name: "Test", description: "Account created using Web API", revenue: 5000000 }));

В этом случае в поле Создано будет подставлена учетная запись пользователя указанная в переменной MSCRMCallerID, а в поле Кем создано (делегат) тот, кто по факту вызвал метод.

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

Создание

Задание всех типов данных

Пример создания Организации:

try {
    var uri = null;
    var stringJSONAcc = null;
    var entityIdWithLink = null;
    var getEntityId = null;

    var JSONAcc = {};

    JSONAcc.name = "CompanyName Pvt. Ltd."; // Single line of text
    JSONAcc.accountcategorycode = "2" // Option Set
    JSONAcc.donotsendmm = false; // Two Options
    JSONAcc.numberofemployees = 151; // Whole Number
    JSONAcc.new_decimalnumber = 345.12; // Decimal Number
    JSONAcc["transactioncurrencyid@odata.bind"] = "/transactioncurrencies(4e950855-9eb3-e511-80de-6c3be5a8ad10)"; // Lookup, Currency
    JSONAcc["primarycontactid@odata.bind"] = "/contacts(DFE54660-37CD-E511-80DE-6C3BE5A831DC)" // Lookup, Primary Contact
    JSONAcc.creditlimit = 15000; // Money
    JSONAcc.new_dateonly = new Date(); // Date
    JSONAcc.birthdate = "2015-09-11" // Date Only YYYY-mm-dd

    // Конвертируем объект JSON в строку
    stringJSONAcc = JSON.stringify(JSONAcc);

    // URL запроса 
    uri = Xrm.Page.context.getClientUrl() + "/api/data/v8.0/accounts";

    // AJAX-запрос
    $.ajax({
        type: "POST",
        dataType: "json",
        contentType: "application/json; charset=utf-8",
        url: uri,
        data: stringJSONAcc,
        beforeSend: function (XMLHttpRequest) {
            XMLHttpRequest.setRequestHeader("Accept", "application/json");
            XMLHttpRequest.setRequestHeader("Content-Type", "application/json; charset=utf-8");
            XMLHttpRequest.setRequestHeader("Prefer", "odata.include-annotations=*");
        },
        //Success Callback Function
        success: function (data, textStatus, XMLHttpRequest) {
            // Получаем GUID созданной записи
            entityIdWithLink = XMLHttpRequest.getResponseHeader("OData-EntityId");
            getEntityId = entityIdWithLink.split(/[()]/);
            getEntityId = getEntityId[1];
            Xrm.Utility.alertDialog("Entity ID : " + getEntityId);
        },
        //Error Callback Function
        error: function () {
            Xrm.Utility.alertDialog("Something Wrong in Script POST...:(");
        }
    });
} catch (e) {
    Xrm.Utility.alertDialog(e.message + "\n" + e.description);
}

Пример создания Действия и Стороны действия:

try {
    var phonecallId = null;
    var stringJSONPhone = null;
    var urlPhone = null;

    //create activity party collection
    var parties = [];
    var JSONPhone = {};

    JSONPhone["subject"] = "Test Phone Call"; // Single line of text
    JSONPhone["phonenumber"] = "9876543210"; //Phone Number
    JSONPhone["description"] = "Phone Call Activity for Testing Purpose only...!"; // Multiple Line of Text
    JSONPhone["scheduledend"] = new Date(); //Date and Time
    JSONPhone["regardingobjectid_account@odata.bind"] = "/accounts(B386D403-F7AD-E511-80DC-A45D36FC4F90)"; //Lookup, Regarding

    // ActivityParty (From)
    var sender = {};
    sender["partyid_systemuser@odata.bind"] = "/systemusers(D949B11D-9240-4037-8379-F31C7A36680E)";
    sender["participationtypemask"] = 1; // From

    // ActivityParty (To)
    var receiver1 = {};
    receiver1["partyid_account@odata.bind"] = "/accounts(B386D403-F7AD-E511-80DC-A45D36FC4F90)";
    receiver1["participationtypemask"] = 2; // To
    //receiver["addressused"] = "roohi@dyn20161.onmicrosoft.com";

    var receiver2 = {};
    receiver2["partyid_contact@odata.bind"] = "/contacts(DFE54660-37CD-E511-80DE-6C3BE5A831DC)";
    receiver2["participationtypemask"] = 2; // To

    var receiver3 = {};
    receiver3["partyid_lead@odata.bind"] = "/leads(ED81F0D9-37CD-E511-80DE-6C3BE5A831DC)";
    receiver3["participationtypemask"] = 2; // To

    // Добавляем все части в коллекцию
    parties.push(sender);
    parties.push(receiver1);
    parties.push(receiver2);
    parties.push(receiver3);

    // Помещаем коллекцию в phonecall_activity_parties
    JSONPhone["phonecall_activity_parties"] = parties;

    JSONPhone["actualdurationminutes"] = 25; // Whole Number
    JSONPhone["directioncode"] = true; // Two Options

    // Конвертируем объект JSON в строку
    stringJSONPhone = JSON.stringify(JSONPhone);

    // URL запроса
    urlPhone = Xrm.Page.context.getClientUrl() + "/api/data/v8.0/phonecalls";

    // AJAX-запрос по созданию Звонка
    $.ajax({
        type: "POST",
        contentType: "application/json; charset=utf-8",
        dataType: "json",
        url: urlPhone,
        data: stringJSONPhone,
        beforeSend: function (CreatePhoneCallActivityRequest) {
            CreatePhoneCallActivityRequest.setRequestHeader("Accept", "application/json");
            CreatePhoneCallActivityRequest.setRequestHeader("Content-Type", "application/json; charset=utf-8");
            CreatePhoneCallActivityRequest.setRequestHeader("Prefer", "odata.include-annotations=*");
            CreatePhoneCallActivityRequest.setRequestHeader("OData-MaxVersion", "4.0");
            CreatePhoneCallActivityRequest.setRequestHeader("OData-Version", "4.0");
        },
        // Success Callback Function
        success: function (data, taxtStatus, getPhoneCallActivityResponse) {
            // Получаем GUID-записи
            phonecallId = getPhoneCallActivityResponse.getResponseHeader("OData-EntityId");
            phonecallId = phonecallId.split(/[()]/);
            phonecallId = phonecallId[1];

            Xrm.Utility.alertDialog("Entity ID : " + phonecallId);

        },
        // Error Callback Function
        error: function (CreatePhoneCallActivityRequest, textStatus, errorThrown) {
            Xrm.Utility.alertDialog("Something Wrong in Script...:(");
        }
    });
} catch (e) {
    Xrm.Utility.alertDialog(e.message + "\n" + e.description);
}

Создание головной и дочерней записи

var clientUrl = Xrm.Page.context.getClientUrl();
var req = new XMLHttpRequest()
req.open("POST", encodeURI(clientUrl + "/api/data/v8.0/accounts"), true);
req.setRequestHeader("Accept", "application/json");
req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
req.setRequestHeader("OData-MaxVersion", "4.0");
req.setRequestHeader("OData-Version", "4.0");
req.setRequestHeader("Prefer", "odata.include-annotations=*");

// Создаем объект для Организации
var objAccount = {};
objAccount.name = "My Account";
objAccount.creditonhold = false;
objAccount.accountcategorycode = 1;
objAccount.revenue = 123456;
// account["abc_approvaldate"] = new Date();

// Создаем новый Контакт и устанавливаем его в поле Primary Contact
objAccount.primarycontactid = {};
objAccount.primarycontactid.firstname = "I am the Primary";
objAccount.primarycontactid.lastname = "Contact";
// Устанавливаем существующий Контакт в поле Primary Contact
// objAccount[‘primarycontactid@odata.bind’] = "/contacts(" + { contact GUID } + ")";

// Конвертируем JSON-объект в строку
var body = JSON.stringify(objAccount);

req.onreadystatechange = function () {
    if (this.readyState == 4 /* complete */) {
        req.onreadystatechange = null;
        if (this.status == 204) {
            var accountUri = this.getResponseHeader("OData-EntityId");
            // Получаем GUID записи
            var accountID = accountUri.split(/[()]/);
            accountID = accountID[1];
            Xrm.Utility.alertDialog("Created Account ID : " + accountID);
        }
    }
};
req.send(body);

Возвращение

Возвращение записи

var clientUrl = Xrm.Page.context.getClientUrl();
var req = new XMLHttpRequest();
req.open("GET", encodeURI(clientUrl + "/api/data/v8.0/contacts?$select=fullname&$filter=fullname eq 'John Miller'"), true);
req.setRequestHeader("Accept", "application/json");
req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
req.setRequestHeader("OData-MaxVersion", "4.0");
req.setRequestHeader("OData-Version", "4.0");
req.onreadystatechange = function () {
    if (this.readyState == 4 /* complete */) {
        req.onreadystatechange = null;
        if (this.status == 200) {
            var data = JSON.parse(this.response);
            var dat = data.value;
            // Просматриваем результаты
            for (var i = 0; i < dat.length; i++) {
                // Вытаскиваем GUID
                var contactId = dat[i].contactid;
                if (contactId) {
                    Xrm.Utility.alertDialog("Contact ID : " + contactId);
                }
            }
        }
        else {
            var error = JSON.parse(this.response).error;
            alert("Error retrieving contact – " + error.message);
        }
    }
};
req.send();

Подробные данные

Если выполнить запрос данных по-простому, то для таких специфических типов как лукапы, пиклисты, валюта и т.д. вернутся только их GUID’s или номера пиклистов. Чтобы обогатить результат отображаемыми значениями необходимо включить в заголовок запроса такой параметр:

Prefer: odata.include-annotations = "OData.Community.Display.V1.FormattedValue"

var headers = {
    'Content-Type': 'application/json',
    Accept: 'application/json', 'OData-MaxVersion': 4.0,
    Prefer: 'odata.include-annotations = "*"'
};

var request = {
    requestUri: qry,
    method: 'GET',
    headers: headers,
    data: null
};

«*» можно заменить на OData.Community.Display.V1.FormattedValue – результат будет тот же. Другие поддерживаемые значения для odata.include-annotations:

  • Microsoft.Dynamics.CRM.lookuplogicalname
  • Microsoft.Dynamics.CRM.associatednavigationproperty

Возвращенный значения будет примерно такими:

_primarycontactid_value: "1ef3c099-bca1-e511-80de-3863bb349a78″
_primarycontactid_value@Microsoft.Dynamics.CRM.associatednavigationproperty: "primarycontactid"
_primarycontactid_value@Microsoft.Dynamics.CRM.lookuplogicalname: "contact"
_primarycontactid_value@OData.Community.Display.V1.FormattedValue: "Rene Valdes (sample)"
donotcall@OData.Community.Display.V1.FormattedValue: "Allow"
donotcall: false
revenue@OData.Community.Display.V1.FormattedValue: "$100,000.00"
revenue: 100000

Разбивка на страницы

Для разбить результат на отдельные порции используйте параметр odata.maxpagesize в переменной Prefer.

var headers = {
    'Content-Type': 'application/json',
    Accept: 'application/json', 'OData-MaxVersion': 4.0,
    Prefer: 'odata.include-annotations = "*", odata.maxpagesize=3'
};

var request = {
    requestUri: qry,
    method: 'GET',
    headers: headers,
    data: null
};

В возвращенном результате будет переменная @odata.nextLink, которая будет содержать полный готовый URL для запроса следующей страницы.

Общее количество

Если включить в URL запроса переменную &$count=true , то в резульате будет возвращена переменная @odata.count, в которой будет содеражться общее количество записей возвращенное запросом (но не более 5000).

Связанные записи

Чтобы расширеть результат дополнительными полями из связанных записей необходимо использовть в URL атрибут $expend:

  • Возвращаем Организацию и всех полей Основного контакта (т.е. лукапа):
    ?$select=accountnumber&$expand=primarycontactid
  • Выбираем определенные поля из лукапа:
    ?$select=accountnumber,paymenttermscode,address1_city&$expand=primarycontactid($select=firstname,lastname)
  • Возвращаем данные записей связанные N:N с основной:
    ?$select=accountnumber,paymenttermscode,address1_city&$expand=primarycontactid($select=firstname,lastname),new_building_account($select=new_name)

    где new_building_account название связи N:N.

Query Functions

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

Полный список этих функций Вы можете найти здесь: https://msdn.microsoft.com/en-us/library/mt607843.aspx

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

&$filter=Microsoft.Dynamics.CRM.ThisYear(PropertyName='modifiedon')

Возвращение записи с использованием альтернативного ключа

try {
    var clientUrl = Xrm.Page.context.getClientUrl();
    var req = new XMLHttpRequest();
    // Account Number - альтернативный ключ
    req.open("GET", encodeURI(clientUrl + "/api/data/v8.0/accounts(accountnumber='Acct123')"), true);
    // Для составных Альтернативных ключей
    //req.open("GET", encodeURI(clientUrl + "/api/data/v8.0/accounts(name='Test',emailaddress1='test@live.com')"), true);
    req.setRequestHeader("Accept", "application/json");
    req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
    req.setRequestHeader("OData-MaxVersion", "4.0");
    req.setRequestHeader("OData-Version", "4.0");
    req.onreadystatechange = function () {
        if (this.readyState == 4 /* complete */) {
            req.onreadystatechange = null;
            if (this.status == 200) {
                var data = JSON.parse(this.response);
                var dat = data.value;
                for (var i = 0; i < dat.length; i++) {
                    var accountId = dat[i].accountid;
                    Xrm.Utility.alertDialog("Account Id : " + accountId);
                }
            }
            else {
                var error = JSON.parse(this.response).error;
                alert("Error retrieving Account- " + error.message);
            }
        }
    };
    req.send();
} catch (e) {
    alert("Error in getAccountByAlternateKey – " + e.description);
}

Возвращение множества записей

function retrieveAccounts() {
    var clientURL = Xrm.Page.context.getClientUrl();
    var req = new XMLHttpRequest()
    req.open("GET", encodeURI(clientURL + "/api/data/v8.0/accounts?$select=name&$top=1"), true);
    req.setRequestHeader("Accept", "application/json");
    req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
    req.setRequestHeader("OData-MaxVersion", "4.0");
    req.setRequestHeader("OData-Version", "4.0");
    req.onreadystatechange = function () {
        if (this.readyState == 4) {
            req.onreadystatechange = null;
            if (this.status == 200) {
                var data = JSON.parse(this.response, dateReviver);
                if (data && data.value) {
                    for (var indxAccounts = 0; indxAccounts < data.value.length; indxAccounts++) {
                        var accountName = data.value[indxAccounts].name;
                        var eTag = data.value[indxAccounts]['@odata.etag'];
                    }
                }
            }
            else {
                var error = JSON.parse(this.response).error;
                alert("Error retrieving Accounts – " + error.message);
            }
        }
    };
    req.send(null);
}
function dateReviver(key, value) {
    var a;
    if (typeof value === 'string') {
        a = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
        if (a) {
            return new Date(Date.UTC(+a[1], +a[2] – 1, +a[3], +a[4], +a[5], +a[6]));
        }
    }
    return value;
};

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

Выполнение FetchXML

var fetch = encodeURI("<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='false'> <entity name='account'><attribute name='name' /> <order attribute='name' descending='false' /><filter type='and'><condition attribute='statecode' operator='eq' value='0' /></filter></entity></fetch>");
var entityname = "accounts";
var serverURL = Xrm.Page.context.getClientUrl();
var Query = entityname + "?fetchXml=" + fetch;
var req = new XMLHttpRequest();
req.open("GET", serverURL + "/api/data/v8.0/" + Query, false);
req.setRequestHeader("Accept", "application/json");
req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
req.setRequestHeader("OData-MaxVersion", "4.0");
req.setRequestHeader("OData-Version", "4.0");
req.onreadystatechange = function () {
    if (this.readyState == 4 /* complete */) {
        req.onreadystatechange = null;
        if (this.status == 200) {
            var data = JSON.parse(this.response);
            if (data != null) {
                // TODO
            }
        }
        else {
            var error = JSON.parse(this.response).error;
            alert(error.message);
        }
    }
};
req.send();

Примечание: фетч вернет за раз не более 5000 записей. Чтобы получить больше необходимо использовать разбивку на страницы. Каждый результат будет содержать параметр Microsoft.Dynamics.CRM.fetchxmlpagingcookie – если он заполнен, то на сервере остались еще записи. А pagenumber для следующего запроса Вы сможете получить следующую порцию данных.

Многоуровневый запрос

Параметр &$expand в запросе позволяет вытаскивать данные только на один уровень вниз. Но с помощью FetchXML Вы можете запросить и более низкие уровни. Например, так:

<fetch mapping="logical" version="1.0"> 
  <entity name="account"> 
     <attribute name="name" /> 
     <filter type="and"> 
        <condition value="56958E9C-5D67-DA11-82FE-0003FF19CD51" attribute="accountid" operator="eq" /> 
     </filter> 
     <link-entity name="contact" to="primarycontactid" from="contactid"> 
        <attribute name="fullname" /> 
        <attribute name="telephone1" /> 
        <link-entity name="systemuser" to="ownerid" from="systemuserid"> 
           <attribute name="internalemailaddress" /> 
        </link-entity> 
      </link-entity> 
   </entity> 
</fetch>

Возвращение записей Предствления

По имени Представления:

function executeView(viewName) {
    try {
        var clientUrl = Xrm.Page.context.getClientUrl();
        var req = new XMLHttpRequest();
        // Get 'Active Accounts' GUID
        req.open("GET", encodeURI(clientUrl + "/api/data/v8.0/savedqueries?$select=name,savedqueryid&$filter=name eq '" + viewName + "'"), true);
        req.setRequestHeader("Accept", "application/json");
        req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
        req.setRequestHeader("OData-MaxVersion", "4.0");
        req.setRequestHeader("OData-Version", "4.0");
        req.onreadystatechange = function () {
            if (this.readyState == 4 /* complete */) {
                req.onreadystatechange = null;
                if (this.status == 200) {
                    var data = JSON.parse(this.response);
                    var dat = data.value;
                    for (var i = 0; i < dat.length; i++) {
                        var viewId = dat[i].savedqueryid;
                        alert("View Id : " + viewId);
                        if (viewId) {
                            executeViewQuery(viewId);
                        }
                    }
                }
                else {
                    var error = JSON.parse(this.response).error;
                    alert("Error while fetching view id" + error.message);
                }
            }
        };
        req.send();
    } catch (e) {
        alert("Error in getViewId" + e.description);
    }
}

По GUID’у Предствления:

function executeViewQuery(viewId) {
    try {
        var clientUrl = Xrm.Page.context.getClientUrl();
        var req = new XMLHttpRequest()
        req.open("GET", encodeURI(clientUrl + "/api/data/v8.0/accounts?savedQuery=" + viewId + ""), true);
        req.setRequestHeader("Accept", "application/json");
        req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
        req.setRequestHeader("OData-MaxVersion", "4.0");
        req.setRequestHeader("OData-Version", "4.0");
        // Include this to get Lookup and Optionset text
        req.setRequestHeader("Prefer", "odata.include-annotations=OData.Community.Display.V1.FormattedValue");
        req.onreadystatechange = function () {
            if (this.readyState == 4 /* complete */) {
                req.onreadystatechange = null;
                if (this.status == 200) {
                    var data = JSON.parse(this.response);
                    var dat = data.value;
                    for (var i = 0; i < dat.length; i++) {
                        alert("Account Name – " + dat[i].name);
                        // Read Lookup name (i.e.,Primary Contact)
                        alert("Primary Contact – " + dat[i]['_primarycontactid_value@OData.Community.Display.V1.FormattedValue']);
                    }
                }
                else {
                    var error = JSON.parse(this.response).error;
                    alert("Error while executing view query" + error.message);
                }
            }
        };
        req.send();
    } catch (e) {
        alert("Error in getViewId" + e.description);
    }
}

Примечание: чтобы использовать личные Представления замените savedqueries на userqueries в URL Web API.

Обновление

Обновление записи

function update() {
    var clientURL = Xrm.Page.context.getClientUrl();
    var req = new XMLHttpRequest()
    var query = "/api/data/v8.0/accounts(5AC84B7C-4D57-E611-80DC-705A0F235AE3)";
    req.open("PATCH", encodeURI(clientURL + query), true);
    req.setRequestHeader("Accept", "application/json");
    req.setRequestHeader("Content-Type", "application/json;charset=utf-8");
    req.setRequestHeader("OData-MaxVersion", "4.0");
    req.setRequestHeader("OData-Version", "4.0");
    req.onreadystatechange = function () {
        if (this.readyState == 4) {
            req.onreadystatechange = null;
            if (this.status == 204) {
                alert("Success");
            }
            else {
                var error = JSON.parse(this.response).error;
                alert("Error deleting Account – " + error.message);
            }
        }
    };

    // Создаем JS-объект с обновленными значениями
    var account = new Object();
    account.name = "Test Neo";
    account.ownershipcode = 1;
    account.donotemail = true;
    account.creditlimit = 12345;
    account.numberofemployees = 28;
    account["primarycontactid@odata.bind"] = "/contacts(7166DE85-1858-E611-80DD-705A0F235AE3)"

    req.send(JSON.stringify(account));
}

Очистка единственного атрибута

Очистка лукапа
Делается это это помощи действия DELETE и ключевого слова $ref.

try {
    var serverURL = Xrm.Page.context.getClientUrl();

    var xhr = new XMLHttpRequest();
    // Удаляем ссылку из лукапа
    xhr.open("DELETE", serverURL + "/api/data/v8.0/accounts(1DD18913-11CB-E511-80D2-C4346BDC11C1)/primarycontactid/$ref", true);
    xhr.setRequestHeader("Accept", "application/json");
    xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8");
    xhr.setRequestHeader("OData-MaxVersion", "4.0");
    xhr.setRequestHeader("OData-Version", "4.0");
    xhr.onreadystatechange = function () {
        if (this.readyState == 4 /* complete */) {
            xhr.onreadystatechange = null;
            if (this.status == 204) {
                alert('Field Value Deleted');
            }
            else {
                var error = JSON.parse(this.response).error;
                alert(error.message);
            }
        }
    };
    xhr.send();
} catch (e) {
    alert("DeleteEntityRefField " + (e.message || e.description));
}

Очистка стрк/дат/пиклистов/валют

try {
    var serverURL = Xrm.Page.context.getClientUrl();

    var xhr = new XMLHttpRequest();
    // Очистка обычных полей
    xhr.open("DELETE", serverURL + "/api/data/v8.0/accounts(1DD18913-11CB-E511-80D2-C4346BDC11C1)/telephone1", true);
    xhr.setRequestHeader("Accept", "application/json");
    xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8");
    xhr.setRequestHeader("OData-MaxVersion", "4.0");
    xhr.setRequestHeader("OData-Version", "4.0");
    xhr.onreadystatechange = function () {
        if (this.readyState == 4 /* complete */) {
            xhr.onreadystatechange = null;
            if (this.status == 204) {
                alert('Field Value Deleted');
            }
            else {
                var error = JSON.parse(this.response).error;
                alert(error.message);
            }
        }
    };
    xhr.send();
} catch (e) {
    alert("DeleteEntityRecord" + (e.message || e.description));
}

Обновление с использование Альтернативного ключа

try {
    var clientUrl = Xrm.Page.context.getClientUrl();
    var req = new XMLHttpRequest();
    // accountnumber это альтернативный ключ
    req.open("PATCH", encodeURI(clientUrl + "/api/data/v8.0/accounts(accountnumber='Acct123')"), true);
    req.setRequestHeader("Accept", "application/json");
    req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
    req.setRequestHeader("OData-MaxVersion", "4.0");
    req.setRequestHeader("OData-Version", "4.0");
    req.onreadystatechange = function () {
        if (this.readyState == 4 /* complete */) {
            req.onreadystatechange = null;
            if (this.status == 204) {
                var accountUri = this.getResponseHeader("OData-EntityId");
                // Получаем GUID хаписи обновленной записи из ответа
                var accountID = accountUri.split(/[()]/);
                accountID = accountID[1];
                Xrm.Utility.alertDialog("Updated Account ID : " + accountID);
            }
        }
    };
    // Задаем обновляемые поля
    var objAccount = {};
    objAccount.name = "Rajeev Modified";
    objAccount.revenue = 123456;

    var body = JSON.stringify(objAccount);
    req.send(body);
} catch (e) {
    alert("Error in updateAccountByAlternateKey – " + e.description);
}

Связь

Associate

associateRecords = function (parentId, parentType, relationshipName, childId, childType, successCallback, errorCallback) {
    var req = new XMLHttpRequest();
    req.open("POST", encodeURI(getWebAPIPath() + parentType + "(" + parentId + ")/" + relationshipName + "/$ref"), true);
    req.setRequestHeader("Accept", "application/json");
    req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
    req.setRequestHeader("OData-MaxVersion", "4.0");
    req.setRequestHeader("OData-Version", "4.0");
    req.onreadystatechange = function () {
        if (this.readyState == 4 /* complete */) {
            req.onreadystatechange = null;
            if (this.status == 204 || this.status == 1223) {
                successCallback();
            }
            else {
                errorCallback(this);
            }
        }
    };
    var childEntityReference = { "@odata.id": getWebAPIPath() + "/" + childType + "(" + childId + ")" };

    req.send(JSON.stringify(childEntityReference));
};

// Account: E48456EE-49B0-E611-810F-3863BB357C39
// Opportunity: F45D2DF7-6DBF-E511-8109-3863CB343C91
associateRecords(
    "E48333EE-49B9-E511-810F-3863BB357C38",
    "accounts",
    "opportunity_customer_accounts",
    "F25D2DF7-6CBF-E511-8109-3863BB343C90",
    "opportunities",
    function () {

    },
    function () {

    }
);
function getWebAPIPath() {
    return getClientUrl() + "/api/data/v8.0/";
}

function getClientUrl() {
    // Get the organization URL
    if (typeof GetGlobalContext == "function" &&
        typeof GetGlobalContext().getClientUrl == "function") {
        return GetGlobalContext().getClientUrl();
    }
    else {
        // If GetGlobalContext is not defined check for Xrm.Page.context;
        if (typeof Xrm != "undefined" &&
            typeof Xrm.Page != "undefined" &&
            typeof Xrm.Page.context != "undefined" &&
            typeof Xrm.Page.context.getClientUrl == "function") {
            try {
                return Xrm.Page.context.getClientUrl();
            } catch (e) {
                throw new Error("Xrm.Page.context.getClientUrl is not available.");
            }
        }
        else { throw new Error("Context is not available."); }
    }
}

Disassociate

disassociateRecords = function (parentId, parentType, relationshipName, childId, childType, successCallback, errorCallback) {
    var req = new XMLHttpRequest();

    var webApiPath = getWebAPIPath();
    var parentUri = webApiPath + "/" + parentType + "(" + parentId + ")/";
    var childUri = webApiPath + "/" + childType + "(" + childId + ")";

    req.open("DELETE", encodeURI(parentUri + relationshipName + "/$ref?$id=" + childUri), true);

    req.setRequestHeader("Accept", "application/json");
    req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
    req.setRequestHeader("OData-MaxVersion", "4.0");
    req.setRequestHeader("OData-Version", "4.0");
    req.onreadystatechange = function () {
        if (this.readyState == 4 /* complete */) {
            req.onreadystatechange = null;
            if (this.status == 204 || this.status == 1223) {
                successCallback();
            }
            else {
                errorCallback(this);
            }
        }
    };
    req.send(JSON.stringify());
};

disassociateRecords(
    "E48333EE-49B9-E511-810F-3863BB357C38",
    "accounts",
    "opportunity_customer_accounts",
    "F25D2DF7-6CBF-E511-8109-3863BB343C90",
    "opportunities",
    function () {

    },
    function () {

    }
);

function getWebAPIPath() {
    return getClientUrl() + "/api/data/v8.0/";
}

function getClientUrl() {
    // Get the organization URL
    if (typeof GetGlobalContext == "function" &&
        typeof GetGlobalContext().getClientUrl == "function") {
        return GetGlobalContext().getClientUrl();
    }
    else {
        // If GetGlobalContext is not defined check for Xrm.Page.context;
        if (typeof Xrm != "undefined" &&
            typeof Xrm.Page != "undefined" &&
            typeof Xrm.Page.context != "undefined" &&
            typeof Xrm.Page.context.getClientUrl == "function") {
            try {
                return Xrm.Page.context.getClientUrl();
            } catch (e) {
                throw new Error("Xrm.Page.context.getClientUrl is not available.");
            }
        }
        else { throw new Error("Context is not available."); }
    }
}

Удаление записи

var clientURL = Xrm.Page.context.getClientUrl();
var req = new XMLHttpRequest()
var query = "/api/data/v8.0/accounts(00000000-0000-0000-0000-000000000000)";
req.open("Delete", encodeURI(clientURL + query), true);
req.setRequestHeader("Accept", "application/json");
req.setRequestHeader("Content-Type", "application/json;charset=utf-8");
req.setRequestHeader("OData-MaxVersion", "4.0");
req.setRequestHeader("OData-Version", "4.0");
req.onreadystatechange = function () {
    if (this.readyState == 4) {
        req.onreadystatechange = null;
        if (this.status == 204) {
            alert("Success");
        }
        else {
            var error = JSON.parse(this.response).error;
            alert("Error deleting Account – " + error.message);
        }
    }
};
req.send(null);

Вызов Actions

Объектный Action

organizationUrl = Xrm.Page.context.getClientUrl(); 
// Объяляем входящие параметры для Action
var data = { 
    "Description": "Test description", 
    "Subject": "Invoking from Web API" 
};
var query = "accounts(DE57510E-59A3-E511-80E4-3863BB35AD90)/Microsoft.Dynamics.CRM.new_TestAction"; // Вызов Action new_TestAction и ередача GUID’а записи
var req = new XMLHttpRequest(); 
req.open("POSTganizationUrl + "/api/data/v8.0/" + query, true); 
req.setRequestHeader("Accept", "application/json"); 
req.setRequestHeader("Content-Type", "application/json; charset=utf-8"); 
req.setRequestHeader("OData-MaxVersion", "4.0"); 
req.setRequestHeader("OData-Version", "4.0"); 
req.onreadystatechange = function () { 
    if (this.readyState == 4) { 
        req.onreadystatechange = null; 
        if (this.status == 200) { 
            alert("Action called successfully");
        } else { 
            var error = JSON.parse(this.response).error; 
            alert(error.message); 
        } 
    } 
};
req.send(window.JSON.stringify(data)); // Передаем входящие параметры

Глобальные Action

С помощью следующего URL Вы можете вызвать глобальный Action, которые не связаны с каким-либо объектом:

https://<crmserver>/api/data/v8.0/new_TestAction

Обратите внимание, что в этом случае Вам не нужно использовать полное пространство имен.

Вызов функций

В CRM есть куча системных функций, которые Вы также можете вызывать через Web API: https://msdn.microsoft.com/en-in/library/mt607829.aspx. Некоторые из них принимают параметры, другие возвращают значения. Также функции могут быть связаны с определенным объектом.

Рассмотрим их вызов на примере функции ConvertSalesOrderToInvoice, которое требует следующих параметров:

  • SalesOrderId: GUID Заказа, который нужно конвертировать.
  • ColumnSet: список атрибутов, который должен быть возвращен из созданного Счета.
function executeAction() {
    var saleOrderID = null;
    var columnsSet = null;
 
    try {      
        // Задаем параметры для функции
        saleOrderID = "D035DD97-3AB5-E511-80E2-6C3BE5A852B0"; 
        columnsSet = new Array("name", "totalamount");      
        //create the action object
        var actionObj = {
            "SalesOrderId": saleOrderID,
            "ColumnSet":
                {
                    'AllColumns': false,
                    'Columns': columnsSet
                },
        };
        // Вызываем функцию
        execute(
            actionObj,
            "ConvertSalesOrderToInvoice()",
            function (data) {
                // open the Created invoice record
                Xrm.Utility.openEntityForm("invoice", data.invoiceid);           
            },
            function (error) {
                throw new Error(error.message);
            } 
        )
        } catch (e) {
            alert(e.message);
        }
}

// Функция выполняет Web API Action
execute: function (req, reqName, successCallback, errorCallback) {
    // AJAX-запрос
    $.ajax({
        type: "POST",
        contentType: "application/json; charset=utf-8",
        datatype: "json",
        url: encodeURI(this.getWebAPIPath() + reqName),
        data: window.JSON.stringify(req),
        beforeSend: function (xhr) {
            // Задачаем такой заголовок, чтобы убедится, что результат будет возвращен в формет JSON
            xhr.setRequestHeader("Accept", "application/json");
            xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8");
            xhr.setRequestHeader("OData-MaxVersion", "4.0");
            xhr.setRequestHeader("OData-Version", "4.0");
        },
        success: function (data, textStatus, xhr) {
            // successCallback function
            successCallback(data);
        },
        error: function (xhr, textStatus, errorThrown) {
            errorCallback(Inogic.ApiLib.errorHandler(xhr));
        }});
}

getClientUrl: function () {
    //Get the organization URL
    if (typeof GetGlobalContext == "function" &&
        typeof GetGlobalContext().getClientUrl == "function") {
        return GetGlobalContext().getClientUrl();
    }               
}

Рассмотрим еще один пример – RetrieveVersion – функция возвращает номер версии сервера CRM. Функция не связана ни с какими объектом и не требует никаких параметров.

function exeFunction() {
    try {
        executeFunction(
            "RetrieveVersion()", 
            exeFunctionSucess, 
            function(error) {
                throw new Error(error.message)
            }
        )
    } catch (e) {
        showMessage(e.message);
    }
}
 
//success call back
function exeFunctionSucess(data) {
    try { 
        var versionNumber = data;
    }
    catch (e) {
        showMessage(e.message);
    }
}

// Выполняем запрос
function executeFunction(funName, successCallback, errorCallback) {
    $.ajax({
        type: "GET",
        contentType: "application/json; charset=utf-8",
        datatype: "json",                  
        url: encodeURI(this.getWebAPIPath() + funName),                   
        beforeSend: function (xhr) {        
            xhr.setRequestHeader("Accept", "application/json");                     
            xhr.setRequestHeader("OData-MaxVersion", "4.0");
            xhr.setRequestHeader("OData-Version", "4.0");
        },
        success: function (data, textStatus, xhr) {
            successCallback(data.Version);
        },
        error: function (xhr, textStatus, errorThrown) {
            errorCallback(errorHandler(xhr));
        }
    });
}

.Net

Web API можно использовать и из C#. При этом ее приминение сдесь не столь очевидно, поскольку Organization Service никто не отменял, а она всяко мощнее любой Web API. Единственное очевилное преимущество – не нужны никакие дополнительные библиотеки – только логин и пароль.

Посмотрим, как работать с Web API на примере консольного приложения…

Но прежде чем начать, добавьте в проект библиотеку для с JSON-объектами. Для этого выполните Nuget-команду: Install-Package Newtonsoft.Json.

Возвращение

using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using System.Net.Http.Headers;
using Newtonsoft.Json;
using System.Linq;
using System.Collections.Generic;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            var credentials = new NetworkCredential("Administrator", "1qaz@WSX", "D2015");
            var handler = new HttpClientHandler { Credentials = credentials };

            using (var http = new HttpClient(handler))
            {
                http.BaseAddress = new Uri("http://crm2015/superfirma/"); // Базовый 

                #region Code

                // Формируем и отправляем запрос
                string recordId = "68E19221-4149-E611-80BB-000C291AFB9D"; // Replace recordId with account record Id
                HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "api/data/v8.0/accounts(" + recordId + ")?$select=name"); //uri to accounts
                Task<HttpResponseMessage> response = http.SendAsync(request);
                response.Wait(); // Ждем пока выполнится запрос

                // в зависимости от успешности запроса...
                if (response.Result.IsSuccessStatusCode)
                {
                    JObject RetrieveResponse = JObject.Parse(response.Result.Content.ReadAsStringAsync().Result);
                    string Name = RetrieveResponse["name"].ToString();
                    Console.WriteLine("Account Name: " + Name);
                }
                else
                {
                    Console.WriteLine("Error: " + response.Result.StatusCode);
                }

                #endregion Code

                Console.WriteLine("Press Enter");
                Console.ReadLine();
            }
        }

        private static string ExtractMessageFromContent(HttpContent content)
        {
            string _stackTrace = String.Empty;
            string message = String.Empty;

            string downloadedContent = content.ReadAsStringAsync().Result;

            if (content.Headers.ContentType.MediaType.Equals("text/plain"))
            {
                message = downloadedContent;
            }
            else if (content.Headers.ContentType.MediaType.Equals("application/json"))
            {
                JObject jcontent = (JObject)JsonConvert.DeserializeObject(downloadedContent);
                IDictionary<string, JToken> d = jcontent;

                // An error message is returned in the content under the 'error' key. 
                if (d.ContainsKey("error"))
                {
                    JObject error = (JObject)jcontent.Property("error").Value;
                    message = (String)error.Property("message").Value;
                }
                else if (d.ContainsKey("Message"))
                    message = (String)jcontent.Property("Message").Value;

                if (d.ContainsKey("StackTrace"))
                    _stackTrace = (String)jcontent.Property("StackTrace").Value;
            }
            else if (content.Headers.ContentType.MediaType.Equals("text/html"))
            {
                message = "HTML content that was returned is shown below.";
                message += "\n\n" + downloadedContent;
            }
            else
            {
                message = String.Format("No handler is available for content in the {0} format.", content.Headers.ContentType.MediaType.ToString());
            }

            return message;
        }
    }
}


Далее на основе этого кода…

Установке всех типов данных

JObject account = new JObject();
account["name"] = "Sid Test Co123"; //String Value
account["accountcategorycode"] = "2"; // Optionset
account["donotsendmm"] = false; // Two Options
account["numberofemployees"] = 100; // Whole number
account["new_num"] = 2.25; // Decimal Number
account["primarycontactid@odata.bind"] = "/contacts(CAE19221-4149-E611-80BB-000C291AFB9D)"; // Lookup
account["transactioncurrencyid@odata.bind"] = "/transactioncurrencies(9131726B-1A49-E611-80B9-000C291AFB9D)"; // Currency
account["creditlimit"] = 1000; // Money
account["new_newdate"] = DateTime.Now; // Date Only
            
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "api/data/v8.1/accounts"); // uri для организаций
request.Content = new StringContent(account.ToString());
request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");
Task<HttpResponseMessage> response = http.SendAsync(request);
response.Wait(); // Ждем пока выполнится запрос

if (response.Result.IsSuccessStatusCode)
{
    string recordUrl = response.Result.Headers.GetValues("OData-EntityId").FirstOrDefault();
    var splitRetrievedData = recordUrl.Split('[', '(', ')', ']');
    string recordId = splitRetrievedData[1];

    Console.WriteLine("Record ID: {0}", recordId);
}
else
{
    Console.WriteLine("Error: " + response.Result.StatusCode);
    Console.WriteLine(ExtractMessageFromContent(response.Result.Content));
}

Создании Действия и Стороны действия

JObject jsonPhoneCall = new JObject(); // JSON-объект
jsonPhoneCall["subject"] = "Test Phone Call" + DateTime.Now.ToShortDateString(); // Single line of text
jsonPhoneCall["phonenumber"] = "4565898756"; // Multiple Line of Text
jsonPhoneCall["description"] = "Phone Call Activity for Testing Purpose only...!"; //Description
jsonPhoneCall["scheduledend"] = DateTime.Now; // Date and Time
jsonPhoneCall["regardingobjectid_account@odata.bind"] = "/accounts(62E19221-4149-E611-80BB-000C291AFB9D)"; //Regarding is an account
jsonPhoneCall["actualdurationminutes"] = 25; // Whole Number
jsonPhoneCall["directioncode"] = true; // Two Options

JArray parties = new JArray(); // Коллекция для участников

// ActivityParty (From)                                                                                                                           
JObject sender = new JObject();
sender["partyid_systemuser@odata.bind"] = "/systemusers(0770F392-1949-E611-80B9-000C291AFB9D)";
sender["participationtypemask"] = 1;
            
// ActivityParty (To)
JObject receiver1 = new JObject();
receiver1["partyid_account@odata.bind"] = "/accounts(62E19221-4149-E611-80BB-000C291AFB9D)";
receiver1["participationtypemask"] = 2;
JObject receiver2 = new JObject();
receiver2["partyid_systemuser@odata.bind"] = "/systemusers(0770F392-1949-E611-80B9-000C291AFB9D)";
receiver2["participationtypemask"] = 2;

// Добавляем участников в коллекцию
parties.Add(sender);
parties.Add(receiver1);
parties.Add(receiver2);

// Помещаем коллекцию в звонок
jsonPhoneCall["phonecall_activity_parties"] = parties;
                
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "api/data/v8.1/phonecalls"); // uri для звонков
request.Content = new StringContent(jsonPhoneCall.ToString());
request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");
Task<HttpResponseMessage> response = http.SendAsync(request);
response.Wait(); // Ждем пока выполнится запрос

if (response.Result.IsSuccessStatusCode)
{
    string recordUrl = response.Result.Headers.GetValues("OData-EntityId").FirstOrDefault();
    var splitRetrievedData = recordUrl.Split('[', '(', ')', ']');
    string recordId = splitRetrievedData[1];

    Console.WriteLine("Record ID: {0}", recordId);
}
else
{
    Console.WriteLine("Error: " + response.Result.StatusCode);
    Console.WriteLine(ExtractMessageFromContent(response.Result.Content));
}

Обновление

JObject updateAccount = new JObject();
updateAccount.Add("telephone1", "123-456-7890");
updateAccount.Add("address1_city", "Sydney");

string accountId = "62E19221-4149-E611-80BB-000C291AFB9D";
HttpRequestMessage request = new HttpRequestMessage(new HttpMethod("PATCH"), "api/data/v8.0/accounts(" + accountId + ")"); //uri to accounts
request.Content = new StringContent(updateAccount.ToString()); // JObject of the data to be posted.
request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");
Task<HttpResponseMessage> response = http.SendAsync(request);
response.Wait(); // Ждем пока выполнится запрос

if (response.Result.IsSuccessStatusCode)
{
    Console.WriteLine("Record updated successfully");
}
else
{
    Console.WriteLine("Error: " + response.Result.StatusCode);
    Console.WriteLine(ExtractMessageFromContent(response.Result.Content));
}

Удаление

string accountId = "62E19221-4149-E611-80BB-000C291AFB9D";
Task<HttpResponseMessage> response = http.DeleteAsync("api/data/v8.0/accounts(" + accountId + ")");
response.Wait();

if (response.Result.IsSuccessStatusCode)
{
    Console.WriteLine("Record deleted successfully.");
}
else
{
    Console.WriteLine("Error: " + response.Result.StatusCode);
    Console.WriteLine(ExtractMessageFromContent(response.Result.Content));
}

Вызов из пллагина под текущим пользователем

using (WebClient client = new WebClient()) 
{            
    client.UseDefaultCredentials = true;
    byte[] responseBytes = client.DownloadData(new Uri("<your web api url>/accounts(3C2D2712-E43F-E411-9402-005056AB452C)"));
    string response = Encoding.UTF8.GetString(responseBytes);
    // parse the json response 
}

IntelliSense

  • Скачате метаданныеорганизациии. Для этого введите в браузере следущий URL <organization URI>/api/data/v8.1/$metadata) – откроется XML описывающий метаданные организации. Сохраните его на жестком диске.
  • Установите в Visual Studio OData v4 Client Code Generator VS Extension (и перезапустите студию).
  • После чего добавьте компонент OData Client OData к вашему проекту в VS.
  • Откройте на редактирвоание созданный ODataClient1.tt и вставьте в константу MetadataDocumentUri путь до файла с метаданными на жестком диске (в следующем формает «File:///C:/_slivka_83/crm2016odata.xml»). На их основе OData v4 Client Code Generator автоматически сгенерирует.cs файл со строготипизированными объектами. Можете использовать их в коде.



Комментарии (3)
  • Fedor 10.09.2016

    Спасибо ещё раз за труд.
    Если бы все статьи были бы собраны как-нибудь в один файл, книгу, pdf купил бы не задумываясь. 🙂

  • Valdimir 10.09.2016

    Подскажите как проверить валидность на клиентской строне (через js) fetch xml? Вроде бы можно использовать ValidateSavedQuery Action, но я не понимаю какую связанную сущность нужно указать и какой guid для неё ставить. Я указываю guid сущности с которой вызываю метод и в экшен уходят два параметра FetchXml И QueryType. Пытаюсь вызвать вот таким вот методом, но мне всё время присылает 400 ошибку с вот таким кодом «message»:»Request message has unresolved parameters.»,»innererror» .
    В чем может быть проблема? Я по разному пробовал (параметры менял, сущности связанной id устанавливал) но ничего годного не вышло, хотя отправляю валидный fetch.

    function callAction() {
    var data = {
    «FetchXml»: «» +
    «» +
    «» +
    «» +
    «» +
    «» +
    «» +
    «»,
    «QueryType»: «1»
    };

    var serverURL = Xrm.Page.context.getClientUrl();

    var req = new XMLHttpRequest();
    req.open(«POST», serverURL + «/api/data/v8.1/savedqueries(«+ Xrm.Page.data.entity.getId().replace(«{«,»»).replace(«}»,»») +»)/Microsoft.Dynamics.CRM.ValidateSavedQuery», true);
    req.setRequestHeader(«Accept», «application/json»);
    req.setRequestHeader(«Content-Type», «application/json; charset=utf-8»);
    req.setRequestHeader(«OData-MaxVersion», «4.0»);
    req.setRequestHeader(«OData-Version», «4.0»);
    req.onreadystatechange = function() {
    if (this.readyState == 4) {
    req.onreadystatechange = null;
    if (this.status == 200 || this.status == 204) {
    if (this.status == 200) {
    var result = JSON.parse(this.response);
    console.log(result);
    }
    } else {
    var error = JSON.parse(this.response).error;
    console.log(error.message);
    }
    }
    };
    req.send(JSON.stringify(data));
    }
    callAction();

  • slivka_83 10.09.2016

    К сожалению я вообще не понял вопроса. Ни бизнес-задачи ни ее реализации.

*

code