教學課程:使用 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 入口網站中找到主機名稱

設定本機 Azure 認證

這個範例會使用 DefaultAzureCredential (屬於 Azure.Identity 程式庫),在本機電腦上執行時,使用 Azure Digital Twins 執行個體來驗證使用者。 如需深入了解用戶端應用程式向 Azure Digital Twins 進行驗證的不同方式,請參閱撰寫應用程式驗證碼

使用 DefaultAzureCredential,該範例會搜尋本機環境中的認證,例如在本機 Azure CLI 中或在 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。 第一個是適用於 .NET 的 Azure Digital Twins SDK 的套件,第二個則會提供協助驗證 Azure 的工具。

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

開始使用專案程式碼

在本節中,您要開始撰寫新應用程式專案的程式碼,以使用 Azure Digital Twins。 涵蓋的動作包括:

  • 驗證服務
  • 上傳模型
  • 攔截錯誤
  • 建立數位分身
  • 建立關聯性
  • 查詢數位分身

本教學課程結束時,會另有一節顯示完整的程式碼。 您可以此區段作為參考,隨時檢查您的程式。

開始前,請使用任何程式碼編輯器開啟 Program.cs 檔案。 您會看到如下所示的基礎程式碼範本:

程式碼編輯器中範例程式碼片段的螢幕擷取畫面。

首先,在程式碼頂端新增一些 using 行,以提取必要的相依性。

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

接下來,您要在此檔案新增程式碼,以填滿一些功能。

驗證服務

應用程式必須執行的第一項工作,就是驗證 Azure Digital Twins 服務。 接著,您就可以建立服務用戶端類別以存取 SDK 函式。

為了進行驗證,您需要 Azure Digital Twins 執行個體的主機名稱。

Program.cs 中,將 "Hello, World!" 列印行下方的下列程式碼貼在 Main 方法中。 將 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

第一次執行時,此命令會還原相依性,然後執行程式。

  • 如未發生錯誤,程式會列印「服務用戶端已建立 - 準備就緒」。
  • 此專案中還沒有任何錯誤處理機制,因此如果發生問題,您將會看到程式碼擲回的例外狀況。

注意

目前有已知問題會影響 DefaultAzureCredential 包裝函式類別,這可能會導致在驗證時發生錯誤。 如果遇到此問題,您可以嘗試使用下列選擇性參數將 DefaultAzureCredential 具現化,以解決此問題:new DefaultAzureCredential(new DefaultAzureCredentialOptions { ExcludeSharedTokenCacheCredential = true });

如需關於此問題的詳細資訊,請參閱 Azure Digital Twins 已知問題

上傳模型

Azure Digital Twins 沒有內建的網域詞彙。 您環境中可以在 Azure Digital Twins 表示的元素類型,是由您使用模型定義。 模型類似物件導向程式設計語言中的類別,為數位分身提供日後可遵循並具現化的使用者定義範本。 以類似 JSON,稱為數位分身定義語言 (DTDL) 的語言所撰寫。

建立 Azure Digital Twins 解決方案的第一個步驟,是在 DTDL 檔案中至少定義一個模型。

在您建立專案的目錄中,建立新的 .json 檔案,名為 SampleModel.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 有效。 如需使用此程式庫的詳細資訊,請參閱剖析和驗證模型

接下來,在 Program.cs 中新增更多程式碼,以將您建立的模型上傳至您的 Azure Digital Twins 執行個體。

首先,在檔案頂端新增一些 using 陳述式:

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

接下來,將 Main 方法簽章變更為允許非同步執行,準備使用 C# 服務 SDK 中的非同步方法。

static async Task Main(string[] args)
{

注意

因為 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

「上傳模型」將會列印在輸出中,指出已連線到此程式碼,但還沒有輸出指出上傳是否成功。

若要新增列印陳述式以顯示所有已成功上傳至執行個體的所有模型,請在上一段程式碼後面緊接著新增下列程式碼:

// 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 不會讓您將相同的模型上傳兩次,因此,如果您再次嘗試上傳相同的模型,程式應該會擲回例外狀況。

請記住此資訊,在您的命令視窗中,再次使用此命令執行程式:

dotnet run

程式應該會擲回例外狀況。 當您嘗試上傳已上傳過的模型時,服務會透過 REST API 傳回「不正確的要求」錯誤。 最後,Azure Digital Twins 用戶端 SDK 就會針對不是傳回成功代碼的每項服務,擲回例外狀況。

下一節將討論類似這樣的例外狀況,以及該如何在您的程式碼中處理。

攔截錯誤

為使程式不損毀,您可以在模型上傳程式碼的周圍新增例外狀況程式碼。 在 try/catch 處理常式中包裝現有的用戶端呼叫 await client.CreateModelsAsync(typeList),如下所示:

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 方法的結尾,根據此模型建立並初始化三個數位對應項。

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 已建立的列印訊息。

然後,再次執行程式。

請注意,第二次建立對應項時,即使對應項在第一次執行後已經存在,也不會擲回任何錯誤。 與建立模型不同,就 REST 層級而言,建立對應項是 PUT 呼叫加上 upsert 語法。 使用此類 REST 呼叫表示如果對應項已存在,若嘗試再次建立相同的對應項,將會直接取代原始對應項。 不會擲回錯誤。

建立關聯

接下來,您可以建立已建立對應項之間的關聯性,將其連線到對應項圖形對應項圖形用來表示整個環境。

Main 方法底下,將新的靜態方法新增至 Program 類別 (程式碼現在有兩個方法):

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 執行程式。 在輸出中,尋找指出已成功建立兩個關聯性的列印陳述式。

如果已有另一個具有相同識別碼的關聯性存在,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 執行程式。 您應該會在輸出陳述式中看到已建立的所有關聯性清單,顯示如下:

主控台的螢幕擷取畫面,其中顯示程序輸出,這會產生列出對應項關聯性的訊息。

查詢數位分身

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 執行程式。 您應該會在輸出中看到此執行個體中的所有數位分身。

注意

變更圖表中的資料之後,最多可能會有 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 命令,選擇性地只刪除您想要移除的元素。

  • 如果您不需要在本教學課程中建立的任何資源,可以使用 az group delete CLI 命令刪除本文的 Azure Digital Twins 執行個體和其他所有資源。 這會刪除資源群組中的所有 Azure 資源,以及資源群組本身。

    重要

    刪除資源群組是無法回復的動作。 資源群組和其中包含的所有資源都將永久刪除。 請確定您不會誤刪錯誤的資源群組或資源。

    開啟 Azure Cloud Shell 或本機 CLI 視窗,並執行以下命令,以刪除資源群組及其包含的所有內容。

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

您也可能想要從本機電腦刪除專案資料夾。

下一步

在本教學課程中,您建立了全新的 .NET 主控台用戶端應用程式。 您為此用戶端應用程式撰寫了程式碼,在 Azure Digital Twins 執行個體上執行基本動作。

請繼續前往下一個教學課程,探索您可以使用這類範例用戶端應用程式執行的工作: