サンプル: グローバル探索サービス (C#)

この Visual Studio ソリューションは、グローバル探索サービスの使用方法を示す .NET 6.0 プロジェクトを 2 つ含みます。

このサンプルを実行する方法

サンプル ソース コードは、Github の PowerApps-Samples/cds/DiscoveryService/ から入手できます。

サンプルを実行するには:

  1. サンプルをダウンロードまたは複製して、ローカル コピーを用意します。

  2. Visual Studio でソリューション ファイルを開きます。

  3. スタートアップ プロジェクトに設定するプロジェクトを選択します (REST または ServiceClient)。

  4. Program.cs でユーザー資格情報を入力するように Main メソッドを編集します:

       string username = "yourUserName@yourOrgName.onmicrosoft.com";
       string password = "yourPassword";
    
  5. F5 キーを押して、サンプルをビルドして実行します。

このサンプルの概要

RESTServiceClient のサンプルは両方とも同じ内容を実行します:

  1. 自分の資格情報で使用できる環境のリストを取得します。
  2. 環境を一覧表示して 1 つ選択します。
  3. WhoAmI メッセージを実行して、選択した環境のアカウントに対する SystemUser.UserId 値を返します。

このサンプルの動作方法

REST

REST プロジェクトは、HttpClient とともに MSAL ライブラリを使用し、追加のアセンブリを利用することなくグローバル検索 OData エンドポイントを使用します。

ServiceClient

ServiceClient プロジェクトは Dataverse.Client.ServiceClient.DiscoverOnlineOrganizationsAsync Method を利用して、グローバル探索 OData エンドポイントを使用します。

実際の動作

どちらのプロジェクトも、Cloud.cs で定義された同じ Cloud 列挙型を使用し、グローバル探索サービスで使用できる異なるクラウドを示します。

using System.ComponentModel;

namespace PowerApps.Samples
{
    /// <summary>
    /// An enum for the known Clouds
    /// </summary>
    public enum Cloud
    {
        [Description("https://globaldisco.crm.dynamics.com")]
        Commercial,
        [Description("https://globaldisco.crm9.dynamics.com")]
        GCC,
        [Description("https://globaldisco.crm.microsoftdynamics.us")]
        USG,
        [Description("https://globaldisco.crm.appsplatform.us")]
        DOD,
        [Description("https://globaldisco.crm.dynamics.cn")]
        CHINA
    }
}

REST プロジェクト

REST プロジェクトは、Instance.cs が含むこの Instance クラスを使用し、グローバル探索サービスが返す JSON を逆シリアル化します。

namespace PowerApps.Samples
{
    /// <summary>
    /// Environment instance returned from the Discovery service.
    /// </summary>
    class Instance
    {
        public string? ApiUrl { get; set; }
        public Guid? DatacenterId { get; set; }
        public string? DatacenterName { get; set; }
        public string? EnvironmentId { get; set; }
        public string? FriendlyName { get; set; }
        public string? Id { get; set; }
        public bool IsUserSysAdmin { get; set; }
        public DateTime? LastUpdated { get; set; }
        public int OrganizationType { get; set; }
        public string? Purpose { get; set; }
        public string? Region { get; set; }
        public string? SchemaType { get; set; }
        public int? State { get; set; }
        public int? StatusMessage { get; set; }
        public string? TenantId { get; set; }
        public string? TrialExpirationDate { get; set; }
        public string? UniqueName { get; set; }
        public string? UrlName { get; set; }
        public string? Version { get; set; }
        public string? Url { get; set; }
    }
}

REST Program.cs ファイルの内容を以下に示します:

using Microsoft.Identity.Client;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.ComponentModel;
using System.Net;
using System.Net.Http.Headers;
using System.Security;

namespace PowerApps.Samples
{
    class Program
    {
        //These sample application registration values are available for all online instances.
        static readonly string clientId = "51f81489-12ee-4a9e-aaae-a2591f45987d";
        static readonly string redirectUrl = "http://localhost";

        //Establishes the MSAL app to manage caching access tokens
        private static IPublicClientApplication app = PublicClientApplicationBuilder.Create(clientId)
            .WithRedirectUri(redirectUrl)
            .WithAuthority("https://login.microsoftonline.com/organizations")
            .Build();

        static async Task Main()
        {
            string username = "yourUserName@yourOrgName.onmicrosoft.com";
            string password = "yourPassword";

            //Set the Cloud if you want to search other than Commercial.
            Cloud cloud = Cloud.Commercial;

            List<Instance> instances = await GetInstances(username, password, cloud);

            if (instances.Count.Equals(0))
            {
                Console.WriteLine("No valid environments returned for these credentials.");
                return;
            }

            Console.WriteLine("Type the number of the environments you want to use and press Enter.");

            int number = 0;

            //Display instances so they can be selected
            foreach (Instance instance in instances)
            {
                number++;

                //Get the Organization Service URL
                string apiUrl = instance.ApiUrl;
                string friendlyName = instance.FriendlyName;

                Console.WriteLine($"{number} Name: {instance.FriendlyName} URL: {apiUrl}");
            }

            string typedValue = string.Empty;

            try
            {
                //Capture the user's choice
                typedValue = Console.ReadLine();

                int selected = int.Parse(typedValue);

                if (selected <= number)
                {

                    Instance selectedInstance = instances[selected - 1];
                    Console.WriteLine($"You selected '{selectedInstance.FriendlyName}'");

                    //Use the selected instance to get the UserId
                    await ShowUserId(selectedInstance, username, password);

                }
                else
                {
                    throw new ArgumentOutOfRangeException("The selected value is not valid.");
                }
            }
            catch (ArgumentOutOfRangeException aex)
            {
                Console.WriteLine(aex.Message);
            }
            catch (Exception)
            {
                Console.WriteLine("Unable to process value: {0}", typedValue);
            }
        }


        /// <summary>
        /// Gets the instance data for the specified user and cloud.
        /// </summary>
        /// <param name="username">The user's username</param>
        /// <param name="password">The user's password</param>
        /// <param name="cloud">The Cloud enum value that corresponds to the region.</param>
        /// <returns>List of all the instances</returns>
        /// <exception cref="Exception"></exception>
        static async Task<List<Instance>> GetInstances(string username, string password, Cloud cloud)
        {
            try
            {
                //Get the Cloud URL from the Description Attribute applied for the Cloud enum member
                //i.e. Commercial is "https://globaldisco.crm.dynamics.com"
                var type = typeof(Cloud);
                var memInfo = type.GetMember(cloud.ToString());
                var attributes = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);
                string baseUrl = ((DescriptionAttribute)attributes[0]).Description;

                HttpClient client = new();
                string token = await GetToken(baseUrl, username, password);
                client.DefaultRequestHeaders.Authorization =
                    new AuthenticationHeaderValue(scheme: "Bearer", parameter: token);
                client.Timeout = new TimeSpan(0, 2, 0);
                client.BaseAddress = new Uri(baseUrl);

                HttpResponseMessage response = await client
                    .GetAsync("/api/discovery/v2.0/Instances", HttpCompletionOption.ResponseHeadersRead);

                if (response.IsSuccessStatusCode)
                {
                    //Get the response content and parse it.
                    string result = await response.Content.ReadAsStringAsync();
                    JObject body = JObject.Parse(result);
                    JArray values = (JArray)body.GetValue("value");

                    if (!values.HasValues)
                    {
                        return new List<Instance>();
                    }

                    return JsonConvert.DeserializeObject<List<Instance>>(values.ToString());
                }
                else
                {
                    throw new Exception(response.ReasonPhrase);
                }
            }
            catch (Exception)
            {

                throw;
            }
        }

        /// <summary>
        /// Gets an access token using MSAL app.
        /// </summary>
        /// <param name="baseUrl">The Resource to authenticate to</param>
        /// <param name="username">The user's username</param>
        /// <param name="password">The user's password</param>
        /// <returns>An AccessToken</returns>
        /// <exception cref="Exception"></exception>
        internal static async Task<string> GetToken(string baseUrl, string username, string password)
        {
            try
            {
                List<string> scopes = new() { $"{baseUrl}//user_impersonation" };
                var accounts = await app.GetAccountsAsync();

                AuthenticationResult? result;
                if (accounts.Any())
                {
                    result = await app.AcquireTokenSilent(scopes, accounts.FirstOrDefault())
                                      .ExecuteAsync();
                }
                else
                {
                    try
                    {
                        SecureString securePassword = new NetworkCredential("", password).SecurePassword;

                        // Flow not recommended for production
                        result = await app.AcquireTokenByUsernamePassword(scopes.ToArray(), username, securePassword)
                            .ExecuteAsync();
                    }
                    catch (MsalUiRequiredException)
                    {

                        // When MFA is required
                        result = await app.AcquireTokenInteractive(scopes)
                                    .ExecuteAsync();

                    }
                    catch (Exception)
                    {
                        throw;
                    }
                }

                if (result != null && !string.IsNullOrEmpty(result.AccessToken))
                {
                    return result.AccessToken;
                }
                else
                {
                    throw new Exception("Failed to get accesstoken.");
                }
            }
            catch (Exception)
            {

                throw;
            }
        }

        /// <summary>
        /// Shows the user's UserId for selected instance.
        /// </summary>
        /// <param name="instance">A selected instance</param>
        /// <param name="username">The user's username</param>
        /// <param name="password">The user's password</param>
        /// <returns></returns>
        private static async Task ShowUserId(Instance instance, string username, string password)
        {
            try
            {
                HttpClient client = new();
                client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", await GetToken(instance.ApiUrl, username, password));
                client.Timeout = new TimeSpan(0, 2, 0);
                client.BaseAddress = new Uri(instance.ApiUrl);

                HttpResponseMessage response = await client.GetAsync("/api/data/v9.2/WhoAmI", HttpCompletionOption.ResponseHeadersRead);

                if (response.IsSuccessStatusCode)
                {
                    JObject content = JObject.Parse(await response.Content.ReadAsStringAsync());
                    string userId = content["UserId"].ToString();

                    Console.WriteLine($"Your UserId for {instance.FriendlyName} is: {userId}");
                }
                else
                {
                    Console.WriteLine($"Error calling WhoAmI: StatusCode {response.StatusCode} Reason: {response.ReasonPhrase}");
                }
            }
            catch (Exception)
            {

                throw;
            }
        }
    }
}

ServiceClient プロジェクト

ServiceClient プロジェクト Program.cs ファイルの内容を以下に示します:

using Microsoft.Crm.Sdk.Messages;
using Microsoft.PowerPlatform.Dataverse.Client;
using Microsoft.PowerPlatform.Dataverse.Client.Auth;
using Microsoft.PowerPlatform.Dataverse.Client.Model;
using Microsoft.Xrm.Sdk.Discovery;
using System.ComponentModel;

namespace PowerApps.Samples
{
    class Program
    {
        //These sample application registration values are available for all online instances.
        public static string clientId = "51f81489-12ee-4a9e-aaae-a2591f45987d";
        public static string redirectUrl = "http://localhost";


        static async Task Main()
        {
            string username = "yourUserName@yourOrgName.onmicrosoft.com";
            string password = "yourPassword";

            //Set the Cloud if you want to search other than Commercial.
            Cloud cloud = Cloud.Commercial;

            //Get all environments for the selected data center.
            DiscoverOrganizationsResult orgs = await GetAllOrganizations(username, password, cloud);

            if (orgs.OrganizationDetailCollection.Count.Equals(0))
            {
                Console.WriteLine("No valid environments returned for these credentials.");
                return;
            }

            Console.WriteLine("Type the number of the environments you want to use and press Enter.");

            int number = 0;

            //Display organizations so they can be selected
            foreach (OrganizationDetail organization in orgs.OrganizationDetailCollection)
            {
                number++;

                //Get the Organization URL
                string webAppUrl = organization.Endpoints[EndpointType.WebApplication];

                Console.WriteLine($"{number} Name: {organization.FriendlyName} URL: {webAppUrl}");
            }

            string typedValue = string.Empty;
            try
            {
                typedValue = Console.ReadLine();

                int selected = int.Parse(typedValue);

                if (selected <= number)
                {
                    OrganizationDetail org = orgs.OrganizationDetailCollection[selected - 1];
                    Console.WriteLine($"You selected '{org.FriendlyName}'");

                    //Use the selected org with ServiceClient to get the UserId
                    ShowUserId(org, username, password);
                }
                else
                {
                    throw new ArgumentOutOfRangeException("The selected value is not valid.");
                }
            }
            catch (ArgumentOutOfRangeException aex)
            {
                Console.WriteLine(aex.Message);
            }
            catch (Exception)
            {
                Console.WriteLine("Unable to process value: {0}", typedValue);
            }
        }


