With the release of the WEB API endpoint in CRM 2016, I thought it was about time I started getting familiar using it in JavaScript and its new features it provides over the traditional SOAP/REST endpoints we have grown familiar with since CRM 2011.

What is the WEB API endpoint?

The Web API is a newly introduced open standard endpoint currently available in CRM 2016  at [organization uri]/api/data/v8.0/ which implements ODATA v4 and allows you the full blown functionality that you would typically experience when interacting through the CRM Organisation Service.

The Web API service is said to eventually replace the existing Organisation and Organisation Data service and MS recommend that if you writing new code that you begin using the Web API endpoint as the existing ODATA REST endpoint is officially deprecated but still available for backward compatibility.

The new API has introduced some impressive functionality that was not available to us previously with the existing ODATA service. Some of that functionality includes:

  • CORS support
  • Ability to execute saved queries
  • Ability to execute FetchXML
  • Discovery service functionality
  • Optimistic concurrency
  • Upsert
  • Batch operations
  • User impersonation
  • Bound and Unbound actions

Using the Web API in JavaScript

Up until now MS provided us with the SDK.REST.js library in the SDK which was a handy wrapper to the underlying ODATA REST calls. I would of expected the same sort of wrapper to be available for the Web API service but unfortunately it doesn’t seem to be included in the CRM 2016 SDK at this point.

So I decided to take the existing SDK.REST library and slightly modify it to work for the new Web API endpoint and to provide a reusable wrapper that I could easily extend as time goes by with the new functionality that Web API provides.

Right now I have updated the CreateRecord, UpdateRecord, RetrieveRecord, RetrieveMultipleRecords and DeleteRecord methods. When I get a chance I will be adding functionality to take advantage of some of the new features as well. No rocket science here but this will save a few people some time establishing a starting point.

SDK.WEBAPI Library


