Download File : Power Automate and JavaScript

What is Issue?

When working on D365 CRM, Some time user wants to download file from SharePoint, OneDrive or CRM Notes itself. So how you will do it in shortest way?

The Reason-

Downloading file from SharePoint or any other cloud location it will be time consuming process if you choose Web service or any WCF service.

Solution-

Now Power Automate provides less code and quick solutions. Power Automate has all the connectors and Actions which help you to connect these cloud locations and process on the files.

Let’s see a scenario, I want to download a file on button click on my CRM Account form. The file is located in my account record Timeline Notes.

Let’s go step by step.

First of all, as this is action on click of button on form, we can choose “When Http Request Received” trigger in Power Automate. And in HTTP request we need record id i.e. accountid to retrieve specific Note attachment i.e. “CustomerInfoDoc”-

Now from above step we will get the Document – File name and Document body (base64) content.

So we need a variable to store files in array and another variable for document content-

Setting the variables from the Notes list which we got from “List rows” action-

We need to append array with below json-

{
“Name”: @{items(‘Apply_to_each’)?[‘filename’]},
“ContentBytes”: @{items(‘Apply_to_each’)?[‘documentbody’]}
}

We will use Name and ContentBytes in JavaScript Code which will be in the response of HttpRequest.

Now once file we got file content we need to send them in response-

We need the only file from Notes, In List Row action we can add row count =1, but still in response we can set formula in body first(variable(‘ContentArray’). As this is document in response add headers “Content-type”:”multipart/form-data”

Now our complete flow is ready and it looks like –

So now remaining part is calling the flow from button to download the file-

Add the below javascript and call “callPowerAutomateFlow” function on button click. Below is complete code for download –

function callPowerAutomateFlow(formContext) { 
    id = formContext.data.entity.getId().replace("{", "").replace("}", ""); 
    var account = '{ "accountid": "' + id + '"'+ '}'; 
    var flowUrl = "https://prod-04.centralindia.logic.azure.com:443/workflows/xxxxxxxxxxxxxxxxxxxxxxxxxx/triggers/manual/paths/invoke?api-version=2016-06-01&sp=%2Ftriggers%2Fmanual%2Frun&sv=1.0&sig=xxxxxxxxxxxx"; 
 
    var req = new XMLHttpRequest(); 
    req.open("POST", flowUrl, true); 
    req.setRequestHeader("Accept", "application/json"); 
    req.setRequestHeader("Content-Type", "application/json; charset=utf-8"); 
    req.onreadystatechange = function () { 
        if (this.readyState === 4) { 
            req.onreadystatechange = null; 
            if (this.status === 200) { 
                    var result = JSON.parse(this.response); 
                    var fileName = result.Name; 
                    var fileCont = result.ContentBytes; 
                    var filebyte = base64ToBufferArray(fileCont); 
                    downloadFile(fileName, [filebyte]); 
            } else { 
                Xrm.Utility.alertDialog(this.responseText); 
            } 
        } 
    }; 
    req.send(account); 
} 
 
function base64ToBufferArray(base64content) { 
    var binaryString = window.atob(base64content); 
    var binaryLen = binaryString.length; 
    var bytes = new Uint8Array(binaryLen); 
    for (var i = 0; i < binaryLen; i++) { 
        var ascii = binaryString.charCodeAt(i); 
        bytes[i] = ascii; 
    } 
    return bytes; 
} 
 
function downloadFile(name,data) { 
        var blob = new Blob(data, { type: "octet/stream" }), 
            url = window.URL.createObjectURL(blob); 
        if (window.navigator && window.navigator.msSaveOrOpenBlob) { 
            window.navigator.msSaveOrOpenBlob(blob, name); 
        } else { 
            const url = window.URL.createObjectURL(blob); 
            const a = document.createElement('a'); 
            document.body.appendChild(a); 
            a.href = url; 
            a.download = name; 
            a.click(); 
            window.URL.revokeObjectURL(url); 
            document.body.removeChild(a); 
        } 
}

That’s it!! when I am clicking on Download button in CRM Account record, it is downloading my Attachment file from Notes which is with title “CustomerInfoDoc”.

Hope this will help…
Enjoy Ms CRM!!!

Follow on Facebook- FB: MSCRM16Tech

Batch operation ($batch) using OData API Dynamics 365

What is Issue?

There are OData API provided by Microsoft to perform operations on D365 Tables like Create, update, delete and retrieve. These operations can be perform individually by building the OData API using some tools or with your knowledge. But some time there are the cases where you need to call the bulk create, bulk update, bulk delete or some bulk create and bulk update/delete simultaneously. If there are more than one records, you need to hit OData API one by one multiple times which takes cost in terms of time and performance of the application which is calling OData API. Is there a way to combine these all requests in one call and execute?

The Reason-

There is way to perform the CRUD operations on D365 Tables using OData, but everyone aware of the single operations. Microsoft provides the way to perform Batch operation on Tables using OData API. Lets see in solution for this.

Solution-

Microsoft D365 provides the OData for batch operation to perform using “$batch” like-

https://your_org.crm8.dynamics.com/api/data/v8.2/$batch

You need to include the all other CRUD (POST, PATCH, DELETE, GET) OData APIs in the body for the above request URL.

This request works like all other OData APIs using OAuth2.0 authentication with proper headers added.

Lets take .Net application where I need to perform the Create, update and some delete operation in one call.

You have to prepare your request as below-

Preparing the Batch Call-

#region prepare Batch Call
        private string BatchQueryCall(string crmRestQuery, string batchName, string requestBody)
        {
            string funResponse = null;
            try
            {
                HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, crmRestQuery);
                //add header parameters
                request.Headers.Add("Authorization", "Bearer " + oauthToken);
                request.Headers.Add("Accept", "application/json");
                request.Content = new StringContent(requestBody);
                request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("multipart/mixed;boundary=" + batchName);

                HttpResponseMessage response = httpClient.SendAsync(request).Result;
                string responseString = response.Content.ReadAsStringAsync().Result;

                if (response.IsSuccessStatusCode)
                {
                    funResponse = responseString;
                }
                else
                    funResponse = responseString;

            }
            catch (Exception ex)
            {
                funResponse = ex.Message;
            }
            return funResponse;
        }
        #endregion