        /// <summary>
        /// Gets organization data for the specified user and cloud
        /// </summary>
        /// <param name="username">The user's username</param>
        /// <param name="password">The user's password</param>
        /// <param name="cloud">The Cloud enum value that corresponds to the region.</param>
        /// <returns>A List of OrganizationDetail records</returns>
        public static async Task<DiscoverOrganizationsResult> GetAllOrganizations(string userName, string password, Cloud cloud)
        {
            try
            {
                //Get the Cloud URL from the Description Attribute applied for the Cloud member
                var type = typeof(Cloud);
                var memInfo = type.GetMember(cloud.ToString());
                var attributes = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);
                string cloudRegionUrl = ((DescriptionAttribute)attributes[0]).Description;

                // Set up user credentials
                var creds = new System.ServiceModel.Description.ClientCredentials();
                creds.UserName.UserName = userName;
                creds.UserName.Password = password;

                try
                {
                    //Call DiscoverOnlineOrganizationsAsync
                    DiscoverOrganizationsResult organizationsResult = await ServiceClient.DiscoverOnlineOrganizationsAsync(
                           discoveryServiceUri: new Uri($"{cloudRegionUrl}/api/discovery/v2.0/Instances"),
                           clientCredentials: creds,
                           clientId: clientId,
                           redirectUri: new Uri(redirectUrl),
                           isOnPrem: false,
                           authority: "https://login.microsoftonline.com/organizations/",
                           promptBehavior: PromptBehavior.Auto);

                    return organizationsResult;
                }
                catch (Exception)
                {

                    throw;
                }
            }
            catch (Exception)
            {

                throw;
            }
        }

