Create Web API and Consume using AJAX – Part 1

This series of articles will describe writing WEB API with all HTTP supported verbs (i.e.) and consuming those using AJAX. Although, you can consume the API in any wbesite or application but, I will show you consumption in Dynamics 365 using AJAX calls.

In the first part of these articles, I am going to explain everything to create and use “Head” and “Options” HTTP Verbs.

Let me explain a little about “Head” and “Options”. Head is almost same like Get but there is no response body in it. This is useful when you you want to check what a Get request will return or is the Web API is currently up and running or not. An OPTIONS request can return what other HTTP verbs/methods the web API supports. This is useful to test API to avoid fatal errors.

Let’s create our empty web API project in Visual studio 2015. Right click on your solution file in VS and add new project like this

screen1

Let’s say new web API name is “StudentWebAPI”

screen2

Click “Ok”. On next screen, select these options

screen3

Add new Web API controller by right clicking on “Controllers” folder

screen4

Let’s create a new action named “IsWebAPIRunning” with Http Verb “Head”. Set RouPrefix of web API and Http supported verb on action along with route.

screen5

We can set Http verb by another way “[HttpHead]” as you can see commented code in above screen i.e. //[HttpHead].

Now create another action named “Options” with Http Verb “Options”.

screen6

Note other things you can try as commented in above code.

You can also write Options action like this

screen7

Above highlighted line of code is necessary if you want to read particular headers from response of API.

You need to be careful about one thing that you need to have only one action with Options verb otherwise the API will throw error at run time when you will call any of these two actions. I have just shown you another way to do the same thing.

Your WebAPIConfig.cs should look like this

screen8

Now, let’s run our web API, it throws error like this

screen9

Don’t worry about this error. You can test the API by this URL

http://localhost:30402/api/Student/IsWebAPIRunning

Hit this URL along with correct port as you can see above. Response comes like this

screen10

As we are using “HEAD” verb on our action “IsWebAPIRunning”. So, this is the reason it is showing this response. One thing is pretty sure by this response that our Web API is working fine.

Now in order to use the web API in another website or domain, CORS should be enabled for this. Enable CORS by adding package for CORS on the web api project using Nuget package manager.

screen11

screen12

After installing CORS, you need to enable CORS which enables web API to be accessed outside of its domain. There are different levels on which you can enable CORS i.e. On a Controller, on specific actions or on entire web API.

Enable CORS on controller

[RoutePrefix("api/Student")]
    [EnableCors(origins: "http://localhost:5901", headers: "*", methods: "*", exposedHeaders: "X-My-Header")]
    public class StudentController : ApiController
Enable CORS globally in WebApiConfig.cs file
var cors = new EnableCorsAttribute("*", "*", "*");
            config.EnableCors(cors);

The constructor of class “EnableCorsAttribute” takes three arguments i.e. Origins, Headers and Methods. If you specify * in first parameter like we did then it means that this API is accessible from all external domains. If we set * for headers, it means all headers are accessible. Similarly for third parameter (i.e. Methods), use of * means this API is accessible by using all HTTP methods i.e. Get, Post, Head, Put, etc.

Here is the JS code to test both actions of Student Web API.

// JavaScript source code
var serviceURL = "http://localhost:30402/api/Student/";

function isStudentsWebAPIWorking() {
    debugger;
    var postData = null;
    //--For "HEAD"
    var fullURL = serviceURL + "IsWebAPIRunning";
    debugger;
    var isAPIWorking = false;

    $.ajax({
        type: "HEAD",
        url: fullURL,
        async: false,
        success: function (jsonObject, responseStatus, response) {
            debugger;

            var statusText = response.statusText;
            if (statusText == "OK" && responseStatus == "success") {
                isAPIWorking = true;
            }

            return isAPIWorking;
        },
        error: function (jqXHR, responseStatus, response) {
            debugger;
            //alert('Error while executing search operation.');
            return isAPIWorking;
        }
    });
    return isAPIWorking;
}

function getOptions() {
    debugger;
    var fullURL = serviceURL + 'OPTIONS';
    var allowedHttpMethods = 'There is some error on getting supported verbs from web API.';

    $.ajax({
        url: fullURL,
        type: "OPTIONS",
        async: false,
        dataType: 'json',
        data: null,
        contentType: 'application/json',
        success: function (data, textStatus, jqXHR) {
            debugger;
            if (data != null && data != undefined) {
                if (data.Headers != null && data != undefined) {
                    for (var i = 0; i < data.Headers.length; i++) {
                        var headerAttributeKey = data.Headers[i].Key;
                        if (headerAttributeKey == "Access-Control-Allow-Methods") {
                            allowedHttpMethods = data.Headers[i].Value[0];
                            break;
                        }
                    }
                }
            }
            return allowedHttpMethods;
        },
        error: function (jqXHR, textStatus, errorThrown) {
            debugger;
            //responseObject.Error = 'Error while executing search operation.';//jsonObject.Message;
            return allowedHttpMethods;
        }
    });
    return allowedHttpMethods;
}

