Executing Fetch XML With WebAPI In Dynamics 365 Using JavaScript

In this article, we will learn how to execute fetchXML with WebAPI in Dynamics 365 using JavaScript XHR & Fetch API.

In Dynamics 365 WebAPI, we can retrieve data using OData queries, but if there is some complex requirement, it’s better to query using fetchXML. Now, the good news is Dynamics 365 WebAPI supports querying using fetchXML. With fetchXML, we have two main advantages - it can be easily generated using Advanced Find, and it is more readable. You can even use the Joins and Aggregate functions in fetchXML queries which are not possible using Advanced Find (refer http://msxrmtools.com/fetchxml/reference). Let’s see how we can query fetchXML with WebAPI.

Generate fetchXML using Advanced Find

Click on funnel icon to open Advanced Find window.

Dynamics 365

I have created a simple fetchXML query to retrieve all the active cases with few columns.
 
Dynamics 365

Click on “Download Fetch XML” button to get the below fetchXML query.

  1. <fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false">  
  2.   <entity name="incident">  
  3.     <attribute name="title" />  
  4.     <attribute name="ticketnumber" />  
  5.     <attribute name="createdon" />  
  6.     <attribute name="incidentid" />  
  7.     <attribute name="caseorigincode" />  
  8.     <order attribute="title" descending="false" />  
  9.     <filter type="and">  
  10.       <condition attribute="statecode" operator="eq" value="0" />  
  11.     </filter>  
  12.   </entity>  
  13. </fetch>   

Executing fetchXML with GET method

To execute fetchXML, you need to simply append entity’s plural name in WebAPI endpoint and pass fetchXml in query string, and make http/ajax request.

Syntax

WebAPI Endpoint + / + Entity Plural Name + ?fetchXml= + Your fetchXml here.

Example

https://AshV.crm.dynamics.com/api/data/v9.0/incidents?fetchXml=<fetchversion="1.0"output-format="xml-platform"mapping="logical"distinct="false"><entityname="incident"><attributename="title"/><attributename="ticketnumber"/><attributename="createdon"/><attributename="incidentid"/><attributename="caseorigincode"/><orderattribute="title"descending="false"/><filtertype="and"><conditionattribute="statecode"operator="eq"value="0"/></filter></entity></fetch>

The response of this request would be similar to the one give below. As this is a simple GET call, you can even execute this in the browser and test, you can check out one of my tools, FetchXmlTester, which lets you test your fetchXML without sharing your credentials. While running in the browser, we can’t set header Prefer: odata.include-annotations=”*“ to see formatted value.

  1. {    
  2.    "@odata.context":"https://abnai.crm.dynamics.com/api/data/v9.0/$metadata#incidents(title,ticketnumber,createdon,incidentid,caseorigincode)",  
  3.    "value":[    
  4.       {    
  5.          "@odata.etag":"W/\"1095635\"",  
  6.          "title":"Average order shipment time",  
  7.          "ticketnumber":"CAS-01213-P8B3X0",  
  8.          "createdon":"2017-01-20T22:50:45Z",  
  9.          "incidentid":"b69e62a8-90df-e311-9565-a45d36fc5fe8",  
  10.          "caseorigincode":3  
  11.       },  
  12.       {    
  13.          "@odata.etag":"W/\"1092817\"",  
  14.          "title":"Shipping time information resend",  
  15.          "ticketnumber":"CAS-01261-N0C8H9",  
  16.          "createdon":"2017-01-20T22:51:13Z",  
  17.          "incidentid":"169f62a8-90df-e311-9565-a45d36fc5fe8",  
  18.          "caseorigincode":2  
  19.       }  
  20.    ]  
  21. }   

Making GET request with XMLHttpRequest

  1. var fetchXmlQuery = `  
  2. <fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false">  
  3.   <entity name="incident">  
  4.     <attribute name="title" />  
  5.     <attribute name="ticketnumber" />  
  6.     <attribute name="createdon" />  
  7.     <attribute name="incidentid" />  
  8.     <attribute name="caseorigincode" />  
  9.     <order attribute="title" descending="false" />  
  10.     <filter type="and">  
  11.       <condition attribute="statecode" operator="eq" value="0" />  
  12.     </filter>  
  13.   </entity>  
  14. </fetch>`;  
  15.   
  16. var req = new XMLHttpRequest();  
  17. req.open(  
  18.   "GET",  
  19.   Xrm.Page.context.getClientUrl() +  
  20.     "/api/data/v9.0/incidents?fetchXml=" +  
  21.     encodeURIComponent(fetchXmlQuery),  
  22.   true  
  23. );  
  24. req.setRequestHeader("Prefer"'odata.include-annotations="*"');  
  25. req.onreadystatechange = function() {  
  26.   if (this.readyState === 4) {  
  27.     req.onreadystatechange = null;  
  28.     if (this.status === 200) {  
  29.       var results = JSON.parse(this.response);  
  30.       console.dir(results);  
  31.     } else {  
  32.       alert(this.statusText);  
  33.     }  
  34.   }  
  35. };  
  36. req.send();   

Making GET request with JavaScript Fetch API

  1. var fetchXmlQuery = `  
  2. <fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false">  
  3.   <entity name="incident">  
  4.     <attribute name="title" />  
  5.     <attribute name="ticketnumber" />  
  6.     <attribute name="createdon" />  
  7.     <attribute name="incidentid" />  
  8.     <attribute name="caseorigincode" />  
  9.     <order attribute="title" descending="false" />  
  10.     <filter type="and">  
  11.       <condition attribute="statecode" operator="eq" value="0" />  
  12.     </filter>  
  13.   </entity>  
  14. </fetch>`;  
  15.   
  16. fetch(  
  17.   Xrm.Page.context.getClientUrl() +  
  18.     "/api/data/v9.0/incidents?fetchXml=" +  
  19.     encodeURIComponent(fetchXmlQuery),  
  20.   {  
  21.     credentials: "same-origin",  
  22.     headers: {  
  23.       Prefer: 'odata.include-annotations="*"'  
  24.     }  
  25.   }  
  26. )  
  27.   .then(response => response.json())  
  28.   .then(result => console.dir(result))  
  29.   .catch(error => console.error("Error:", error));   

Executing Large fetchXML using POST method with $batch

As I mentioned in the beginning, we can choose to use fetchXML over OData query for complex queries which will be obviously bigger in size. But we have URL length limit which is around 2000 characters roughly (varies in different browsers) so if our URL extends this, the Bad Gateway exception is thrown.

$batch is solution for this. In Dynamics 365 WebAPI, we can execute bigger GET requests using $batch. We need to set header Content-Type: multipart/mixed;boundary=batch_fetchquery and content of GET request should go in body enclosed within value given in boundary, which is batch_fetchquery in our case. To learn more about $batch, you can refer here.

Making POST request with XMLHttpRequest

  1. var fetchXmlQuery = `  
  2. <fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false">  
  3.   <entity name="incident">  
  4.     <attribute name="title" />  
  5.     <attribute name="ticketnumber" />  
  6.     <attribute name="createdon" />  
  7.     <attribute name="incidentid" />  
  8.     <attribute name="caseorigincode" />  
  9.     <order attribute="title" descending="false" />  
  10.     <filter type="and">  
  11.       <condition attribute="statecode" operator="eq" value="0" />  
  12.     </filter>  
  13.   </entity>  
  14. </fetch>`;  
  15.   
  16. var req = new XMLHttpRequest();  
  17. req.open(  
  18.   "POST",  
  19.   Xrm.Page.context.getClientUrl() + "/api/data/v9.0/$batch",  
  20.   true  
  21. );  
  22. req.setRequestHeader(  
  23.   "Content-Type",  
  24.   "multipart/mixed;boundary=batch_fetchquery"  
  25. );  
  26. req.onreadystatechange = function() {  
  27.   if (this.readyState === 4) {  
  28.     req.onreadystatechange = null;  
  29.     if (this.status === 200) {  
  30.       var result = JSON.parse(  
  31.         this.response.substring(  
  32.           this.response.indexOf("{"),  
  33.           this.response.lastIndexOf("}") + 1  
  34.         )  
  35.       );  
  36.       console.dir(result);  
  37.     } else {  
  38.       console.error(this.statusText);  
  39.     }  
  40.   }  
  41. };  
  42.   
  43. var body =  
  44.   "--batch_fetchquery\n" +  
  45.   "Content-Type: application/http\n" +  
  46.   "Content-Transfer-Encoding: binary\n" +  
  47.   "\n" +  
  48.   "GET " +  
  49.   Xrm.Page.context.getClientUrl() +  
  50.   "/api/data/v9.0/incidents?fetchXml=" +  
  51.   encodeURIComponent(fetchXmlQuery) +  
  52.   " HTTP/1.1\n" +  
  53.   'Prefer: odata.include-annotations="*"\n' +  
  54.   "\n" +  
  55.   "--batch_fetchquery--";  
  56.   
  57. req.send(body);   

Making POST request with JavaScript Fetch API

  1. var fetchXmlQuery = `  
  2. <fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false">  
  3.   <entity name="incident">  
  4.     <attribute name="title" />  
  5.     <attribute name="ticketnumber" />  
  6.     <attribute name="createdon" />  
  7.     <attribute name="incidentid" />  
  8.     <attribute name="caseorigincode" />  
  9.     <order attribute="title" descending="false" />  
  10.     <filter type="and">  
  11.       <condition attribute="statecode" operator="eq" value="0" />  
  12.     </filter>  
  13.   </entity>  
  14. </fetch>`;  
  15.   
  16. fetch(Xrm.Page.context.getClientUrl() + "/api/data/v9.0/$batch", {  
  17.   body:  
  18.     "--batch_fetchquery\n" +  
  19.     "Content-Type: application/http\n" +  
  20.     "Content-Transfer-Encoding: binary\n" +  
  21.     "\n" +  
  22.     "GET " +  
  23.     Xrm.Page.context.getClientUrl() +  
  24.     "/api/data/v9.0/incidents?fetchXml=" +  
  25.     encodeURIComponent(fetchXmlQuery) +  
  26.     " HTTP/1.1\n" +  
  27.     'Prefer: odata.include-annotations="*"\n' +  
  28.     "\n" +  
  29.     "--batch_fetchquery--",  
  30.   headers: {  
  31.     "Content-Type""multipart/mixed;boundary=batch_fetchquery"  
  32.   },  
  33.   credentials: "same-origin",  
  34.   method: "POST"  
  35. })  
  36.   .then(response => response.text())  
  37.   .then(data => {  
  38.     var result = JSON.parse(  
  39.       data.substring(data.indexOf("{"), data.lastIndexOf("}") + 1)  
  40.     );  
  41.     console.log(result);  
  42.   })  
  43.   .catch(error => console.error("Error:", error));   

Testing the code in Dyanmics 365

To test the above code, I made small tweaks to run it from HTML web resource as you can see below.

  1. <!DOCTYPE html>  
  2. <html>  
  3. <head>  
  4.     <title>Execute fetchXML with WebAPI in Dynamics 365 using JavaScript</title>  
  5.     <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/milligram/1.3.0/milligram.min.css" integrity="sha256-Ro/wP8uUi8LR71kwIdilf78atpu8bTEwrK5ZotZo+Zc=" crossorigin="anonymous" />  
  6.     <style> button { width:100% } </style>  
  7. </head>  
  8. <body>  
  9.     <h1>Testing : Execute fetchXML with WebAPI in Dynamics 365 using JavaScript</h1>  
  10.         <button onclick="GetXHR()" id="GetXHR">Test using GET with XMLHttpRequest</button>  
  11.         <button onclick="GetFetch()" id="GetFetch">Test using GET with Fetch API</button>  
  12.         <button onclick="PostXHR()" id="PostXHR">Test using POST with XMLHttpRequest</button>  
  13.         <button onclick="PostFetch()" id="PostFetch">Test using POST with Fetch API</button>  
  14.     <script>  
  15.         var fetchXmlQuery = `  
  16.             <fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false">  
  17.               <entity name="incident">  
  18.                 <attribute name="title" />  
  19.                 <attribute name="ticketnumber" />  
  20.                 <attribute name="createdon" />  
  21.                 <attribute name="incidentid" />  
  22.                 <attribute name="caseorigincode" />  
  23.                 <order attribute="title" descending="false" />  
  24.                 <filter type="and">  
  25.                   <condition attribute="statecode" operator="eq" value="0" />  
  26.                 </filter>  
  27.               </entity>  
  28.             </fetch>`;  
  29.         var clientURL = location.protocol + '//' + location.host  
  30.   
  31.         function GetXHR() {  
  32.             var req = new XMLHttpRequest();  
  33.             req.open(  
  34.                 "GET",  
  35.                 clientURL +  
  36.                 "/api/data/v9.0/incidents?fetchXml=" +  
  37.                 encodeURIComponent(fetchXmlQuery),  
  38.                 true  
  39.             );  
  40.             req.setRequestHeader("Prefer", 'odata.include-annotations="*"');  
  41.             req.onreadystatechange = function () {  
  42.                 if (this.readyState === 4) {  
  43.                     req.onreadystatechange = null;  
  44.                     if (this.status === 200) {  
  45.                         var results = JSON.parse(this.response);  
  46.                         console.dir(results);  
  47.                         alert(JSON.stringify(results));  
  48.                     } else {  
  49.                         alert(this.statusText);  
  50.                     }  
  51.                 }  
  52.             };  
  53.             req.send();  
  54.         }  
  55.   
  56.         function GetFetch() {  
  57.             fetch(  
  58.                 clientURL +  
  59.                 "/api/data/v9.0/incidents?fetchXml=" +  
  60.                 encodeURIComponent(fetchXmlQuery),  
  61.                 {  
  62.                     credentials: "same-origin",  
  63.                     headers: {  
  64.                         Prefer: 'odata.include-annotations="*"'  
  65.                     }  
  66.                 }  
  67.             )  
  68.                 .then(response => response.json())  
  69.                 .then(result => { console.dir(result); alert(JSON.stringify(result)); })  
  70.                 .catch(error => console.error("Error:", error));  
  71.         }  
  72.           
  73.         function PostXHR() {  
  74.             var req = new XMLHttpRequest();  
  75.             req.open(  
  76.                 "POST",  
  77.                 clientURL + "/api/data/v9.0/$batch",  
  78.                 true  
  79.             );  
  80.             req.setRequestHeader(  
  81.                 "Content-Type",  
  82.                 "multipart/mixed;boundary=batch_fetchquery"  
  83.             );  
  84.             req.onreadystatechange = function () {  
  85.                 if (this.readyState === 4) {  
  86.                     req.onreadystatechange = null;  
  87.                     if (this.status === 200) {  
  88.                         var result = JSON.parse(  
  89.                             this.response.substring(  
  90.                                 this.response.indexOf("{"),  
  91.                                 this.response.lastIndexOf("}") + 1  
  92.                             )  
  93.                         );  
  94.                         console.dir(result);  
  95.                         alert(JSON.stringify(result));  
  96.                     } else {  
  97.                         console.error(this.statusText);  
  98.                     }  
  99.                 }  
  100.             };  
  101.   
  102.             var body =  
  103.                 "--batch_fetchquery\n" +  
  104.                 "Content-Type: application/http\n" +  
  105.                 "Content-Transfer-Encoding: binary\n" +  
  106.                 "\n" +  
  107.                 "GET " +  
  108.                 clientURL +  
  109.                 "/api/data/v9.0/incidents?fetchXml=" +  
  110.                 encodeURIComponent(fetchXmlQuery) +  
  111.                 " HTTP/1.1\n" +  
  112.                 'Prefer: odata.include-annotations="*"\n' +  
  113.                 "\n" +  
  114.                 "--batch_fetchquery--";  
  115.   
  116.             req.send(body);  
  117.         }  
  118.   
  119.         function PostFetch() {  
  120.             fetch(clientURL + "/api/data/v9.0/$batch", {  
  121.                 body:  
  122.                     "--batch_fetchquery\n" +  
  123.                     "Content-Type: application/http\n" +  
  124.                     "Content-Transfer-Encoding: binary\n" +  
  125.                     "\n" +  
  126.                     "GET " +  
  127.                     clientURL +  
  128.                     "/api/data/v9.0/incidents?fetchXml=" +  
  129.                     encodeURIComponent(fetchXmlQuery) +  
  130.                     " HTTP/1.1\n" +  
  131.                     'Prefer: odata.include-annotations="*"\n' +  
  132.                     "\n" +  
  133.                     "--batch_fetchquery--",  
  134.                 headers: {  
  135.                     "Content-Type": "multipart/mixed;boundary=batch_fetchquery"  
  136.                 },  
  137.                 credentials: "same-origin",  
  138.                 method: "POST"  
  139.             })  
  140.                 .then(response => response.text())  
  141.                 .then(data => {  
  142.                     var result = JSON.parse(  
  143.                         data.substring(data.indexOf("{"), data.lastIndexOf("}") + 1)  
  144.                     );  
  145.                     console.log(result);  
  146.                     alert(JSON.stringify(result));  
  147.                 })  
  148.                 .catch(error => console.error("Error:", error));  
  149.         }  
  150.     </script>  
  151. </body>  
  152. </html>   

Create a new HTML web resource with the above code & publish it.

Dynamics 365

Click on the URL to open it, you will see the below window.

Dynamics 365

Press F12 to open the console. Click on buttons & verify the retrieved data in console or alert.

Dynamics 365
Thanks for reading; I hope it helps!