        /// <summary>
        /// Show the user's UserId for the selected organization
        /// </summary>
        /// <param name="org">The selected organization</param>
        /// <param name="username">The user's username</param>
        /// <param name="password">The user's password</param>
        private static void ShowUserId(OrganizationDetail org, string username, string password)
        {
            try
            {
                string conn = $@"AuthType=OAuth;
                         Url={org.Endpoints[EndpointType.OrganizationService]};
                         UserName={username};
                         Password={password};
                         ClientId={clientId};
                         RedirectUri={redirectUrl};
                         Prompt=Auto;
                         RequireNewInstance=True";
                ServiceClient svc = new(conn);

                if (svc.IsReady)
                {
                    try
                    {
                        var response = (WhoAmIResponse)svc.Execute(new WhoAmIRequest());

                        Console.WriteLine($"Your UserId for {org.FriendlyName} is: {response.UserId}");
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.Message);
                    }
                }
                else
                {
                    Console.WriteLine(svc.LastError);
                }
            }
            catch (Exception)
            {

                throw;
            }
        }
    }
}

関連項目

ユーザー組織の検出
サンプル: グローバル検索を利用した Blazor WebAssembly

注意

ドキュメントの言語設定についてお聞かせください。 簡単な調査を行います。 (この調査は英語です)

この調査には約 7 分かかります。 個人データは収集されません (プライバシー ステートメント)。