你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

教程:使用 Azure 数字孪生 SDK 编写代码

使用 Azure 数字孪生的开发人员经常会编写客户端应用程序来与其 Azure 数字孪生服务实例交互。 这篇面向开发人员的教程介绍如何使用适用于 .NET 的 Azure 数字孪生 SDK (C#) 对 Azure 数字孪生服务进行编程。 本教程会逐步引导你从头开始编写 C# 控制台客户端应用。

  • 设置项目
  • 开始使用项目代码
  • 完整的代码示例
  • 清理资源
  • 后续步骤

先决条件

本 Azure 数字孪生教程使用命令行执行设置和项目工作。 因此,你可以使用任何代码编辑器来逐步完成练习。

准备工作:

  • 任何代码编辑器
  • 开发电脑上安装有 .NET Core 3.1。 你可以从下载 .NET Core 3.1 下载适用于多个平台的此版 .NET Core SDK。

准备 Azure 数字孪生实例

若要在本文中使用 Azure 数字孪生,你需要有一个 Azure 数字孪生实例,还需具备使用它所必需的权限。 如果你已设置了一个 Azure 数字孪生实例,则可以使用该实例并跳到下一部分。 如果没有,请按照设置实例和身份验证中的说明操作。 该说明中包含可帮助你验证是否已成功完成每个步骤的信息。

设置实例后,记下实例的主机名。 可以在 Azure 门户中找到该主机名

设置本地 Azure 凭据

当你在本地计算机上运行示例时,此示例使用 DefaultAzureCredential(属于 Azure.Identity 库的一部分)对用户进行 Azure 数字孪生实例验证。 若要详细了解客户端应用可向 Azure 数字孪生进行身份验证的不同方法,请参阅编写应用身份验证代码

使用 DefaultAzureCredential,此示例将在本地环境中搜索凭据,如本地 DefaultAzureCredential 或 Visual Studio/Visual Studio Code 中的 Azure 登录。 因此,应该通过这些机制在本地登录 Azure,以便设置示例的凭据。

如果使用 Visual Studio 或 Visual Studio Code 运行代码示例,请确保使用要用于访问 Azure 数字孪生实例的相同 Azure 凭据登录到该编辑器。 如果使用本地 CLI 窗口,请运行 az login 命令来登录到你的 Azure 帐户。 此后,当你运行代码示例时,应当会自动对你进行身份验证。

设置项目

准备好使用 Azure 数字孪生实例后,请开始设置客户端应用项目。

在计算机上打开一个控制台窗口,创建一个空的项目目录,用于存储你在学习本教程期间所做的工作。 将目录命名为任何你喜欢的名称(例如,DigitalTwinsCodeTutorial)。

导航到新目录。

进入项目目录后,创建一个空的 .NET 控制台应用项目。 在命令窗口中运行以下命令,可以为控制台创建基础 C# 项目:

dotnet new console

此命令将在你的目录中创建多个文件,其中包括一个名为 Program.cs 的文件,你将在其中编写大部分代码。

让命令窗口保持打开状态,因为整个教程都要继续使用该窗口。

接下来,将两个依赖项添加到你的项目,这是与 Azure 数字孪生结合使用所必需的。 第一个是适用于 .NET 的 Azure 数字孪生 SDK 的包,第二个提供工具来帮助向 Azure 进行身份验证。

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

开始使用项目代码

在本部分,你将开始编写新应用项目的代码以使用 Azure 数字孪生。 涵盖的操作包括:

  • 对服务进行身份验证
  • 上传模型
  • 捕获错误
  • 创建数字孪生
  • 创建关系
  • 查询数字孪生

在本教程末尾,另有一节显示了完整的代码。 你可以将本部分用作参考,随时检查自己的程序。

开始前,请在任何代码编辑器中打开 Program.cs 文件。 你将看到如下所示的基础代码模板:

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

首先,在代码的顶部添加一些 using 行,以拉取必需的依赖项。

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

接下来,需要向此文件添加代码以扩充某些功能。

对服务进行身份验证

应用必须执行的第一项操作是对 Azure 数字孪生服务进行身份验证。 接着,你可以创建服务客户端类来访问 SDK 函数。

若要进行身份验证,需要 Azure 数字孪生实例的主机名。

在 Program.cs 中,将以下代码粘贴到 Main 方法中的“Hello, World!”打印行下方。 将 adtInstanceUrl 的值设置为 Azure 数字孪生实例的主机名。

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 数字孪生已知问题

上传模型

Azure 数字孪生没有内部域词汇。 环境中可在 Azure 数字孪生中表示的元素类型由你使用模型定义。 模型类似于面向对象的编程语言中的类;它们为数字孪生提供了日后可遵循并实例化的用户定义的模板。 它们以类似于 JSON 的语言编写,这种语言称为数字孪生定义语言 (DTDL)。

创建 Azure 数字孪生解决方案的第一步是在 DTDL 文件中至少定义一个模型。

在创建项目的目录中,创建名为 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 有效。 有关使用此库的详细信息,请参阅分析和验证模型

接下来,向 Program.cs 添加更多代码,将创建的模型上传到 Azure 数字孪生实例。

首先,在文件顶部添加几个 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 数字孪生服务交互的第一段代码。 此代码将加载你从磁盘创建的 DTDL 文件,然后将其上传到 Azure 数字孪生服务实例。

将以下代码粘贴到前面添加的授权代码下。

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 数字孪生不允许两次上传相同的模型,因此,如果尝试再次上传相同的模型,则程序会引发异常。

考虑到这一点,请在命令窗口中,再次使用以下命令运行程序:

dotnet run

程序应该会引发异常。 当你尝试上传已上传过的模型时,服务会通过 REST API 返回“请求错误”错误。 因此,Azure 数字孪生客户端 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 数字孪生后,可以使用此模型定义创建数字孪生。 数字孪生是模型的实例,表示业务环境中的实体,例如农场中的传感器、大楼中的房间或汽车上的灯。 本部分将在你之前上传的模型的基础上创建几个数字孪生。

将以下代码添加到 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 运行程序。 在输出中,查找指出这两种关系已成功创建的 print 语句。

如果已存在另一个 ID 相同的关系,Azure 数字孪生将不会让你创建关系;因此,如果多次运行程序,你将会看到有关创建关系的异常信息。 此代码会捕获异常并将其忽略。

列出关系

你将添加的下一段代码可让你查看已创建的关系列表。

将以下新方法添加到 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 运行程序。 应该会在 output 语句中看到已创建的所有关系的列表,如下所示:

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

查询数字孪生

Azure 数字孪生的主要功能是能够轻松有效地查询孪生图,以解答有关环境的问题。

要在本教程中添加的最后一段代码会对对 Azure 数字孪生实例运行查询。 本示例中使用的查询会返回此实例中的所有数字孪生。

添加此 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 数字孪生执行基本操作。 下面列出了 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 数字孪生资源,并跳过本部分的其余部分。
  • 如果不再需要在本教程中创建的任何资源,可使用 az group delete CLI 命令从本文中删除 Azure 数字孪生实例及所有其他资源。 这会删除资源组中的所有 Azure 资源及资源组本身。

    重要

    删除资源组的操作不可逆。 资源组以及包含在其中的所有资源将被永久删除。 请确保不要意外删除错误的资源组或资源。

    打开 Azure Cloud Shell 或本地 CLI 窗口并运行以下命令,以删除资源组及其包含的所有内容。

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

你可能还需要从本地计算机中删除项目文件夹。

后续步骤

在本教程中,你从头开始创建了 .NET 控制台客户端应用程序。 你为此客户端应用编写了代码,在 Azure 数字孪生实例上执行基本操作。

请继续学习下一个教程,了解可使用这类示例客户端应用执行的操作: