Calling the Dynamics Global Discovery Service

With the launch of Dynamics 365 comes a global version of the CRM Discovery Service that can be used to find instances across regions. This is a Web API only service and with this service you will no longer have to prompt users for their region, as the service can return a list of all instances from all regions at once. For developers building CRM applications this is an important service to be aware of as it can greatly improve the user experience of the the application since users no longer will have to indicate their region before querying for a list of instances.

You can read the documentation for the Global Discovery Service on the Microsoft Developer Network (MSDN) – Discover the URL for your organization using the Web API.

One of the key aspects of using the new Global Discovery Service is getting an authorization token for the Dynamics Web API that allows you to properly access this resource. This example will utilize the new Server-to-Server (S2S) authentication that has been made available in Dynamics 365 as well. To learn more and setup S2S authentication you can view the MSDN documentation, Build web applications using Server-to-Server (S2S) authentication.

Firstly you will need to initiate a OpenID challenge to get an authorization code that we can then use to acquire a token to use in Web API calls to Dynamics. You can do this using the OWIN method described in the in Walkthrough: Multi-tenant server-to-server authentication (note this can be followed even if not creating a multi-tenant application) or you can make this challenge manually without using OWIN. We use the authorization code returned to then request a token to a particular resource, like a Dynamics instance or the discovery service it self.

To call the global discovery service you will need to request a token for the resource https://disco.crm.dynamics.com/ not the global discovery service address https://globaldisco.crm.dynamics.com/. Below is an example that utilizes ADAL (Active Directory Authentication Library) to retrieve a token that will allow that request against the desired Azure AD tenant with client credentials (client id + secret), the authorization code, return URI as well as the resource indicated above.

// setup parameters for authentication context
var tenantId = "<guid-of-azure-ad-instance>";
var aadInstance = "https://login.microsoftonline.com";

// define auth context
var authContext = new Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext($"{aadInstance}{tenantId}");

// setup parameters to get token
var code = "<code-returned-from-OpenID-challenge>";
var credential = new ClientCredential("<ClientId>", "<ClientSecret>");
var uri = "<same-return-URI-from-OpenID-challenge>";
var resource = "https://disco.crm.dynamics.com/";

// request token with parameters
var authResult = await authContext.AcquireTokenByAuthorizationCodeAsync(code, uri, credential, resource);

Once you have your token we will now use it in a call to the global discovery service. The token you received back must be included in the request authorization header in the call to the global discovery service Web API endpoint https://globaldisco.crm.dynamics.com/api/discovery/v1.0/Instances.

// setup http client for GET of instances from global discovery service
var discoEndpoint = "https://globaldisco.crm.dynamics.com/api/discovery/v1.0/Instances";
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
HttpRequestMessage request = new HttpRequestMessage(new HttpMethod("GET"), discoEndpoint);

// send http client request
var response = await httpClient.SendAsync(request);

The first thing we are going to want to do with the response is validate that is was successful.

 // validate status code OK
if (response.StatusCode != HttpStatusCode.OK)
{
    throw new Exception($"Global Discovery Service response status code: {response.StatusCode}");
}

If it was not successful then review the WWW-Authenticate in the response header to check to see if it wants another resource to be specified in the token (it might also be as simple as you missed a trailing slash in the resource). If you do see another resource indicated in the response you will need to re-request a token with the resource specified in the response header then re-make your request to the global discovery service. With the refresh token that is included in the authentication result of the AcquireTokenByAuthorizationCodeAsync you can easily re-request a token for a new resource instead of requiring the authorization code. ADAL will cache all your tokens and you will build up a multi-resource token repository by using this method. Below is an example of using the refresh token to now access the Canadian discovery service after getting the following response with the WWW-Authenticate in the header.

Bearer authorization_uri=https://login.windows.net/common/oauth2/authorize, resource_id=https://disco.crm3.dynamics.com/
var newResource = "https://disco.crm3.dynamics.com/";
var authResultCRM4 = await authContext.AcquireTokenByRefreshTokenAsync(authResult.RefreshToken, credential, newResource);

Note: The tenants I have worked with thus far have all used the North American Discovery Service resource (even tenants started on CRM4 for instance).

Once you have successfully been able to get a response with a HTTP status code of OK using the correct token, the body of the response should be a JSON string that can be converted to an object for you to bind or return to other interfaces. The response JSON content if returning the entire instance object should deserialize into a list of the following DiscoInstanceModel class.

public class DiscoInstanceModel
{
    public Guid Id { get; set; }
    public string UniqueName { get; set; }
    public string UrlName { get; set; }
    public string FriendlyName { get; set; }
    public int State { get; set; }
    public string Version { get; set; }
    public string Url { get; set; }
    public string ApiUrl { get; set; }
    public DateTime LastUpdated { get; set; }
}

You can deserialize the data using Newtonsoft.Json.

// read response content to string
var data = await response.Content.ReadAsStringAsync();

// parse the string to a JObject and deserialize to list of DiscoInstanceModel from the content value
var dataValues = JObject.Parse(data); 
var discoInstances = JsonConvert.DeserializeObject<List<DiscoInstanceModel>>(dataValues["value"].ToString());

The service just like the other Dynamics Web API services provided support for $select, $filter as well as $metadata with the instances entity. So if for instance you wanted to get back only instances of a certain version you could do so with the following filter for all those v8.2.x instances.

https://globaldisco.crm.dynamics.com/api/discovery/v1.0/Instances?$filter=startswith(Version,'8.2.')

A lot of the code provided here is meant as a sample, it will need to be adapted to how you will use this service within your own applications. There are lots of ways of using ADAL to obtain and maintain a token to access the service, so it will require you to implement one that works for individual use cases.

3 thoughts on “Calling the Dynamics Global Discovery Service”

  1. Can you please post an example of just the requests (with headers and body) and the responses returned for using the API with HTTP only and not any of the microsoft tools?

    I have a registered app under my Azure portal account and I am able to complete OAuth (grant permissions to app, get back a code, use the code to get access and refresh tokens) successfully. Now, I am stuck trying to figure out how to get my organization id. It used to be available from logging into the dynamics crm account. But, with the new dynamics 365 I guess we have to go through the discovery service.

    1. By the way, I am trying to get this all to work using just Postman – which is a great tool to debug http requests/responses. Once that works, it is an easy matter to implement the java code to accomplish the same thing.

    2. You will want to make an GET request to https://globaldisco.crm.dynamics.com/api/discovery/v1.0/Instances with the access token in the request authorization header. The access token needs to be for the resource https://disco.crm.dynamics.com/. The response you get back will have a list of instances in the body with the attributes I specified in the .NET object above, one of the attributes is the organization id. If you already know the URL to the instance you want then you can skip the discovery service.

Leave a Reply

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