Preparing to Call Batch Operation-

Suppose I have to create one record, update one record and delete 2 records in same call.
Below code helps you to understand preparing the Batch call-

  #region bulk Operations
        public string BulkOperationsAPI(string entityname)
        {
            #region Local Variable
            string crmRestQuery = apiUrl + "$batch";
            string batchName = "batch_bt123";
            string changeSetVar = "changeset_ch123";
            int coRelId = 1;
            string changeSet1 = null;
            string requestBody = "--" + batchName + Environment.NewLine
                             + "Content-Type:multipart/mixed;boundary=" + changeSetVar + Environment.NewLine + Environment.NewLine + Environment.NewLine;
            #endregion

            #region Create
                //Creating one record
                CreateUpdateOpportunity entity = new CreateUpdateOpportunity();

                entity.transactioncurrencyid_odata_bind = "/transactioncurrencies(c63ba581-6bc6-e811-a96f-000d3af04fb0)";
                entity.estimatedvalue = 1000;
                entity.estimatedclosedate = "2021-01-01";
                entity.name = "Generated From C# Code";
                entity.parentaccountid_odata_bind = "/accounts(3b3b7c71-61d2-ea11-a813-000d3af0205e)";

                changeSet1 = PrepareReuqestBody("POST", changeSetVar, coRelId++, entityname, null, entity);

                if (!string.IsNullOrEmpty(changeSet1))
                    requestBody += changeSet1 + Environment.NewLine;
            
            #endregion

            #region Update
                //updating One record
                CreateUpdateOpportunity entity1 = new CreateUpdateOpportunity();

                entity1.transactioncurrencyid_odata_bind = "/transactioncurrencies(c63ba581-6bc6-e811-a96f-000d3af04fb0)";
                entity1.estimatedvalue = 1000;
                entity1.estimatedclosedate = "2021-01-01";
                entity1.name = "Generated From CSharp Code";
                entity1.parentaccountid_odata_bind = "/accounts(3b3b7c71-61d2-ea11-a813-000d3af0205e)";

                changeSet1 = PrepareReuqestBody("PATCH", changeSetVar, coRelId++, entityname, "20e6bb72-2861-eb11-a812-0022486e93ce", entity1);

                if (!string.IsNullOrEmpty(changeSet1))
                    requestBody += changeSet1 + Environment.NewLine;
            #endregion

            #region Delete
            //delete 1st record  
            changeSet1 = PrepareReuqestBody("DELETE", changeSetVar, coRelId++, entityname, "29e6bb72-2861-eb11-a812-0022486e93ce", null);
                if (!string.IsNullOrEmpty(changeSet1))
                    requestBody += changeSet1 + Environment.NewLine;

            //delete 2nd record
            changeSet1 = PrepareReuqestBody("DELETE", changeSetVar, coRelId++, entityname, "27e6bb72-2861-eb11-a812-0022486e93ce", null);
            if (!string.IsNullOrEmpty(changeSet1))
                requestBody += changeSet1 + Environment.NewLine;
            #endregion


            requestBody += "--" + changeSetVar + "--" + Environment.NewLine;
            requestBody += "--" + batchName + "--";
            
            //format the body URLs
            requestBody = requestBody.Replace("\\/", "/");  

            //call the batch Operations
            return BatchQueryCall(crmRestQuery, batchName, requestBody);
        }
        #endregion

Preparing the body content of Batch call-

