Showing posts with label Web Api. Show all posts
Showing posts with label Web Api. Show all posts

Jun 17, 2020

Client side Retrieval using Xrm.WebApi - Handling Lookups

One of the previous posts I provided sample codes for different client side operations using Xrm.WebApi. Click this to find it.

Anyway, one of the tricks I couldnt post was how to retrieve a Lookup which is little tricky. Lets check it using with examples.

Here I am trying to retrieve Full Name and Email Address as below.

https://xxxx.crm6.dynamics.com/api/data/v9.1/contacts(73bc115c-4317-ea11-a811-000d3a6aa9c8)?$select=fullname,emailaddress1

It works fine. Check my result.

{"@odata.context":"https://xxxx.crm6.dynamics.com/api/data/v9.1/$metadata#contacts(fullname,emailaddress1)/$entity","@odata.etag":"W/\"77570311\"","fullname":"Aaron Smith","emailaddress1":null,"contactid":"73bc115c-4317-ea11-a811-000d3a6aa9c8"}

Suppose, I need to retrieve a lookup field value. For example, Created By name. What I feel is just adding it as another field. So I am adding to the end of he field list.

https://xxxx.crm6.dynamics.com/api/data/v9.1/contacts(73bc115c-4317-ea11-a811-000d3a6aa9c8)?$select=fullname,emailaddress1,createdby

Hmm.. here comes the confusion. I don't get any values for Created By lookup field. I am sure I spelled correctly, otherwise I would get a error message about unknown field.

{"@odata.context":"https://xxxx.crm6.dynamics.com/api/data/v9.1/$metadata#contacts(fullname,emailaddress1,createdby)/$entity","@odata.etag":"W/\"77570311\"","fullname":"Aaron Smith","emailaddress1":null,"contactid":"73bc115c-4317-ea11-a811-000d3a6aa9c8"}

Well, this is the trick WebApi just doesn't return values from other entities. Lookup value is physically in related entity. So you have to extend the query with Expand keyword.

https://xxxx.crm6.dynamics.com/api/data/v9.1/contacts(73bc115c-4317-ea11-a811-000d3a6aa9c8)?$select=fullname,emailaddress1&$expand=createdby($select=fullname,systemuserid)

Here we get the result like a charm.

{"@odata.context":"https://xxxx.crm6.dynamics.com/api/data/v9.1/$metadata#contacts(fullname,emailaddress1,createdby(fullname,systemuserid))/$entity","@odata.etag":"W/\"77570311\"","fullname":"Aaron Bailey","emailaddress1":null,"contactid":"73bc115c-4317-ea11-a811-000d3a6aa9c8","createdby":{"@odata.etag":"W/\"29915606\"","fullname":"Paul Wicks","systemuserid":"267874e4-645c-e911-a870-000d3a6a065c","ownerid":"267874e4-645c-e911-a870-000d3a6a065c"}}

In summery, make sure you link entity with Expand command to retrieve lookup values.

Simple!

Apr 22, 2020

Passing FetchXml to Xrm.WebApi

Previously we explained how to do basic operations using Xrm.WebApi. Please refer this for relevant post.

What we didn't mention there is passing of FetchXml. Pass a FetchXml to retrive a specific set of records is a very useful operation. Lets check below scenario.

In this fictitious system, we have custom entity called Office that is associated to Account and contains two custom fields called Territory and Type. While we are in Account, suppose we need to select all the associate offices where Territory is Asia-Pacific and Type is Regional. So, this is pretty realistic requirement. If we try to do only with basic operations, it may be complex and would require many server-calls which is not ideal.

This is a scenario that easily achieved with FetchXml. You can easily download the FetchXml through Advanced Find as shown below;


Find below code snippet to understand how we pass this to WebApi;

And this online tool help you with converting your FetchXml to a string that could be used in Java Script: https://www.ashishvishwakarma.com/FetchXmlFormatter/