if (typeof (SDK) == "undefined")
{ SDK = { __namespace: true }; }
SDK.WEBAPI = {
_context: function () {
///
/// Private function to the context object.
///
 
///Context
if (typeof GetGlobalContext != "undefined")
{ return GetGlobalContext(); }
else {
if (typeof Xrm != "undefined") {
return Xrm.Page.context;
}
else { throw new Error("Context is not available."); }
}
},
_getClientUrl: function () {
///
/// Private function to return the server URL from the context
///
 
///String
var clientUrl = this._context().getClientUrl()
 
return clientUrl;
},
_WebAPIPath: function () {
///
/// Private function to return the path to the REST endpoint.
///
 
///String
return this._getClientUrl() + "/api/data/v8.0/";
},
_errorHandler: function (req) {
///
/// Private function return an Error object to the errorCallback
///
 
////// The XMLHttpRequest response that returned an error.
/// ///Error
//Error descriptions come from http://support.microsoft.com/kb/193625
if (req.status == 12029)
{ return new Error("The attempt to connect to the server failed."); }
if (req.status == 12007)
{ return new Error("The server name could not be resolved."); }
var errorText;
try
{ errorText = JSON.parse(req.responseText).error.message.value; }
catch (e)
{ errorText = req.responseText }
 
return new Error("Error : " +
req.status + ": " +
req.statusText + ": " + errorText);
},
_dateReviver: function (key, value) {
///
/// Private function to convert matching string values to Date objects.
///
 
////// The key used to identify the object property
/// ////// The string value representing a date
/// 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;
},
_parameterCheck: function (parameter, message) {
///
/// Private function used to check whether required parameters are null or undefined
///
 
/// The parameter to check;
/// The error message text to include when the error is thrown.
    if ((typeof parameter === "undefined") || parameter === null) {
        throw new Error(message);
    }
},
_stringParameterCheck: function (parameter, message) {
///
/// Private function used to check whether required parameters are null or undefined
///
 
///The string parameter to check;
/// The error message text to include when the error is thrown.
    if (typeof parameter != "string") {
        throw new Error(message);
    }
},
_callbackParameterCheck: function (callbackParameter, message) {
///
/// Private function used to check whether required callback parameters are functions
///
 
/// The callback parameter to check;
///  The error message text to include when the error is thrown.
    if (typeof callbackParameter != "function") {
        throw new Error(message);
    }
},
createRecord: function (object, type, successCallback, errorCallback) {
///
/// Sends an asynchronous request to create a new record.
///
 
////// A JavaScript object with properties corresponding to the Schema name of
/// entity attributes that are valid for create operations.
/// ////// The Schema Name of the Entity type record to create.
/// For an Account record, use "Account"
/// ////// The function that will be passed through and be called by a successful response.
/// This function can accept the returned record as a parameter.
/// ////// The function that will be passed through and be called by a failed response.
/// This function must accept an Error object as a parameter.
/// this._parameterCheck(object, "SDK.WEBAPI.createRecord requires the object parameter.");
this._stringParameterCheck(type, "SDK.WEBAPI.createRecord requires the type parameter is a string.");
this._callbackParameterCheck(successCallback, "SDK.WEBAPI.createRecord requires the successCallback is a function.");
this._callbackParameterCheck(errorCallback, "SDK.WEBAPI.createRecord requires the errorCallback is a function.");
 
if (type.slice(-1) != "s") {
type = type + "s";
}
 
var req = new XMLHttpRequest();
req.open("POST", encodeURI(this._WebAPIPath() + type), 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 entityUri = this.getResponseHeader("OData-EntityId");
successCallback(entityUri);
}
else {
errorCallback(SDK.WEBAPI._errorHandler(this));
}
}
};
req.send(JSON.stringify(object));
},
retrieveRecord: function (id, type, select, expand, successCallback, errorCallback) {
///
/// Sends an asynchronous request to retrieve a record.
///
 
////// A String representing the GUID value for the record to retrieve.
/// ////// The Schema Name of the Entity type record to retrieve.
/// For an Account record, use "Account"
/// ////// A String representing the $select OData System Query Option to control which
/// attributes will be returned. This is a comma separated list of Attribute names that are valid for retrieve.
/// If null all properties for the record will be returned
/// ////// A String representing the $expand OData System Query Option value to control which
/// related records are also returned. This is a comma separated list of of up to 6 entity relationship names
/// If null no expanded related records will be returned.
/// ////// The function that will be passed through and be called by a successful response.
/// This function must accept the returned record as a parameter.
/// ////// The function that will be passed through and be called by a failed response.
/// This function must accept an Error object as a parameter.
/// this._stringParameterCheck(id, "SDK.WEBAPI.retrieveRecord requires the id parameter is a string.");
this._stringParameterCheck(type, "SDK.WEBAPI.retrieveRecord requires the type parameter is a string.");
if (select != null)
this._stringParameterCheck(select, "SDK.WEBAPI.retrieveRecord requires the select parameter is a string.");
if (expand != null)
this._stringParameterCheck(expand, "SDK.WEBAPI.retrieveRecord requires the expand parameter is a string.");
this._callbackParameterCheck(successCallback, "SDK.WEBAPI.retrieveRecord requires the successCallback parameter is a function.");
this._callbackParameterCheck(errorCallback, "SDK.WEBAPI.retrieveRecord requires the errorCallback parameter is a function.");
 
if (type.slice(-1) != "s") {
type = type + "s";
}
 
var systemQueryOptions = "";
 
if (select != null || expand != null) {
systemQueryOptions = "?";
if (select != null) {
var selectString = "$select=" + select;
if (expand != null) {
selectString = selectString + "," + expand;
}
systemQueryOptions = systemQueryOptions + selectString;
}
if (expand != null) {
systemQueryOptions = systemQueryOptions + "&$expand=" + expand;
}
}
 
var req = new XMLHttpRequest();
req.open("GET", encodeURI(this._WebAPIPath() + type + "(" + id + ")" + systemQueryOptions), 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, SDK.WEBAPI._dateReviver)
successCallback(data);
}
else {
errorCallback(SDK.WEBAPI._errorHandler(this));
}
}
};
req.send();
},
updateRecord: function (id, object, type, successCallback, errorCallback) {
///
/// Sends an asynchronous request to update a record.
///
 
////// A String representing the GUID value for the record to retrieve.
/// ////// A JavaScript object with properties corresponding to the Schema Names for
/// entity attributes that are valid for update operations.
/// ////// The Schema Name of the Entity type record to retrieve.
/// For an Account record, use "Account"
/// ////// The function that will be passed through and be called by a successful response.
/// Nothing will be returned to this function.
/// ////// The function that will be passed through and be called by a failed response.
/// This function must accept an Error object as a parameter.
/// this._stringParameterCheck(id, "SDK.WEBAPI.updateRecord requires the id parameter.");
this._parameterCheck(object, "SDK.WEBAPI.updateRecord requires the object parameter.");
this._stringParameterCheck(type, "SDK.WEBAPI.updateRecord requires the type parameter.");
this._callbackParameterCheck(successCallback, "SDK.WEBAPI.updateRecord requires the successCallback is a function.");
this._callbackParameterCheck(errorCallback, "SDK.WEBAPI.updateRecord requires the errorCallback is a function.");
 
if (type.slice(-1) != "s") {
type = type + "s";
}
 
var req = new XMLHttpRequest();
req.open("PATCH", encodeURI(this._WebAPIPath() + type + "(" + id + ")"), 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(SDK.WEBAPI._errorHandler(this));
}
}
};
req.send(JSON.stringify(object));
},
deleteRecord: function (id, type, successCallback, errorCallback) {
///
/// Sends an asynchronous request to delete a record.
///
 
////// A String representing the GUID value for the record to delete.
/// ////// The Schema Name of the Entity type record to delete.
/// For an Account record, use "Account"
/// ////// The function that will be passed through and be called by a successful response.
/// Nothing will be returned to this function.
/// ////// The function that will be passed through and be called by a failed response.
/// This function must accept an Error object as a parameter.
/// this._stringParameterCheck(id, "SDK.WEBAPI.deleteRecord requires the id parameter.");
this._stringParameterCheck(type, "SDK.WEBAPI.deleteRecord requires the type parameter.");
this._callbackParameterCheck(successCallback, "SDK.WEBAPI.deleteRecord requires the successCallback is a function.");
this._callbackParameterCheck(errorCallback, "SDK.WEBAPI.deleteRecord requires the errorCallback is a function.");
 
if (type.slice(-1) != "s") {
type = type + "s";
}
 
var req = new XMLHttpRequest();
req.open("DELETE", encodeURI(this._WebAPIPath() + type + "(" + id + ")", 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) {
successCallback();
}
else {
errorCallback(SDK.WEBAPI._errorHandler(this));
}
}
};
req.send();
 
},
retrieveMultipleRecords: function (type, options, successCallback, errorCallback, OnComplete) {
///
/// Sends an asynchronous request to retrieve records.
///
 
////// The Schema Name of the Entity type record to retrieve.
/// For an Account record, use "Account"
/// ////// A String representing the OData System Query Options to control the data returned
/// ////// The function that will be passed through and be called for each page of records returned.
/// Each page is 50 records. If you expect that more than one page of records will be returned,
/// this function should loop through the results and push the records into an array outside of the function.
/// Use the OnComplete event handler to know when all the records have been processed.
/// ////// The function that will be passed through and be called by a failed response.
/// This function must accept an Error object as a parameter.
/// ////// The function that will be called when all the requested records have been returned.
/// No parameters are passed to this function.
/// this._stringParameterCheck(type, "SDK.WEBAPI.retrieveMultipleRecords requires the type parameter is a string.");
if (options != null)
this._stringParameterCheck(options, "SDK.WEBAPI.retrieveMultipleRecords requires the options parameter is a string.");
this._callbackParameterCheck(successCallback, "SDK.WEBAPI.retrieveMultipleRecords requires the successCallback parameter is a function.");
this._callbackParameterCheck(errorCallback, "SDK.WEBAPI.retrieveMultipleRecords requires the errorCallback parameter is a function.");
this._callbackParameterCheck(OnComplete, "SDK.WEBAPI.retrieveMultipleRecords requires the OnComplete parameter is a function.");
 
if (type.slice(-1) != "s") {
type = type + "s";
}
 
var optionsString;
if (options != null) {
if (options.charAt(0) != "?") {
optionsString = "?" + options;
}
else { optionsString = options; }
}
var req = new XMLHttpRequest();
req.open("GET", this._WebAPIPath() + type + optionsString, 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, SDK.WEBAPI._dateReviver)
successCallback(data);
OnComplete();
}
else {
errorCallback(SDK.WEBAPI._errorHandler(this));
}
}
};
req.send();
},
__namespace: true
};

 

Calling the library

Below are a few simplistic examples how you would call the methods in the library. There are a few differences compared to how you would use the REST library, mostly with the entity set which is parsed to the function. We no longer parse AccountSet for example, but rather Accounts. To familiarise yourself with these changes I would suggest referencing the CRM SDK.


//Create
var account = new Object();
account.name = "My Test Account";
SDK.WEBAPI.createRecord(account, "accounts", function () { alert("Success"); }, function () { alert("Error"); });

//Retrieve Multiple
var query = "$select=name,revenue,&$orderby=revenue asc,name desc&$filter=revenue ne null";
SDK.WEBAPI.retrieveMultipleRecords("accounts", query, function (results) { alert("Success"); }, function () { alert("Error"); }, function () { alert("Complete"); });

//Retrieve
var accountId = '7877837E-1BCC-E511-80E4-FC15B428AA54';
var query = "name";
SDK.WEBAPI.retrieveRecord(accountId, "accounts", query, "", function (result) { alert("Success"); }, function () { alert("Error"); })

//Update
var accountId = '7877837E-1BCC-E511-80E4-FC15B428AA54';
var account = new Object();
account.name = account.name + " updated";
SDK.WEBAPI.updateRecord(accountId, account, "accounts", function () { alert("Success"); }, function () { alert("Error"); });