References:

https://www.c-sharpcorner.com/article/enable-cors-in-asp-net-webapi-2/

https://msdn.microsoft.com/en-us/magazine/dn532203.aspx

https://restfulapi.net/http-methods/#patch

https://assertible.com/blog/7-http-methods-every-web-developer-should-know-and-how-to-test-them#patch

Part-6 – Code Conversion to latest Web API (Dynamics 365)

Execute FetchXML

Note: You can find complete code at the end of the article attached

Using 2011 endpoint in CRM 2011 to CRM 2016 we can execute fetchXML and get results like given below

You have to call JS function “callFetchXMLBy2011Enpoint” to test this. Other JS functions are called from this function.

function callFetchXMLBy2011Enpoint() {
    var fetch = "<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='true'>" +
                                          "<entity name='opportunity'>" +
                                            "<attribute name='name' />" +
                                            "<attribute name='customerid' />" +
                                            "<attribute name='estimatedvalue' />" +
                                            "<attribute name='statecode' />" +
                                            "<attribute name='opportunityid' />" +
                                            "<order attribute='name' descending='true' />" +
                                            "<link-entity name='lead' from='qualifyingopportunityid' to='opportunityid' alias='lead'>" +
                                                "<attribute name='fullname' />" +
                                                "<attribute name='companyname' />" +
                                            "</link-entity>" +
                                          "</entity>" +
                                        "</fetch>";
    debugger;
    var res = callFetchXMLRequest(fetch);
    //var res = JCL.Fetch(fetch);
    debugger;
    if (res != null && res[0] != null && res[0].attributes["name"] != null) {
        debugger;
        var oppGuid = res[0].guid;
        var logicalName = res[0].logicalName;
        var name = res[0].attributes["name"].value;
        var customerid_id = res[0].attributes["customerid"].guid;
        var customerid_name = res[0].attributes["customerid"].name;
        var lead_fullname = res[0].attributes["lead.fullname"].value;
        var lead_companyname = res[0].attributes["lead.companyname"].value;
        var estimatedvalue = res[0].attributes["estimatedvalue"].value;
        var opportunityid = res[0].attributes["opportunityid"].value;
        var statecode = res[0].attributes["statecode"].value;
        var statecode_text = res[0].attributes["statecode"].formattedValue;
        var transactioncurrencyid = res[0].attributes["transactioncurrencyid"].guid;
        var transactioncurrencyid_name = res[0].attributes["transactioncurrencyid"].name;
    }    
}

function callFetchXMLRequest(sFetchXml) {
    'use strict';
    var fCallback = null;

    /// Executes a FetchXml request.  (Returns an array of entity records)
var clientURL = Xrm.Page.context.getClientUrl();
    var request = "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">";
    request += "<s:Body>";
    request += '<Execute xmlns="http://schemas.microsoft.com/xrm/2011/Contracts/Services">' +
        '<request i:type="b:RetrieveMultipleRequest" ' +
        ' xmlns:b="http://schemas.microsoft.com/xrm/2011/Contracts" ' +
        ' xmlns:i="http://www.w3.org/2001/XMLSchema-instance">' +
        '<b:Parameters xmlns:c="http://schemas.datacontract.org/2004/07/System.Collections.Generic">' +
        '<b:KeyValuePairOfstringanyType>' +
        '<c:key>Query</c:key>' +
        '<c:value i:type="b:FetchExpression">' +
        '<b:Query>';

    request += CrmEncodeDecode.CrmXmlEncode(sFetchXml);

    request += '</b:Query>' +
        '</c:value>' +
        '</b:KeyValuePairOfstringanyType>' +
        '</b:Parameters>' +
        '<b:RequestId i:nil="true"/>' +
        '<b:RequestName>RetrieveMultiple</b:RequestName>' +
        '</request>' +
        '</Execute>';

    request += '</s:Body></s:Envelope>';

    // To execute synchronously, the calling function should pass null.
    //   This allows the user to pass nothing for a callback to execute synchronously
    if (typeof fCallback === 'undefined') {
        fCallback = null;
    }

    var sMessage = 'Execute';

    var xmlhttp = new XMLHttpRequest();
    xmlhttp.open("POST", clientURL + "/XRMServices/2011/Organization.svc/web", (fCallback !== null));
    xmlhttp.setRequestHeader("Accept", "application/xml, text/xml, */*");
    xmlhttp.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
    xmlhttp.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/" + sMessage);

    if (fCallback !== null) {
        debugger;
        // asynchronous: register callback function, then send the request.
        xmlhttp.onreadystatechange = function () {
            fetchXMLCallback(this, xmlhttp, fUserCallback);
        };

        xmlhttp.send(request);
    } else {
        debugger;
        // synchronous: send request, then call the callback function directly
        xmlhttp.send(request);
        debugger;
        //return fetchXMLCallback(this, xmlhttp, null);
        return fetchXMLCallback(xmlhttp);
    }
}

From 2016 we can use latest web API approach like given below. You have to call JS function “callFetchXMLByWebAPI” to test this. Other JS functions are called from this function.

function executeFetchXML(fetchXml, entityName) {
    debugger;
    var results = null;
    var isAsync = false;
    var entityPluralName = getEntityPluralName(entityName);
    var encodedFetchXml = encodeURI(fetchXml);
    var queryPath = "/api/data/v8.0/" + entityPluralName + "?fetchXml=" + encodedFetchXml;
    var requestPath = Xrm.Page.context.getClientUrl() + queryPath;
    var req = new XMLHttpRequest();
    req.open("GET", requestPath, isAsync);
    req.setRequestHeader("Accept", "application/json");
    req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
    req.onreadystatechange = function () {
        if (this.readyState === 4) {
            this.onreadystatechange = null;
            debugger;
            if (this.status === 200) {
                var returned = JSON.parse(this.responseText);
                results = returned.value;
                return results;
            }
            else {

                alert(this.statusText);
            }
        }
    };
    req.send();
    return results;
}

function callFetchXMLByWebAPI() {
    var fetch = "<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='true'>" +
                                          "<entity name='opportunity'>" +
                                            "<attribute name='name' />" +
                                            "<attribute name='customerid' />" +
                                            "<attribute name='estimatedvalue' />" +
                                            "<attribute name='statecode' />" +
                                            "<attribute name='opportunityid' />" +
                                            "<order attribute='name' descending='true' />" +
                                            "<link-entity name='lead' from='qualifyingopportunityid' to='opportunityid' alias='lead'>" +
                                                "<attribute name='fullname' />" +
                                                "<attribute name='companyname' />" +
                                            "</link-entity>" +
                                          "</entity>" +
                                        "</fetch>";
    debugger;
    var entityName = 'opportunity';
    var res = executeFetchXML(fetch, entityName)
    //var res = JCL.Fetch(fetch);
    debugger;
    if (res != null && res.length > 0) {
        debugger;
        var oppGuid = res[0]["opportunityid"];//-- This is primary key field
        //var logicalName = res[0].logicalName;
        var name = res[0]["name"]; //-- This is simple text field
        var customerid_id = res[0]["_customerid_value"]; //-- This is lookup field
        //var customerid_name = res[0]["customerid"].name;
        var lead_fullname = res[0]["lead_x002e_fullname"]; //-- This is field from linked entity
        var lead_companyname = res[0]["lead_x002e_companyname"]; //-- This is field from linked entity
        var estimatedvalue = res[0]["estimatedvalue"]; //-- This is currency field
        var opportunityid = res[0]["opportunityid"];
        var statecode = res[0]["statecode"]; //-- This is optionset field
        //var statecode_text = res[0]["statecode"].formattedValue;
        var transactioncurrencyid = res[0]["_transactioncurrencyid_value"]; //-- This is lookup field
        //var transactioncurrencyid_name = res[0]["transactioncurrencyid"].name;
    }    
} 

Part-5 – Code Conversion to latest Web API (Dynamics 365)

Quote Close Request

Using 2011 endpoint in CRM 2011 to CRM 2016 we can call request to close quote like given below

//--***********************************************************************************************//
//-- Quote Close Request by Using 2011 End Point
//--***********************************************************************************************//

function getClientUrl() {
    if (typeof Xrm.Page.context == "object") {
        clientUrl = Xrm.Page.context.getClientUrl();
    }
    var ServicePath = "/XRMServices/2011/Organization.svc/web";
    console.log(clientUrl + ServicePath);
    return clientUrl + ServicePath;
}

function QuoteCloseRequestBy2011() {
    var quoteId = Xrm.Page.data.entity.getId();
    var requestMain = "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">";
    requestMain += "  <s:Body>";
    requestMain += "    <Execute xmlns=\"http://schemas.microsoft.com/xrm/2011/Contracts/Services\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">";
    requestMain += "      <request i:type=\"b:WinQuoteRequest\" xmlns:a=\"http://schemas.microsoft.com/xrm/2011/Contracts\" xmlns:b=\"http://schemas.microsoft.com/crm/2011/Contracts\">";
    requestMain += "        <a:Parameters xmlns:c=\"http://schemas.datacontract.org/2004/07/System.Collections.Generic\">";
    requestMain += "          <a:KeyValuePairOfstringanyType>";
    requestMain += "            <c:key>QuoteClose</c:key>";
    requestMain += "            <c:value i:type=\"a:Entity\">";
    requestMain += "              <a:Attributes>";
    requestMain += "                <a:KeyValuePairOfstringanyType>";
    requestMain += "                  <c:key>quoteid</c:key>";
    requestMain += "                  <c:value i:type=\"a:EntityReference\">";
    requestMain += "                    <a:Id>" + quoteId + "</a:Id>";
    requestMain += "                    <a:LogicalName>quote</a:LogicalName>";
    requestMain += "                    <a:Name i:nil=\"true\" />";
    requestMain += "                  </c:value>";
    requestMain += "                </a:KeyValuePairOfstringanyType>";
    requestMain += "                <a:KeyValuePairOfstringanyType>";
    requestMain += "                  <c:key>subject</c:key>";
    requestMain += "                  <c:value i:type=\"d:string\" xmlns:d=\"http://www.w3.org/2001/XMLSchema\">Won the Quote</c:value>";
    requestMain += "                </a:KeyValuePairOfstringanyType>";
    requestMain += "              </a:Attributes>";
    requestMain += "              <a:EntityState i:nil=\"true\" />";
    requestMain += "              <a:FormattedValues />";
    requestMain += "              <a:Id>00000000-0000-0000-0000-000000000000</a:Id>";
    requestMain += "              <a:LogicalName>quoteclose</a:LogicalName>";
    requestMain += "              <a:RelatedEntities />";
    requestMain += "            </c:value>";
    requestMain += "          </a:KeyValuePairOfstringanyType>";
    requestMain += "          <a:KeyValuePairOfstringanyType>";
    requestMain += "            <c:key>Status</c:key>";
    requestMain += "            <c:value i:type=\"a:OptionSetValue\">";
    requestMain += "              <a:Value>4</a:Value>";
    requestMain += "            </c:value>";
    requestMain += "          </a:KeyValuePairOfstringanyType>";
    requestMain += "        </a:Parameters>";
    requestMain += "        <a:RequestId i:nil=\"true\" />";
    requestMain += "        <a:RequestName>WinQuote</a:RequestName>";
    requestMain += "      </request>";
    requestMain += "    </Execute>";
    requestMain += "  </s:Body>";
    requestMain += "</s:Envelope>";

    var req = new XMLHttpRequest();
    req.open("POST", getClientUrl(), false);
    req.setRequestHeader("Accept", "application/xml, text/xml, */*");
    req.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
    req.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/Execute");
    req.send(requestMain);
    console.log(req.status);
    console.log(req.responseText);
    if (req.status === 200) {
    }
}

From 2016 we can use latest web API approach like this

//--***********************************************************************************************//
//-- Quote Close Request by Using Web API
//--***********************************************************************************************//

function QuoteCloseRequestByWebAPI() {
    var quoteId = Xrm.Page.data.entity.getId();
    var isSuccessful = false;
    quoteID = quoteID.replace(/\{|\}/gi, '');;
    var clientURL = Xrm.Page.context.getClientUrl();
    var req = new XMLHttpRequest();
    req.open("POST", encodeURI(clientURL + "/api/data/v8.0/WinQuote"), 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 === 204 || this.status === 200) {
                debugger;
                //Action Executed Successfully
                isSuccessful = true;
                return isSuccessful;
            } else {
                var error = JSON.parse(this.response).error;
                console.log(error.message);
            }
        }
    };

    //Parameters
    var body = JSON.stringify({
        "Status": 4,
        "QuoteClose": {
            "subject": "Won Quote",
            "quoteid@odata.bind": clientURL + "/api/data/v8.0/quotes(" + quoteID + ")"
        }
    }); req.send(body);
    return isSuccessful;
}