サンプル: グローバル探索サービス (C#)
この Visual Studio ソリューションは、グローバル探索サービスの使用方法を示す .NET 6.0 プロジェクトを 2 つ含みます。
REST
プロジェクトは OData エンドポイントを使用して表示しますServiceClient
プロジェクトは Dataverse.Client.ServiceClient.DiscoverOnlineOrganizationsAsync Method を使用して表示します
このサンプルを実行する方法
サンプル ソース コードは、Github の PowerApps-Samples/cds/DiscoveryService/ から入手できます。
サンプルを実行するには:
サンプルをダウンロードまたは複製して、ローカル コピーを用意します。
Visual Studio でソリューション ファイルを開きます。
スタートアップ プロジェクトに設定するプロジェクトを選択します (
REST
またはServiceClient
)。Program.cs でユーザー資格情報を入力するように
Main
メソッドを編集します:string username = "yourUserName@yourOrgName.onmicrosoft.com"; string password = "yourPassword";
F5 キーを押して、サンプルをビルドして実行します。
このサンプルの概要
REST
と ServiceClient
のサンプルは両方とも同じ内容を実行します:
- 自分の資格情報で使用できる環境のリストを取得します。
- 環境を一覧表示して 1 つ選択します。
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 分かかります。 個人データは収集されません (プライバシー ステートメント)。