In above code there is call made to the function PrepareRequestBody-
This function will prepare the body content of Batch call.

        #region Prepare Request Body
        private string PrepareReuqestBody(string method, string changeset, int contentId, string entityName, string recordid, object entity)
        {

            string x = null;
            switch (method.ToUpper())
            {
                case "PATCH":
                    using (MemoryStream streamOpportunitySerialize = new MemoryStream())
                    {
                        DataContractJsonSerializer ser = new DataContractJsonSerializer(entity.GetType());
                        ser.WriteObject(streamOpportunitySerialize, entity);
                        streamOpportunitySerialize.Position = 0;
                        StreamReader srOpportunity = new StreamReader(streamOpportunitySerialize);
                        string objectJSON = srOpportunity.ReadToEnd();
                        objectJSON = objectJSON.Replace("_odata_bind", "@odata.bind");

                        x = "--" + changeset + Environment.NewLine
                            + "Content-Type: application/http" + Environment.NewLine
                            + "Content-Transfer-Encoding: binary" + Environment.NewLine
                            + "Content-ID: " + contentId + Environment.NewLine + Environment.NewLine
                            + "PATCH " + apiUrl + entityName + "(" + recordid + ") HTTP/1.1" + Environment.NewLine
                            + "Content-Type: application/json;type=entry" + Environment.NewLine
                            + "Accept:application/json" + Environment.NewLine + Environment.NewLine
                            + objectJSON + Environment.NewLine + Environment.NewLine;
                    }
                    break;
                case "POST":
                    using (MemoryStream streamOpportunitySerialize = new MemoryStream())
                    {
                        DataContractJsonSerializer ser = new DataContractJsonSerializer(entity.GetType());
                        ser.WriteObject(streamOpportunitySerialize, entity);
                        streamOpportunitySerialize.Position = 0;
                        StreamReader srOpportunity = new StreamReader(streamOpportunitySerialize);
                        string objectJSON = srOpportunity.ReadToEnd();
                        objectJSON = objectJSON.Replace("_odata_bind", "@odata.bind");

                        x = "--" + changeset + Environment.NewLine
                            + "Content-Type: application/http" + Environment.NewLine
                            + "Content-Transfer-Encoding: binary" + Environment.NewLine
                            + "Content-ID: " + contentId + Environment.NewLine + Environment.NewLine
                            + "POST " + apiUrl + entityName + " HTTP/1.1" + Environment.NewLine
                            + "Content-Type: application/json;type=entry" + Environment.NewLine
                            + "Accept:application/json" + Environment.NewLine + Environment.NewLine
                            + objectJSON + Environment.NewLine + Environment.NewLine;
                    }

                    break;
                case "DELETE":
                    x = "--" + changeset + Environment.NewLine
                    + "Content-Type: application/http" + Environment.NewLine
                    + "Content-Transfer-Encoding: binary" + Environment.NewLine
                    + "Content-ID: " + contentId + Environment.NewLine + Environment.NewLine
                    + "DELETE " + apiUrl + entityName + "(" + recordid + ") HTTP/1.1" + Environment.NewLine
                    + "Accept:application/json" + Environment.NewLine + Environment.NewLine;
                    break;
            }

            return x;
        }
        #endregion

Request Body content will look like-

--batch_bt123
Content-Type:multipart/mixed;boundary=changeset_ch123


--changeset_ch123
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 1

POST https://your_org.crm8.dynamics.com/api/data/v8.2/opportunities HTTP/1.1
Content-Type: application/json;type=entry
Accept:application/json

{"estimatedclosedate":"2021-01-01","estimatedvalue":1000,"name":"Generated From C# Code","parentaccountid@odata.bind":"/accounts(3b3b7c71-61d2-ea11-a813-000d3af0205e)","transactioncurrencyid@odata.bind":"/transactioncurrencies(c63ba581-6bc6-e811-a96f-000d3af04fb0)"}


--changeset_ch123
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 2

PATCH https://your_org.crm8.dynamics.com/api/data/v8.2/opportunities(d659b4d6-0f5b-eb11-a812-6045bd727e64) HTTP/1.1
Content-Type: application/json;type=entry
Accept:application/json

{"estimatedclosedate":"2021-01-01","estimatedvalue":1000,"name":"Generated From CSharp Code","parentaccountid@odata.bind":"/accounts(3b3b7c71-61d2-ea11-a813-000d3af0205e)","transactioncurrencyid@odata.bind":"/transactioncurrencies(c63ba581-6bc6-e811-a96f-000d3af04fb0)"}


--changeset_ch123
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 3

DELETE https://your_org.crm8.dynamics.com/api/data/v8.2/opportunities(46c9eab1-5ac2-4673-a55a-51cb886db902) HTTP/1.1
Accept:application/json


--changeset_ch123
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 4

DELETE https://your_org.crm8.dynamics.com/api/data/v8.2/opportunities(d659b4d6-0f5b-eb11-a812-6045bd727e64) HTTP/1.1
Accept:application/json


--changeset_ch123--
--batch_bt123--

Response recieved will be like-

  --batchresponse_319a4c10-b7fe-4bf0-b4a6-da016d1c0319
Content-Type: multipart/mixed; boundary=changesetresponse_ee30dcdb-1094-4c24-8170-262eae9336a4

--changesetresponse_ee30dcdb-1094-4c24-8170-262eae9336a4
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 1