//Delete
var accountId = '7877837E-1BCC-E511-80E4-FC15B428AA54';
SDK.WEBAPI.deleteRecord(accountId, "accounts", function () { alert("Success"); }, function () { alert("Error"); });

Enjoy!

 

I have added the library to CodePlex available at https://sdkwebapi.codeplex.com/

Please follow and like us:
0

11 thoughts on “JS CRUD Operations with Web API in CRM 2016

  1. I have done in same way but how can i capture the result.

    function SetContactname()
    {
    debugger;
    alert(“Done”);
    //var contactid= Xrm.Page.getAttribute(‘primarycontactid’).getValue()[0].id;

    var contactid= ‘465B158C-541C-E511-80D3-3863BB347BA8’;
    alert(contactid);
    var jobtitle= Xrm.Page.getAttribute(‘primarycontactid’).getValue()[0].name;
    alert(jobtitle);
    var query = “jobtitle”;

    SDK.WEBAPI.retrieveRecord(contactid, “contacts”, query, “”, function (result) { alert(“Success”); alert(result);
    if (result != null)
    {
    alert(“test1”);
    debugger;
    var result = JSON.parse(this.response);
    var title= result[“jobtitle@OData.Community.Display.V1.FormattedValue”];
    alert(title);

    }},
    function ()
    {
    alert(“Error”);
    })
    }

    1. I am able to resolve. Made a synchronous call in sdk.webapi.js for retrieve method.

  2. Hi
    I am getting 404 error in below code can u plz suggest me, how can i solve that issue

    function CreateMethodWithWebApi() {
    debugger;
    var acc = new Object();
    acc.new_name = “Testing web api”;
    SDK.WEBAPI.createRecord(acc, “new_testingjs”, function () { alert(“sucess”); }, errorHandler );
    }

    function errorHandler(error) {
    debugger;
    alert(error)
    }

    1. Have you added the SDK.WebAPI library to the form? 404 is usually when a resource cannot be found.

  3. Hi,
    I created this enhancement for generating the plural names:
    _pluralName: function (name) {
    var plural = ”;
    if (name != null) {

    var len = name.length;
    var lastChar = len > 0 ? name.slice(-1) : ”;
    var last2Chars = len > 1 ? name.slice(-2) : ”;

    if (lastChar == ‘s’ || lastChar == ‘x’ || lastChar == ‘z’ || last2Chars == ‘ch’ || last2Chars == ‘sh’) {
    plural = name + ‘es’;
    }
    else if (lastChar == ‘y’) {
    var root = name.substr(0, len – 1);
    plural = root + ‘ies’;
    }

    else {
    plural = name + ‘s’;
    }

    }
    return plural;
    },

    usage:
    type = _pluralName(type);

    which can replace:
    if (type.slice(-1) != “s”) {
    type = type + “s”;
    }

    1. Also, should mention, this allows the user to continue using the singular name, as with SDK.REST.js.

Leave a Reply

Your email address will not be published. Required fields are marked *