チュートリアル:Azure Digital Twins SDK を使用したコーディング

Azure Digital Twins を使用する開発者は一般的に、Azure Digital Twins サービスのインスタンスとのやり取りのためにクライアント アプリケーションを作成します。 この開発者向けのチュートリアルでは、.NET 用 Azure Digital Twins SDK (C#) を使用した、Azure Digital Twins サービスに対するプログラミングの概要を説明します。 C# コンソール クライアント アプリの作成手順を最初から順を追って説明します。

  • プロジェクトの設定
  • プロジェクト コードでの作業開始
  • 完全なコード例
  • リソースをクリーンアップする
  • 次のステップ

前提条件

この Azure Digital Twins のチュートリアルでは、セットアップとプロジェクトの作業にコマンド ラインを使用します。 そのため、任意のコード エディターを使用して演習を進めることができます。

開始するために必要なもの:

  • 任意のコード エディター
  • 開発マシンにインストールされた .NET Core 3.1。 「.NET Core 3.1 のダウンロード」から、複数のプラットフォームに対応する .NET Core SDK のこのバージョンをダウンロードできます。

Azure Digital Twins インスタンスを準備する

この記事で Azure Digital Twins を操作するには、まず Azure Digital Twins インスタンスとそれを使用するために必要なアクセス許可が必要です。 Azure Digital Twins インスタンスが既に設定されている場合は、そのインスタンスを使用し、次のセクションに進むことができます。 それ以外の場合は、「インスタンスと認証を設定する」の手順に従います。 手順には、各ステップを正常に完了したことを確認するために役立つ情報が含まれています。

インスタンスの設定後、インスタンスのホスト名を書き留めておきます。 ホスト名は Azure portal で確認できます。

ローカルの Azure 資格情報を設定する

このサンプルでは、ローカル コンピューターで実行された Azure Digital Twins インスタンスに対し、(Azure.Identity ライブラリの) DefaultAzureCredential を使用してユーザーを認証します。 Azure Digital Twins に対してクライアント アプリの認証を行う各種の方法について詳しくは、「アプリ認証コードを作成する」を参照してください。

このサンプルでは、DefaultAzureCredential を使用して、ローカル環境から資格情報が検索されます。たとえば、ローカルの DefaultAzureCredential や Visual Studio または Visual Studio Code での Azure サインインなどです。 このため、サンプルの資格情報を設定するために、これらのメカニズムのいずれかを使用して、Azure にローカルでサインインする必要があります。

Visual Studio または Visual Studio Code を使用してコード サンプルを実行する場合は、Azure Digital Twins インスタンスへのアクセスに使用する Azure 資格情報でそのエディターにサインインしていることを確認してください。 ローカル CLI ウィンドウを使用している場合は、az login コマンドを実行して Azure アカウントにサインインします。 その後、コード サンプルを実行すれば、自動的に認証処理が行われます。

プロジェクトの設定

Azure Digital Twins インスタンスを使用する準備ができたら、クライアント アプリ プロジェクトの設定を開始します。

ご使用のマシンでコンソール ウィンドウを開き、このチュートリアルでの作業を格納する空のプロジェクト ディレクトリを作成します。 ディレクトリに任意の名前を指定します (たとえば、DigitalTwinsCodeTutorial)。

その新しいディレクトリに移動します。

プロジェクト ディレクトリに移動したら、空の .NET コンソール アプリ プロジェクトを作成します。 コマンド ウィンドウで次のコマンドを実行して、コンソール用の最小限の C# プロジェクトを作成できます。

dotnet new console

このコマンドにより、コードの大部分を記述する Program.cs を含む、いくつかのファイルがこのディレクトリ内に作成されます。

コマンド ウィンドウは、チュートリアル全体を通して使用するため、開いたままにしておきます。

次に、Azure Digital Twins を操作するために必要な 2 つの依存関係をプロジェクトに追加します。 1 つ目は .NET 用 Azure Digital Twins SDK パッケージであり、2 つ目は Azure に対する認証に役立つツールを提供します。

dotnet add package Azure.DigitalTwins.Core
dotnet add package Azure.Identity

プロジェクト コードでの作業開始

このセクションでは、Azure Digital Twins とやり取りする新しいアプリ プロジェクトのコードの記述を開始します。 説明するアクションは次のとおりです。

  • サービスに対する認証
  • モデルのアップロード
  • エラーのキャッチ
  • デジタル ツインの作成
  • リレーションシップの作成
  • デジタル ツインのクエリ

チュートリアルの最後にコード全体を示すセクションもあります。 このセクションを参考にして、ご自分のプログラムを確認しながら進めることができます。

最初に、任意のコード エディターで Program.cs ファイルを開きます。 次のような最小限のコードのテンプレートが表示されます。

Screenshot of a snippet of sample code in a code editor.

まず、コードの先頭に using 行をいくつか追加して、必要な依存関係を取得します。

using Azure.DigitalTwins.Core;
using Azure.Identity;

次に、何らかの機能を記述するコードをこのファイルに追加します。

サービスに対する認証

アプリで最初に行う必要があるのは、Azure Digital Twins サービスに対して認証することです。 その後、SDK 関数にアクセスするためのサービス クライアント クラスを作成できます。

認証するには、Azure Digital Twins インスタンスの "ホスト名" が必要です。

Program.cs で、Main メソッド内の "Hello, World!" 出力行の下に、次のコードを貼り付けます。 adtInstanceUrl の値を、Azure Digital Twins インスタンスのホスト名に設定します。

string adtInstanceUrl = "https://<your-Azure-Digital-Twins-instance-hostName>"; 

var credential = new DefaultAzureCredential();
var client = new DigitalTwinsClient(new Uri(adtInstanceUrl), credential);
Console.WriteLine($"Service client created – ready to go");

ファイルを保存します。

コマンド ウィンドウで、次のコマンドを使用してコードを実行します。

dotnet run

このコマンドにより、最初の実行での依存関係が復元されてから、プログラムが実行されます。

  • エラーが発生しなかった場合は、プログラムから "Service client created - ready to go" と出力されます。
  • このプロジェクトにはまだエラー処理がないため、何らかの問題が発生すると、コードによってスローされた例外が表示されます。

注意

現在 DefaultAzureCredential ラッパー クラスに影響を与える既知の問題があり、それが原因で認証中にエラーが発生する可能性があります。 この問題が発生した場合は、次の省略可能なパラメーターを使用して DefaultAzureCredential のインスタンス化を試して解決することができます: new DefaultAzureCredential(new DefaultAzureCredentialOptions { ExcludeSharedTokenCacheCredential = true });

この問題の詳細については、Azure Digital Twins の既知の問題に関する記事を参照してください。

モデルのアップロード

Azure Digital Twins には、組み込みのドメイン ボキャブラリはありません。 Azure Digital Twins で表すことができる環境内の要素の種類は、モデルを使用してご自分で定義します。 モデルは、オブジェクト指向プログラミング言語におけるクラスに似ています。つまりモデルは、デジタル ツインのユーザー定義のテンプレートとなります。デジタル ツインは、そのテンプレートに従ってインスタンス化されることになります。 それらは、JSON に似た Digital Twins Definition Language (DTDL) と呼ばれる言語で記述されます。

Azure Digital Twins ソリューションを作成するにあたり最初にすべきことは、DTDL ファイル内に少なくとも 1 つのモデルを定義することです。

プロジェクトを作成したディレクトリに、SampleModel.json という名前の新しい .json ファイルを作成します。 次のファイル本文を貼り付けます。

{
  "@id": "dtmi:example:SampleModel;1",
  "@type": "Interface",
  "displayName": "SampleModel",
  "contents": [
    {
      "@type": "Relationship",
      "name": "contains"
    },
    {
      "@type": "Property",
      "name": "data",
      "schema": "string"
    }
  ],
  "@context": "dtmi:dtdl:context;3"
}

ヒント

このチュートリアルのために Visual Studio を使用している場合は、新しく作成した JSON ファイルを選択し、プロパティ インスペクターで [出力ディレクトリにコピー] プロパティを [新しい場合はコピーする] または [常にコピーする] に設定することができます。 これにより、チュートリアルの残りの部分で F5 キーを使用してプログラムを実行するときに、Visual Studio の既定のパスで JSON ファイルが検出されるようになります。

ヒント

モデル ドキュメントをチェックし、DTDLParser ライブラリを使用して DTDL が有効であることを確認できます。 このライブラリの使用の詳細については、モデルの解析と検証に関するページを参照してください。

次に、作成したモデルを Azure Digital Twins インスタンスにアップロードするためのコードを Program.cs に追加します。

まず、いくつかの using ステートメントをファイルの先頭に追加します。

using System.Threading.Tasks;
using System.IO;
using System.Collections.Generic;
using Azure;

次に、C# サービス SDK の非同期メソッドを使用する準備として、非同期実行を許可するように Main メソッドのシグネチャを変更します。

static async Task Main(string[] args)
{

Note

SDK では、すべての呼び出しの同期バージョンも提供されているため、async の使用は厳密には必須ではありません。 このチュートリアルでは、async の使用を実践しています。

次に、Azure Digital Twins サービスとやり取りするコードの最初の部分があります。 このコードによって、作成した DTDL ファイルがディスクから読み込まれ、Azure Digital Twins サービス インスタンスにアップロードされます。

前の手順で追加した承認コードの下に、次のコードを貼り付けます。

Console.WriteLine();
Console.WriteLine($"Upload a model");
string dtdl = File.ReadAllText("SampleModel.json");
var models = new List<string> { dtdl };
// Upload the model to the service
await client.CreateModelsAsync(models);

コマンド ウィンドウで、次のコマンドを使用してプログラムを実行します。

dotnet run

このコードに到達したことを示す "Upload a model" が出力に表示されますが、アップロードが成功したかどうかを示す出力はまだありません。

インスタンスに正常にアップロードされたすべてのモデルを示す print ステートメントを追加するには、前のセクションの直後に次のコードを追加します。

// Read a list of models back from the service
AsyncPageable<DigitalTwinsModelData> modelDataList = client.GetModelsAsync();
await foreach (DigitalTwinsModelData md in modelDataList)
{
    Console.WriteLine($"Model: {md.Id}");
}

この新しいコードをテストするためにプログラムをもう一度実行する前に、前回のプログラム実行時にモデルを既にアップロードしたことを思い出してください。 Azure Digital Twins では、同じモデルを 2 回アップロードすることはできません。そのため、同じモデルを再度アップロードしようとすると、プログラムから例外がスローされます。

この情報を考慮して、コマンド ウィンドウでこのコマンドを使用してプログラムを再度実行します。

dotnet run

プログラムで例外がスローされます。 既にアップロード済みのモデルをアップロードしようとすると、サービスから REST API を介して "無効な要求" エラーが返されます。 その結果、今度は Azure Digital Twins クライアント SDK で、成功以外のすべてのサービス リターン コードに対して例外がスローされます。

次のセクションでは、このような例外と、ご自分のコードでそれらを処理する方法について説明します。

エラーのキャッチ

プログラムのクラッシュを防ぐために、モデル アップロードのコードの周囲に例外コードを追加できます。 次のように、既存のクライアント呼び出し await client.CreateModelsAsync(typeList) を try/catch ハンドラー内にラップします。

try
{
    await client.CreateModelsAsync(models);
    Console.WriteLine("Models uploaded to the instance:");
}
catch (RequestFailedException e)
{
    Console.WriteLine($"Upload model error: {e.Status}: {e.Message}");
}

コマンド ウィンドウの dotnet run を使用してプログラムを再度実行します。 ModelIdAlreadyExists というエラー コードを含め、モデルのアップロードの問題に関する詳細が返されます。

この時点以降、このチュートリアルでは、サービス メソッドへのすべての呼び出しを try/catch ハンドラー内にラップします。

デジタル ツインの作成

これで、Azure Digital Twins にモデルがアップロードされたので、このモデル定義を使用してデジタル ツインを作成できます。 デジタル ツインはモデルのインスタンスであり、農場のセンサー、建物内の部屋、車内の照明など、対象となるビジネス環境内のエンティティを表します。 このセクションでは、先ほどアップロードしたモデルに基づいて、いくつかのデジタル ツインを作成します。

Main メソッドの末尾に次のコードを追加し、このモデルに基づいて 3 つのデジタル ツインを作成して初期化します。

var twinData = new BasicDigitalTwin();
twinData.Metadata.ModelId = "dtmi:example:SampleModel;1";
twinData.Contents.Add("data", $"Hello World!");

string prefix = "sampleTwin-";
for (int i = 0; i < 3; i++)
{
    try
    {
        twinData.Id = $"{prefix}{i}";
        await client.CreateOrReplaceDigitalTwinAsync<BasicDigitalTwin>(twinData.Id, twinData);
        Console.WriteLine($"Created twin: {twinData.Id}");
    }
    catch(RequestFailedException e)
    {
        Console.WriteLine($"Create twin error: {e.Status}: {e.Message}");
    }
}

コマンド ウィンドウで、dotnet run を使用してプログラムを実行します。 出力で、sampleTwin-0、sampleTwin-1、および sampleTwin-2 が作成されたことを示す出力メッセージを探します。

次に、プログラムを再度実行します。

最初の実行後にツインが既に存在していても、ツインを 2 回目に作成したときにエラーがスローされないことに注意してください。 モデルの作成とは異なり、ツインの作成では、REST レベルで upsert セマンティクスを使用して PUT 呼び出しを行います。 この種の REST 呼び出しを使用すると、ツインが既に存在する場合、同じツインを再び作成しようとすると、単に元のツインが置き換えられます。 エラーはスローされません。

リレーションシップの作成

次に、作成したツイン間にリレーションシップを作成することで、それらのツインを接続し、ツイン グラフを形成することができます。 ツイン グラフは、ご自分の環境全体を表すために使用されます。

新しい静的メソッドを Program クラスの Main メソッドの下に追加します (これで、コードには 2 つのメソッドがあります)。

public async static Task CreateRelationshipAsync(DigitalTwinsClient client, string srcId, string targetId)
{
    var relationship = new BasicRelationship
    {
        TargetId = targetId,
        Name = "contains"
    };

    try
    {
        string relId = $"{srcId}-contains->{targetId}";
        await client.CreateOrReplaceRelationshipAsync(srcId, relId, relationship);
        Console.WriteLine("Created relationship successfully");
    }
    catch (RequestFailedException e)
    {
        Console.WriteLine($"Create relationship error: {e.Status}: {e.Message}");
    }
}

次に、Main メソッドの末尾に、CreateRelationship コードを呼び出す次のコードを追加して、先ほど記述したコードを使用します。

// Connect the twins with relationships
await CreateRelationshipAsync(client, "sampleTwin-0", "sampleTwin-1");
await CreateRelationshipAsync(client, "sampleTwin-0", "sampleTwin-2");

コマンド ウィンドウで、dotnet run を使用してプログラムを実行します。 出力で、2 つのリレーションシップが正常に作成されたことを示す出力ステートメントを探します。

同じ ID を持つ別のリレーションシップが既に存在する場合、Azure Digital Twins ではリレーションシップを作成できません。そのため、プログラムを複数回実行すると、リレーションシップの作成時に例外が表示されます。 このコードでは、例外をキャッチして無視します。

リレーションシップの一覧表示

次に追加するコードを使用して、作成したリレーションシップの一覧を表示できます。

次の新しいメソッドを Program クラスに追加します。

public async static Task ListRelationshipsAsync(DigitalTwinsClient client, string srcId)
{
    try
    {
        AsyncPageable<BasicRelationship> results = client.GetRelationshipsAsync<BasicRelationship>(srcId);
        Console.WriteLine($"Twin {srcId} is connected to:");
        await foreach (BasicRelationship rel in results)
        {
            Console.WriteLine($" -{rel.Name}->{rel.TargetId}");
        }
    }
    catch (RequestFailedException e)
    {
        Console.WriteLine($"Relationship retrieval error: {e.Status}: {e.Message}");
    }
}

Main メソッドの末尾に、ListRelationships コードを呼び出す次のコードを追加します。

//List the relationships
await ListRelationshipsAsync(client, "sampleTwin-0");

コマンド ウィンドウで、dotnet run を使用してプログラムを実行します。 このような出力ステートメントで、作成したすべてのリレーションシップの一覧が表示されます。

Screenshot of a console showing the program output, which results in a message that lists the twin relationships.

デジタル ツインのクエリ

Azure Digital Twins の主な機能は、環境についての質問に答えるクエリをツイン グラフに対して容易に、かつ効率よく実行できることです。

このチュートリアルで追加するコードの最後のセクションでは、Azure Digital Twins インスタンスに対してクエリを実行します。 この例で使用するクエリでは、インスタンス内のすべてのデジタル ツインが返されます。

この using ステートメントを追加して、JsonSerializer クラスを使用してデジタル ツイン情報を表示できるようにします。

using System.Text.Json;

その後、Main メソッドの末尾に次のコードを追加します。

// Run a query for all twins
string query = "SELECT * FROM digitaltwins";
AsyncPageable<BasicDigitalTwin> queryResult = client.QueryAsync<BasicDigitalTwin>(query);

await foreach (BasicDigitalTwin twin in queryResult)
{
    Console.WriteLine(JsonSerializer.Serialize(twin));
    Console.WriteLine("---------------");
}

コマンド ウィンドウで、dotnet run を使用してプログラムを実行します。 出力には、このインスタンスのすべてのデジタル ツインが表示されます。

Note

グラフのデータを変更してからそれがクエリに反映されるまで、最大 10 秒の待機時間が発生する場合があります。

DigitalTwins API を使用すると変更がすぐに反映されるため、即時の応答が必要な場合は、クエリの代わりに API 要求 (DigitalTwins GetById) または SDK 呼び出し (GetDigitalTwin) を使用してツイン データを取得します。

完全なコード例

チュートリアルのこの時点で、Azure Digital Twins に対して基本的なアクションを実行できる完全なクライアント アプリができました。 参照用に、Program.cs 内のプログラムの完全なコードを次に示します。

using System;
// <Azure_Digital_Twins_dependencies>
using Azure.DigitalTwins.Core;
using Azure.Identity;
// </Azure_Digital_Twins_dependencies>
// <Model_dependencies>
using System.Threading.Tasks;
using System.IO;
using System.Collections.Generic;
using Azure;
// </Model_dependencies>
// <Query_dependencies>
using System.Text.Json;
// </Query_dependencies>

namespace DigitalTwins_Samples
{
    class DigitalTwinsClientAppSample
    {
        // <Async_signature>
        static async Task Main(string[] args)
        {
        // </Async_signature>
            Console.WriteLine("Hello World!");
            // <Authentication_code>
            string adtInstanceUrl = "https://<your-Azure-Digital-Twins-instance-hostName>"; 
            
            var credential = new DefaultAzureCredential();
            var client = new DigitalTwinsClient(new Uri(adtInstanceUrl), credential);
            Console.WriteLine($"Service client created – ready to go");
            // </Authentication_code>

            // <Model_code>
            Console.WriteLine();
            Console.WriteLine("Upload a model");
            string dtdl = File.ReadAllText("SampleModel.json");
            var models = new List<string> { dtdl };

            // Upload the model to the service
            // <Model_try_catch>
            try
            {
                await client.CreateModelsAsync(models);
                Console.WriteLine("Models uploaded to the instance:");
            }
            catch (RequestFailedException e)
            {
                Console.WriteLine($"Upload model error: {e.Status}: {e.Message}");
            }
            // </Model_try_catch>

            // <Print_model>
            // Read a list of models back from the service
            AsyncPageable<DigitalTwinsModelData> modelDataList = client.GetModelsAsync();
            await foreach (DigitalTwinsModelData md in modelDataList)
            {
                Console.WriteLine($"Model: {md.Id}");
            }
            // </Print_model>
            // </Model_code>

            // <Initialize_twins>
            var twinData = new BasicDigitalTwin();
            twinData.Metadata.ModelId = "dtmi:example:SampleModel;1";
            twinData.Contents.Add("data", $"Hello World!");
            
            string prefix = "sampleTwin-";
            for (int i = 0; i < 3; i++)
            {
                try
                {
                    twinData.Id = $"{prefix}{i}";
                    await client.CreateOrReplaceDigitalTwinAsync<BasicDigitalTwin>(twinData.Id, twinData);
                    Console.WriteLine($"Created twin: {twinData.Id}");
                }
                catch(RequestFailedException e)
                {
                    Console.WriteLine($"Create twin error: {e.Status}: {e.Message}");
                }
            }
            // </Initialize_twins>

            // <Use_create_relationship>
            // Connect the twins with relationships
            await CreateRelationshipAsync(client, "sampleTwin-0", "sampleTwin-1");
            await CreateRelationshipAsync(client, "sampleTwin-0", "sampleTwin-2");
            // </Use_create_relationship>

            // <Use_list_relationships>
            //List the relationships
            await ListRelationshipsAsync(client, "sampleTwin-0");
            // </Use_list_relationships>

            // <Query_twins>
            // Run a query for all twins
            string query = "SELECT * FROM digitaltwins";
            AsyncPageable<BasicDigitalTwin> queryResult = client.QueryAsync<BasicDigitalTwin>(query);
            
            await foreach (BasicDigitalTwin twin in queryResult)
            {
                Console.WriteLine(JsonSerializer.Serialize(twin));
                Console.WriteLine("---------------");
            }
            // </Query_twins>
        }

        // <Create_relationship>
        public async static Task CreateRelationshipAsync(DigitalTwinsClient client, string srcId, string targetId)
        {
            var relationship = new BasicRelationship
            {
                TargetId = targetId,
                Name = "contains"
            };
        
            try
            {
                string relId = $"{srcId}-contains->{targetId}";
                await client.CreateOrReplaceRelationshipAsync(srcId, relId, relationship);
                Console.WriteLine("Created relationship successfully");
            }
            catch (RequestFailedException e)
            {
                Console.WriteLine($"Create relationship error: {e.Status}: {e.Message}");
            }
        }
        // </Create_relationship>
        
        // <List_relationships>
        public async static Task ListRelationshipsAsync(DigitalTwinsClient client, string srcId)
        {
            try
            {
                AsyncPageable<BasicRelationship> results = client.GetRelationshipsAsync<BasicRelationship>(srcId);
                Console.WriteLine($"Twin {srcId} is connected to:");
                await foreach (BasicRelationship rel in results)
                {
                    Console.WriteLine($" -{rel.Name}->{rel.TargetId}");
                }
            }
            catch (RequestFailedException e)
            {
                Console.WriteLine($"Relationship retrieval error: {e.Status}: {e.Message}");
            }
        }
        // </List_relationships>
    }
}

リソースをクリーンアップする

このチュートリアルを終えたら、次に行う作業に応じて、削除するリソースを選択できます。

  • 次のチュートリアルに進む場合は、このチュートリアルで使用したインスタンスを次のチュートリアルで再利用できます。 ここでセットアップした Azure Digital Twins リソースは維持したまま、このセクションの残りはスキップしてください。
  • この記事の Azure Digital Twins インスタンスを引き続き使用するが、そのモデル、ツイン、リレーションシップをすべてクリアする場合は、次の az dt job deletion CLI コマンドを実行してください:

    az dt job deletion create -n <name-of-Azure-Digital-Twins-instance> -y
    

    これらの要素の一部のみを削除する場合は、az dt twin relationship deleteaz dt twin deleteaz dt model delete コマンドを使用して、削除する要素のみを選択的に削除できます。

  • このチュートリアルで作成したリソースがすべて不要である場合、この記事で使用した Azure Digital Twins インスタンスとその他すべてのリソースを az group delete CLI コマンドで削除できます。 そのリソース グループとそこに含まれるすべての Azure リソースが削除されます。

    重要

    リソース グループを削除すると、元に戻すことができません。 リソース グループとそこに含まれるすべてのリソースは完全に削除されます。 間違ったリソース グループやリソースをうっかり削除しないようにしてください。

    Azure Cloud Shell またはローカル CLI ウィンドウを開き、次のコマンドを実行すると、リソース グループとそこに含まれる内容がすべて削除されます。

    az group delete --name <your-resource-group>
    

さらに、プロジェクト フォルダーもローカル コンピューターから削除してください。

次のステップ

このチュートリアルでは、.NET コンソール クライアント アプリケーションをゼロから作成しました。 このクライアント アプリのコードを記述して、Azure Digital Twins インスタンスに対する基本的なアクションを実行しました。

次のチュートリアルに進み、このようなサンプル クライアント アプリを使用して実行できることを確認してください。