function onAccountLoad(executionContext) {

    formContext = executionContext.getFormContext();
    var accountId = formContext.data.entity.getId();

    var fetchXML = new String();
    fetchXML += "<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='false'>";
    fetchXML += "    <entity name='su_office'>";
    fetchXML += "        <attribute name='su_officeid' />";
    fetchXML += "        <attribute name='su_name' />";
    fetchXML += "        <attribute name='createdon' />";
    fetchXML += "        <order attribute='su_name' descending='false' />";
    fetchXML += "        <filter type='and'>";
    fetchXML += "           <condition attribute='su_mainaccountid' operator='eq' value='" + accountId + "' />";
    fetchXML += "            <condition attribute='su_territoryid' operator='eq' value='{A25642F7-61EF-E411-80EB-C4346BACE124}' />";
    fetchXML += "            <condition attribute='su_type' operator='eq' value='100000001' />";
    fetchXML += "        </filter>";
    fetchXML += "    </entity>";
    fetchXML += "</fetch>";

    Xrm.WebApi.online.retrieveMultipleRecords("su_office", "?fetchXml=" + encodeURIComponent(fetchXML)).then(
        function success(result) {
            var eligibleOffices = result.entities;
            if (eligibleOffices != null && eligibleOffices.length > 0) {

                for (i = 0; i < eligibleOffices.length; i++) {
                    // Do the logic
                    alert(eligibleOffices[i].su_name);
                }
            }
        },
        function (error) {
        }
    );
}

Hope this is helpful.

Aug 29, 2019

Connect to WebApi using ClientId and Secret

We can now register our Dynamics 365 CE in Azure Active Directory, so that platform can be accessed through different client applications using OAuth. Idea behind is client applications can securely access WebApi using just Client Id and Secret.

Below is a code snippet with C# in .NET Framework 4.6.2 to achieve it.

Click here to see how to register D365 CE in Azure

Only two NuGet packages required. (Please note one of them are not in latest version)


using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Clients.ActiveDirectory;


namespace ConnectWebApiWithClientId
{
    class Program
    {
        static void Main(string[] args)
        {
            Task.WaitAll(Task.Run(async () => await Auth()));
        }

        public static async Task Auth()
        {
            string url = "https://SumeTest.crm.dynamics.com";
            string clientId = "5d83f9es-a577-4s01-ab9b-9513e39k970c";
            string secret = "MvrHJ2T2YK7NabYFRSOfrEqLMME/1OMW8n6sVBA7zxI=";
            string apiVersion = "9.1";

            try
            {
                var userCredential = new ClientCredential(clientId, secret);
                string webApiUrl = $"{url}/api/data/v{apiVersion}/";

                var authParameters = AuthenticationParameters.CreateFromResourceUrlAsync(new Uri(webApiUrl)).Result;

                var authContext = new AuthenticationContext(authParameters.Authority, false);
                var authResult = await authContext.AcquireTokenAsync(url, userCredential);
                var authHeader = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);

                using (var client = new HttpClient())
                {
                    client.BaseAddress = new Uri(webApiUrl);
                    client.DefaultRequestHeaders.Authorization = authHeader;

                    // Use the WhoAmI function
                    var response = client.GetAsync("WhoAmI").Result;
                    if (response.IsSuccessStatusCode)
                    {
                        Console.WriteLine("Authenticated Successfully");
                        Console.WriteLine("Environement : {0}", url);
                        Console.WriteLine();
                    }
                    else
                    {
                        Console.WriteLine("The request failed with a status of '{0}'", response.ReasonPhrase);
                    }
                }
            }
            catch (Exception ex)
            {
                throw ex.InnerException;
            }
            Console.WriteLine("Click Enter to Exit Application");
            Console.ReadLine();
        }
    }
}

Hope this helps.

Jun 8, 2019

Client side operations using Xrm.WebApi

Xrm.WebApi provides properties and methods to use Dynamics 365 Customer Engagement Web API. Here I am presenting some code snippets.