HTTP/1.1 204 No Content
OData-Version: 4.0
Location: https://your_org.crm8.dynamics.com/api/data/v8.2/opportunities(3d4916b4-3861-eb11-a812-0022486e93ce)
OData-EntityId: https://your_org.crm8.dynamics.com/api/data/v8.2/opportunities(3d4916b4-3861-eb11-a812-0022486e93ce)


--changesetresponse_ee30dcdb-1094-4c24-8170-262eae9336a4
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 2

HTTP/1.1 204 No Content
OData-Version: 4.0
Location: https://your_org.crm8.dynamics.com/api/data/v8.2/opportunities(d659b4d6-0f5b-eb11-a812-6045bd727e64)
OData-EntityId: https://your_org.crm8.dynamics.com/api/data/v8.2/opportunities(d659b4d6-0f5b-eb11-a812-6045bd727e64)


--changesetresponse_ee30dcdb-1094-4c24-8170-262eae9336a4
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 3

HTTP/1.1 204 No Content
OData-Version: 4.0


--changesetresponse_ee30dcdb-1094-4c24-8170-262eae9336a4
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 4

HTTP/1.1 204 No Content
OData-Version: 4.0


--changesetresponse_ee30dcdb-1094-4c24-8170-262eae9336a4--
--batchresponse_319a4c10-b7fe-4bf0-b4a6-da016d1c0319--

For Token Generation you can refer the code in my last blog – https://mscrm16tech.com/2021/01/21/crud-operations-on-dynamics-365-crm-tables-with-odata-api/

Hope this will help…
Enjoy Ms CRM!!!

Follow on Facebook- FB: MSCRM16Tech

CRUD Operations on Dynamics 365 CRM Tables with ODATA API

What is Issue?

Performing CRUD operation on Dynamics 365 CRM using API (Rest/ODATA) from other applications.

The Reason-

While performing integration of other application with CRM, creating custom Web API and webservice is having much efforts in terms of time and development. Microsoft already made developers job easy by providing the ODATA API available for their products. How to consume that API in other applications.

Solution-

While performing integration of other application with CRM, rather than creating the custom API or webservice, we can utilize ODATA APIs that can be consumed with the OAuth 2.0 authentication.

Lets take .net application which is consuming ODATA API.

Some time we don’t want to include any third party dll due to some environmental restrictions. so the sample code added below is using only system libraries.

(In case of Json serialization, you can utilize Newtonsoft.Json to avoid the classes to be written manually)

Create records using ODATA API-

#region Create API Call
        /// <summary>
        /// Create the entity Record using API Call
        /// </summary>
        /// <param name="entityname">entity Name</param>
        /// <param name="entity">entity Object with parameter values</param>
        /// <returns></returns>
        public string CreateAPI(string entityname, object entity)
        {
            string funResponse = null;
            string query = entityname;
            string crmRestQuery = apiUrl + query;

            try
            {
                using (MemoryStream streamOpportunitySerialize = new MemoryStream())
                {
                    DataContractJsonSerializer ser = new DataContractJsonSerializer(entity.GetType());
                    ser.WriteObject(streamOpportunitySerialize, entity);
                    streamOpportunitySerialize.Position = 0;
                    StreamReader srOpportunity = new StreamReader(streamOpportunitySerialize);
                    string objectJSON = srOpportunity.ReadToEnd();
                    objectJSON = objectJSON.Replace("_odata_bind", "@odata.bind");

                    HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, crmRestQuery);
                    //add header parameters
                    request.Headers.Add("Authorization", "Bearer " + oauthToken);
                    request.Content = new StringContent(objectJSON);
                    request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json; charset=utf-8");

                    HttpResponseMessage response = httpClient.SendAsync(request).Result;
                    string responseString = response.Content.ReadAsStringAsync().Result;

                    if (response.IsSuccessStatusCode)
                    {
                        string _recordUrl = response.Headers.GetValues("OData-EntityId").FirstOrDefault();
                        string[] splitRetrievedData = _recordUrl.Split('[', '(', ')', ']');
                        funResponse = splitRetrievedData[1];
                    }
                    else
                        funResponse = responseString;

                }
            }
            catch (Exception ex)
            {
                funResponse = ex.Message;
            }
            return funResponse;
        }
        #endregion

Update records using ODATA API-

  #region Update API Call
        /// <summary>
        /// Update the entity record using API Call
        /// </summary>
        /// <param name="entityname">entity name</param>
        /// <param name="entityid">entity id</param>
        /// <param name="entity">entity Object with parameter values</param>
        /// <returns></returns>
        public string UpdateAPI(string entityname, string entityid, object entity)
        {
            string funResponse = null;
            string query = entityname + "(" + entityid + ")";
            string crmRestQuery = apiUrl + query;
            try
            {
                using (MemoryStream streamOpportunitySerialize = new MemoryStream())
                {

                    DataContractJsonSerializer ser = new DataContractJsonSerializer(entity.GetType());
                    ser.WriteObject(streamOpportunitySerialize, entity);
                    streamOpportunitySerialize.Position = 0;
                    StreamReader srOpportunity = new StreamReader(streamOpportunitySerialize);
                    string objectJSON = srOpportunity.ReadToEnd();
                    objectJSON = objectJSON.Replace("_odata_bind", "@odata.bind");

                    HttpMethod method = new HttpMethod("PATCH");
                    HttpRequestMessage request = new HttpRequestMessage(method, crmRestQuery);
                    //add header parameters
                    request.Headers.Add("Authorization", "Bearer " + oauthToken);
                    request.Headers.Add("If-Match", "*");      //preventing creation of new record if no record found with ID
                    request.Content = new StringContent(objectJSON);
                    request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json; charset=utf-8");

                    HttpResponseMessage response = httpClient.SendAsync(request).Result;
                    string responseString = response.Content.ReadAsStringAsync().Result;
                    if (response.IsSuccessStatusCode)
                    {
                        funResponse = "Sucessfully Updated Record!!";
                    }
                    else
                        funResponse = responseString;
                }
            }
            catch (Exception ex)
            {
                funResponse = ex.Message;
            }
            return funResponse;
        }
        #endregion

Delete records using ODATA API-

#region Delete API Call
        /// <summary>
        /// Delete the entity Record using API Call
        /// </summary>
        /// <param name="entityname">entity Name</param>
        /// <param name="entityid">Entity id</param>
        /// <returns></returns>
        public string DeleteAPI(string entityname, string entityid)
        {
            string funResponse = null;
            string query = entityname + "(" + entityid + ")";
            string crmRestQuery = apiUrl + query;

            try
            {
                HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Delete, crmRestQuery);
                //add header parameters
                request.Headers.Add("Authorization", "Bearer " + oauthToken);
                request.Headers.Add("Accept", "application/json; charset=utf-8");

                HttpResponseMessage response = httpClient.SendAsync(request).Result;
                string responseString = response.Content.ReadAsStringAsync().Result;
                if (response.IsSuccessStatusCode)
                    funResponse = "Deleted!!!";
                else
                    funResponse = responseString;
            }
            catch (Exception ex)
            {
                funResponse = ex.Message;
            }
            return funResponse;
        }
        #endregion

Retrieve Single record using ODATA API-

 /// <summary>
        /// Retrieve single using API Call
        /// </summary>
        /// <param name="entityPluralName">entity Name</param>
        /// <param name="entityId">entity id</param>
        /// <param name="entityObject">blank entity object</param>
        /// <param name="selectColumns">select columns to retrieve in API Call</param>
        /// <returns>Object of entity</returns>
        public object RetrieveSingle(string entityPluralName,string entityId,object entityObject, string selectColumns=null)
        {
            string query = "("+entityId+")";
            if (selectColumns != null) {
                query += "?$select=" + selectColumns;
            }
            string crmRestQuery = apiUrl + entityPluralName + query;
            try
            {
                    HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, crmRestQuery);
                    //add header parameters
                    request.Headers.Add("Prefer", "odata.include-annotations=\"*\"");//for formatted values
                    request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/XML"));
                    request.Headers.Add("Authorization", "Bearer " + oauthToken);

                    //send request
                    HttpResponseMessage response = httpClient.SendAsync(request).Result;
                    string responseString = response.Content.ReadAsStringAsync().Result;
                    responseString = FormatResponse(responseString);

                    using (var ms = new MemoryStream(Encoding.Unicode.GetBytes(responseString)))
                    {
                        // Deserialization from JSON  
                        DataContractJsonSerializer deserializer = new DataContractJsonSerializer(entityObject.GetType());
                    entityObject = (object)deserializer.ReadObject(ms);
                    }
                    
            }
            catch (Exception ex){ throw ex; }
            return entityObject;
        }

Retrieve Multiple records using ODATA API-

  /// <summary>
        /// Retrieve multiple using Fetch XML in API Call
        /// </summary>
        /// <param name="entityPluralName">entity name</param>
        /// <param name="fetchXML">fetchXML string</param>
        /// <returns>List of retrieved Objects</returns>
        public List<RetrieveOpportunity> RetrieveMultiple(string entityPluralName, string fetchXML)
        {
            fetchXML = fetchXML.Replace("<fetch", "<fetch {0} ");
            int page = 1;
            var cookie = string.Format("page='{0}'", page);
            string xml = string.Format(fetchXML, cookie);
            string crmRestQuery = apiUrl + entityPluralName + "?fetchXml=" + xml;
            List<RetrieveOpportunity> lstOpties = new List<RetrieveOpportunity>();
            bool isDone = false;
            try
            {
                while (isDone == false)
                {
                    isDone = true;
                    HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, crmRestQuery);
                    //add header parameters
                    request.Headers.Add("Prefer", "odata.include-annotations=\"*\"");//for formatted values
                    request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/XML"));
                    request.Headers.Add("Authorization", "Bearer " + oauthToken);

                    //send request
                    HttpResponseMessage response = httpClient.SendAsync(request).Result;
                    string responseString = response.Content.ReadAsStringAsync().Result;
                    responseString = FormatResponse(responseString);

                    using (var ms = new MemoryStream(Encoding.Unicode.GetBytes(responseString)))
                    {
                        // Deserialization from JSON  
                        DataContractJsonSerializer deserializer = new DataContractJsonSerializer(typeof(APIResult));
                        APIResult apiResult = (APIResult)deserializer.ReadObject(ms);

                        if (!string.IsNullOrEmpty(apiResult.Microsoft_Dynamics_CRM_fetchxmlpagingcookie))
                        {
                            isDone = false;
                            page++;

                            //retrieve pagingCookie value
                            string xmlVal = WebUtility.UrlDecode((WebUtility.UrlDecode((string)apiResult.Microsoft_Dynamics_CRM_fetchxmlpagingcookie)));
                            int Start = xmlVal.IndexOf("pagingcookie=\"", 0) + "pagingcookie=\"".Length;
                            int End = xmlVal.IndexOf("\" istracking=\"False\"", Start);

                            //proccess the pagingCookie value to support in API URL
                            string strVal = xmlVal.Substring(Start, End - Start).Replace("<", "%26lt;").Replace(">", "%26gt;").Replace("\"", "%26quot;").Replace("'", "%26apos;").Replace("&", "%26amp;");
                            cookie = string.Format("page='{0}' paging-cookie='{1}'", page, strVal);

                            //modify the query for next page with paging cookie
                            xml = string.Format(fetchXML, cookie);
                            crmRestQuery = apiUrl + entityPluralName +"?fetchXml=" + xml;

                        }
                        if (!string.IsNullOrEmpty(apiResult.odata_nextLink))
                        {
                            isDone = false;
                            crmRestQuery = apiResult.odata_nextLink;
                        }

                        if (lstOpties == null || lstOpties.Count == 0)
                        {
                            lstOpties = apiResult.value;
                        }
                        else
                        {
                            lstOpties.AddRange(apiResult.value);
                        }
                    }
                }
            }
            catch (Exception ex) {
                throw ex;
            }
            return lstOpties;
        }

        public List<RetrieveOpportunity> RetrieveMultiple(string entityPluralName, string selectColumns,string filterConditions=null, string orderby=null,bool returnCount=false)
        {
           string query= "?$select=" + selectColumns;
            if (filterConditions != null) {
                query += "&$filter=" + filterConditions;
            }
            if (orderby != null) {
                query += "&$orderby=" + orderby;
            }
            if (returnCount == true) {
                query += "&$count=true";
            }

            string crmRestQuery = apiUrl + entityPluralName +  query;
            List<RetrieveOpportunity> lstOpties = new List<RetrieveOpportunity>();
            bool isDone = false;
            try
            {
                while (isDone == false)
                {
                    isDone = true;
                    HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, crmRestQuery);
                    //add header parameters
                    request.Headers.Add("Prefer", "odata.include-annotations=\"*\"");//for formatted values
                    request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/XML"));
                    request.Headers.Add("Authorization", "Bearer " + oauthToken);

                    //send request
                    HttpResponseMessage response = httpClient.SendAsync(request).Result;
                    string responseString = response.Content.ReadAsStringAsync().Result;
                    responseString = FormatResponse(responseString);

                    using (var ms = new MemoryStream(Encoding.Unicode.GetBytes(responseString)))
                    {
                        // Deserialization from JSON  
                        DataContractJsonSerializer deserializer = new DataContractJsonSerializer(typeof(APIResult));
                        APIResult apiResult = (APIResult)deserializer.ReadObject(ms);
                        
                        if (!string.IsNullOrEmpty(apiResult.odata_nextLink))
                        {
                            isDone = false;
                            crmRestQuery = apiResult.odata_nextLink;
                        }

                        if (lstOpties == null || lstOpties.Count == 0)
                        {
                            lstOpties = apiResult.value;
                        }
                        else
                        {
                            lstOpties.AddRange(apiResult.value);
                        }

                    }
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
            return lstOpties;
        }

Some common Methods in above Code –

  #region Local variables
        /// <summary>
        /// Local Variables
        /// </summary>
        private string clientID, secretKey, resource, oauthToken, apiUrl, tokenURL;
        private HttpClient httpClient;

        /// <summary>
        /// Constructor to initialize the variables
        /// </summary>
        public Operations()
        {
            apiUrl = "https://mscrm16tech.crm8.dynamics.com/api/data/v8.2/";
            clientID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
            secretKey = "asdasdxzxczdsdaasda";
            resource = "https://mscrm16tech.crm8.dynamics.com";
            tokenURL = "https://login.microsoftonline.com:443/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/oauth2/token/";
        }
        #endregion 
 #region Generate Bearer Token
        /// <summary>
        /// Function to generate Bearer token OAuth2.0 using client id and secret
        /// </summary>
        /// <returns>access token</returns>
        public string GetBearerToken()
        {
            oauthToken = null;
            HttpClientHandler clientHandler = new HttpClientHandler();
            clientHandler.Proxy = WebRequest.GetSystemWebProxy();
            clientHandler.Proxy.Credentials = CredentialCache.DefaultCredentials; // or new NetworkCredential("username","password","DOMAIN");
            clientHandler.UseProxy = true;
            httpClient = new HttpClient(clientHandler);
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
            HttpContent requestContent = new FormUrlEncodedContent(new Dictionary<string, string>
            {
                {"grant_type","client_credentials"},
                {"client_id",clientID},
                {"client_secret",secretKey},
                {"resource",resource}
            }
            );
            HttpResponseMessage response = httpClient.PostAsync(tokenURL, requestContent).Result;
            String responseString = response.Content.ReadAsStringAsync().Result;
            using (var ms = new MemoryStream(Encoding.Unicode.GetBytes(responseString)))
            {
                // Deserialization from JSON  
                DataContractJsonSerializer deserializer = new DataContractJsonSerializer(typeof(BearerToken));
                BearerToken token = (BearerToken)deserializer.ReadObject(ms);
                oauthToken = token.access_token;
            }

            return oauthToken;
        }
        #endregion
#region Common Methods
       public string FormatResponse(string responseString) {
            responseString = responseString.Replace("@odata.etag", "odata_etag");
            responseString = responseString.Replace("@OData.Community.Display.V1.FormattedValue", "_OData_Community_Display_V1_FormattedValue");
            responseString = responseString.Replace("@Microsoft.Dynamics.CRM.associatednavigationproperty", "_Microsoft_Dynamics_CRM_associatednavigationproperty");
            responseString = responseString.Replace("@Microsoft.Dynamics.CRM.lookuplogicalname", "_Microsoft_Dynamics_CRM_lookuplogicalname");
            responseString = responseString.Replace("@Microsoft.Dynamics.CRM.lookuplogicalname", "_Microsoft_Dynamics_CRM_lookuplogicalname");
            responseString = responseString.Replace("@odata.context", "odata_context");
            responseString = responseString.Replace("@Microsoft.Dynamics.CRM.totalrecordcount", "Microsoft_Dynamics_CRM_totalrecordcount");
            responseString = responseString.Replace("@Microsoft.Dynamics.CRM.totalrecordcountlimitexceeded", "Microsoft_Dynamics_CRM_totalrecordcountlimitexceeded");
            responseString = responseString.Replace("@Microsoft.Dynamics.CRM.fetchxmlpagingcookie", "Microsoft_Dynamics_CRM_fetchxmlpagingcookie");
            responseString = responseString.Replace("@Microsoft.Dynamics.CRM.morerecords", "Microsoft_Dynamics_CRM_morerecords");
            responseString = responseString.Replace("@odata.nextLink", "odata_nextLink");
            return responseString;
        }
        #endregion

Sample Class for Json Serialization in API Calls-

This class is not needed in you are using Newtonsoft.Json library.

using System.Collections.Generic;
using System.Runtime.Serialization;

namespace ODATA_API
{
    [DataContract]
    public class BearerToken
    {
        [DataMember]
        public string token_type;
        [DataMember]
        public string expires_in;
        [DataMember]
        public string ext_expires_in;
        [DataMember]
        public string expires_on;
        [DataMember]
        public string not_before;
        [DataMember]
        public string resource;
        [DataMember]
        public string access_token;
    }

    [DataContract]
    public class CreateUpdateOpportunity {
        [DataMember]
        public string name;
        [DataMember]
        public string estimatedclosedate;
        [DataMember]
        public string parentaccountid_odata_bind;
        [DataMember]
        public decimal estimatedvalue;
        [DataMember]
        public string transactioncurrencyid_odata_bind;
    }
    [DataContract]
    public class RetrieveOpportunity
    {
        [DataMember]
        public string _odata_context;
        [DataMember]
        public string _odata_etag;
        [DataMember]
        public string estimatedclosedate_OData_Community_Display_V1_FormattedValue;
        [DataMember]
        public string estimatedclosedate;
        [DataMember]
        public string estimatedvalue_OData_Community_Display_V1_FormattedValue;
        [DataMember]
        public string estimatedvalue;
        [DataMember]
        public string estimatedvalue_base_OData_Community_Display_V1_FormattedValue;
        [DataMember]
        public string estimatedvalue_base;
        [DataMember]
        public string name;
        [DataMember]
        public string _parentaccountid_value_OData_Community_Display_V1_FormattedValue;
        [DataMember]
        public string _parentaccountid_value;
        [DataMember]
        public string opportunityid;
        [DataMember]
        public string _transactioncurrencyid_value_OData_Community_Display_V1_FormattedValue;
        [DataMember]
        public string _transactioncurrencyid_value;
    }

    [DataContract]
    public class APIResult
    {
        [DataMember]
        public string odata_context;
        [DataMember]
        public int Microsoft_Dynamics_CRM_totalrecordcount;
        [DataMember]
        public bool Microsoft_Dynamics_CRM_totalrecordcountlimitexceeded;
        [DataMember]
        public string Microsoft_Dynamics_CRM_fetchxmlpagingcookie;
        [DataMember]
        public bool Microsoft_Dynamics_CRM_morerecords;
        [DataMember]
        public string odata_nextLink;
        [DataMember]
        public List<RetrieveOpportunity> value;
    }

}

Calling the Above methods Sample-

static void Main(string[] args)
        {
            Operations ops = new Operations();
            Console.WriteLine("Started!!");
            Console.WriteLine(ops.GetBearerToken());
            
            CreateUpdateOpportunity entity = new CreateUpdateOpportunity();

            entity.transactioncurrencyid_odata_bind = "/transactioncurrencies(c63ba581-6bc6-e811-a96f-000d3af04fb0)";
            entity.estimatedvalue = 1000;
            entity.estimatedclosedate = "2021-01-01";
            entity.name = "Generated From C# Code";
            entity.parentaccountid_odata_bind = "/accounts(3b3b7c71-61d2-ea11-a813-000d3af0205e)";
            //create record
            string recordGuid = ops.CreateAPI("opportunities", entity);
            Console.WriteLine(recordGuid);
           
            //update record
            entity.name = "Generated From CSharp Code";
            Console.WriteLine(ops.UpdateAPI("opportunities", recordGuid, entity));

            //retrieve record
            RetrieveOpportunity retrieveOpportunity = new RetrieveOpportunity();
            retrieveOpportunity =(RetrieveOpportunity)ops.RetrieveSingle("opportunities", recordGuid,retrieveOpportunity);
            Console.WriteLine(retrieveOpportunity.name);

            //delete Record
            Console.WriteLine(ops.DeleteAPI("opportunities", recordGuid));


            Console.Read();
        }

Hope this will help…
Enjoy Ms CRM!!!

Follow on Facebook- FB: MSCRM16Tech

Check highest privileges for entities from different Security roles assigned (including Teams Roles) to User in MS CRM

What is Issue?

User in CRM might be assigned with the different security roles directly or indirectly from Teams security role. Some time it becomes difficult to check privileges on some entity, due to number of different security roles assigned. In this cases, checking different security roles for user becomes tedious and time consuming task. You need to check each security role and each security roles for team in which user is member.

The Reason-

No direct way available to check highest privileges on entities for an user.

Solution-

you can use the tool created by mscrm16Tech as below-
It provides view of all security roles assigned directly or indirectly through Team to user.

When you install and open the application in your system you will get below window which will help you in configuration of MS CRM connection and database connection if needed.

AppScreen1

After configuring the app and clicks on OK, Select the application “Check User Privileges on Entity” in next window  –

App3Screen0

Now you need to load the available active (Enabled) users in connected CRM Organization.
You can select any user from the drop down list and get Privileges for the user.

You also have option to check security roles for entities for some publisher only(custom entities).

The output loaded in grid will show highest privileges on entities available to user cumulatively from all security security roles.

Please use below image for reference-

App3Screen1

Please follow the images to work with the app.

You can find the list of all available security roles assigned directly or indirectly through teams to user.

On click of first option in List -“Get Cumulative privileges for all Roles”  will show the all highest privileges from all the different security roles. and on click of other options in list will show the privileges for that role.

You can save this report as excel file.

You can download MSCRMTechTool Here- MSCRM16Tech Tool

Hope this will help…
Enjoy Ms CRM!!!

Follow on Facebook- FB: MSCRM16Tech

 

Generating Early Bound Classes and simple Entity Classes MS CRM

What is Issue?

While developing the CRM, when we have to go through coding part, we need to create Early-bound classes for some specific entities. But CrmSvcUtil.exe provides us the Early Bound classes for all the entities available in CRM.
Second scenario- we don’t want this much of complicated classes, Only want to add simple Entity classes. But it might become very lengthy and time consuming task to add the fields in class one by one.

The Reason-

Creating Early bound classes for particular entities is not possible using CrmSvcUtil.exe directly and creating simple entity classes is lengthy and time consuming task.

Solution-

To create early bound classe using CrmSvcUtil.exe, please refer blog – Generate Early Bound Classes for selected entities using CrmSvcUtil.exe in MS CRM

OR

you can use the tool created by mscrm16Tech as below-
It provides both the options like creating Simple CRM entity classes or to Use CrmSvcUtil.exe to generate early bound classes for selected entities only.

When you install and open the application in your system you will get below window which will help you in configuration of MS CRM connection and database connection if needed.

AppScreen1

After configuring the app and clicks on OK, Select the application “Create Early-Bound Classes” in next window  –

App2Screen0

Now you can load the available entities in connected CRM Organization. Please use below image for reference-

App2Screen In case you are using windows 10 and trying to generate Early Bound classes using CrmSvcUtil.exe, you need to make sure following feature is enabled on your windows-
>Control Panel Programs and Features – Turn Windows feature On or Off
->.NetFramework 4.8 Advanced Services
-> WCF Services
->HTTP Activation (Please enable if not)

App2Screen1

Please follow the images to work with the app.

You can download app Here- MSCRM16Tech Tool

Hope this will help…
Enjoy Ms CRM!!!

Follow on Facebook- FB: MSCRM16Tech