function XrmWebApi_Create()
{
        var data = {  
        "su_name": "Test2 Office",
        "su_ContactId@odata.bind": "/contacts(dba3e5b9-88df-e311-b8e5-6c3be5a8b200)",
        "su_type": 100000001
        }  
 
        Xrm.WebApi.createRecord("su_office", data).then(  
        function success(result) {  
                 alert("Success. Id: " + result.id);  
        },  
        function(error) {  
                alert("Error: " + error.message);  
        }  
        );
}

function XrmWebApi_Update()
{
        var data = {
        "su_name": "Test2 Office NEW",
        "su_type": 100000002
        }

        Xrm.WebApi.updateRecord("su_office", "c1730963-b087-e911-a992-00224800cc7a", data).then(
        function success(result) {
                alert("Updated");
        },
        function (error) {
                console.log(error.message);
        }
        );
}


function XrmWebApi_Retrive()
{
        parent.Xrm.WebApi.retrieveRecord("su_office", "c1730963-b087-e911-a992-00224800cc7a", "?$select=su_name,su_type").then(
        function success(result) {
                alert("Retrieved values: Name:" + result.su_name + " Type:" + result.su_type);
        },
        function (error) {
                alert(error.message);
        }
        );
}

function XrmWebApi_RetriveMultiple()
{
        Xrm.WebApi.retrieveMultipleRecords("su_office", "?$select=su_name&$filter=su_type eq 100000002").then(
        function success(result) {
                for (var i = 0; i < result.entities.length; i++) {
                     alert(result.entities[i].su_name);
                     break; 
                }
        },
        function(error) {  
                        alert("Error: " + error.message);  
        }  
        );
}

function XrmWebApi_Delete()
{
        Xrm.WebApi.deleteRecord("su_office", "fca4b45e-4087-e911-a992-00224800cc7a").then(
        function success(result) {
                alert("Deleted");
        },
        function (error) {
                alert(error.message);
        }
        );
}

Sometimes it can be quite difficult to deal with PartyLists (in Activities). So I am giving a code snippet for that. This example is for creating a Phone Call. Please note how below fields are being set;

a. Regarding Object
b. ParttList

function XrmWebApi_Create()
{
        var contact = "/contacts(dba3e5b9-88df-e311-b8e5-6c3be5a8b200)";
        var user = "/systemusers(63265A2B-F31C-4E0E-951D-13974E3CCB4E)";
        var activityParties = new Array();
        var partyFrom = {"partyid_contact@odata.bind" : contact, "participationtypemask" : 1};
        var partyTo = {"partyid_systemuser@odata.bind" : user, "participationtypemask" : 2};
        activityParties[0] = partyFrom;
        activityParties[1] = partyTo;

        var data = {  
        "subject": "0003",        
        "phonenumber": "789999",
        "description": "test",
        "regardingobjectid_account@odata.bind": "/accounts(aaa19cdd-88df-e311-b8e5-6c3be5a8b200)",
        "phonecall_activity_parties":activityParties
        }  
 
        Xrm.WebApi.createRecord("phonecall", data).then(  
        function success(result) {  
                 alert("Success. Id: " + result.id);  
        },  
        function(error) {  
                alert("Error: " + error.message);  
        }  
        );
}

Please refer this on passing FetchXml to WebApi.

Enjoy!

May 23, 2017

Web API - Retrieve records in JavaScript

It is high time to use WEB API for JavaScript calls, though no one is recommending one particular library for that. In fact, underlying argument has two sides;

1) Using a Library - can be tricky since Microsoft can change WEB API and no guarantee new version of Library will be out
2) Without a Library - Coding sequential lines will not manageable in long run

When take both in to account we decided we will have our own light library (easily updated as necessary).

In Order to do this, we found a cool tool that create JS codes for WEB API. This is simply a Dynamics Solution which needs to be deployed first as any other solution. Then you will see the button that launches the tool;


So we generated record Retrieve operation and made below small library for Retrieval of single record in general manner. (We plan to extend this to other CRUD operations).

We made same method to be used to below scenarios;

1) Retrieve record by Id
2) Retrieve record by Criteria

So this is the simple Library. I hope this is manageable than a heavily customized and heavy one.

function RetrieveEntityById(id, entityName, fieldsArry, filterStr) {
    var RetrieveStr = RetrieveStringBuilder(id, entityName, fieldsArry, filterStr);
    return RetrieveEntityByIdWebApi(RetrieveStr);
}

function IdBracketRemover(id) {
    return (id.indexOf('{')>-1) ? id.substring(1, id.length - 1) : id;
}

function RetrieveStringBuilder(id, entityName, fieldsArry, filterStr) {
    var Str;

    if (id != null)
    { Str = entityName + '(' + IdBracketRemover(id) + ')' + '?$select='; }
    else
    { Str = entityName + '?$select='; }

    for (i = 0; i < fieldsArry.length; i++)
    { Str = Str + fieldsArry[i] + ','; }
    Str = Str.substring(0, Str.length - 1);
    if (filterStr != null)
        Str = Str + '&$filter=' + filterStr;
    return Str;
}

function RetrieveEntityByIdWebApi(RetriveString) {
    var result;
    var req = new XMLHttpRequest();
    req.open("GET", Xrm.Page.context.getClientUrl() + "/api/data/v8.2/" + RetriveString, false);
    req.setRequestHeader("OData-MaxVersion", "4.0");
    req.setRequestHeader("OData-Version", "4.0");
    req.setRequestHeader("Accept", "application/json");
    req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
    req.setRequestHeader("Prefer", "odata.include-annotations=\"*\"");
    req.onreadystatechange = function () {
        if (this.readyState === 4) {
            req.onreadystatechange = null;
            if (this.status === 200) {
                result = JSON.parse(this.response);
            } else {
                Xrm.Utility.alertDialog(this.statusText);
            }
        }
    };
    req.send();
    return result;
}

Please find the ways of calling the method for two different scenarios as mentioned. Check how parameters become null in different scenarios.

// Query basded on Id
// string : "/customeraddresses(1A170E1D-91AE-4965-8631-0FB9270504D7)"
var fieldsArry = ['city', 'country'];
var Id = '{1A170E1D-91AE-4965-8631-0FB9270504D7}';
var AddressEnt = RetrieveEntityById(Id, 'customeraddresses', fieldsArry, null);
if (AddressEnt != null) {
    alert(AddressEnt["city"]);
}


// Query basded on condition (Parent ID and Prefred = true
// string : "/customeraddresses?$filter=_parentid_value eq 19139FC0-DC44-4E88-A793-924F1F90B08F 
//          and  smsmt_ispreferred eq true"
var fieldsArry = ['city', 'country'];
var ParentId = '{19139FC0-DC44-4E88-A793-924F1F90B08F}';
var filterStr = 'new_ispreferred eq true and  _parentid_value eq ' + IdBracketRemover(Id);
var AddressEnt = RetrieveEntityById(null, 'customeraddresses', fieldsArry, filterStr);
if ((AddressEnt != null) && (AddressEnt.value[0] != null)) {
    alert(AddressEnt.value[0].city);
}

For the moment, this seems to be catering the need. If I develop the other operations, I will post them. Anyway, it is the CRM Rest Builder generated this code for me, what I have done is making little changes with parameters to create correct string to be passed.

Special thanks to my colleague Biplab Singha for guiding to this approach.

Post Note;
Noticed assigning a lookup value after retrieving through this could be little tricky. Please find below code snippet of assigning lookup value (coming from entity called new_shareholders and lookup field called new_shname) to primarycontact;


var shEnt = RetrieveEntityById(null, 'new_shareholders', fieldsArry, filterStr);
if ((shEnt != null) && (shEnt.value[0] != null)) {
    var constLookup = new Array();
    constLookup[0] = new Object();
    constLookup[0].id = shEnt.value[0]['_new_shname_value'];
    constLookup[0].name = shEnt.value[0]['_new_shname_value@OData.Community.Display.V1.FormattedValue'];
    constLookup[0].entityType = shEnt.value[0]['_new_shname_value@Microsoft.Dynamics.CRM.lookuplogicalname'];
    Xrm.Page.getAttribute('primarycontact').setValue(constLookup);
}