批量导入和导出 IoT 中心设备标识Import and export IoT Hub device identities in bulk

每个 IoT 中心都有一个标识注册表,可以使用该注册表在服务中创建每设备资源。Each IoT hub has an identity registry you can use to create per-device resources in the service. 标识注册表还可用于控制对面向设备的终结点的访问。The identity registry also enables you to control access to the device-facing endpoints. 本文介绍如何从标识注册表批量导入和导出设备标识。This article describes how to import and export device identities in bulk to and from an identity registry. 若要查看 C# 的工作示例并了解在将中心克隆到其他区域时如何使用此功能,请参阅如何克隆 IoT 中心To see a working sample in C# and learn how you can use this capability when cloning a hub to a different region, see How to Clone an IoT Hub.

作业的上下文中发生导入和导出操作,可允许对 IoT 中心执行批量服务操作。Import and export operations take place in the context of Jobs that enable you to execute bulk service operations against an IoT hub.

RegistryManager 类包括使用作业框架的 ExportDevicesAsyncImportDevicesAsync 方法。The RegistryManager class includes the ExportDevicesAsync and ImportDevicesAsync methods that use the Job framework. 这些方法可以导出、导入和同步整个 IoT 中心标识注册表。These methods enable you to export, import, and synchronize the entirety of an IoT hub identity registry.

本主题讨论如何使用 RegistryManager 类和作业系统执行设备到 IoT 中心的标识注册表的批量导入,以及从 IoT 中心的标识注册表到设备的批量导出。This topic discusses using the RegistryManager class and Job system to perform bulk imports and exports of devices to and from an IoT hub's identity registry. 还可以使用 Azure IoT 中心设备预配服务实现无需人工干预,零接触实时预配到一个或多个 IoT 中心。You can also use the Azure IoT Hub Device Provisioning Service to enable zero-touch, just-in-time provisioning to one or more IoT hubs without requiring human intervention. 若要了解详细信息,请参阅预配服务文档To learn more, see the provisioning service documentation.

什么是作业?What are jobs?

当操作出现以下情况时,标识注册表操作使用“作业” 系统:Identity registry operations use the Job system when the operation:

  • 相较标准运行时操作,其执行时间可能很长。Has a potentially long execution time compared to standard run-time operations.
  • 向用户返回大量数据。Returns a large amount of data to the user.

操作将以异步方式为该 IoT 中心创建作业,而不是对操作结果进行单一的 API 调用等待或阻塞。Instead of a single API call waiting or blocking on the result of the operation, the operation asynchronously creates a Job for that IoT hub. 然后,操作立即返回 JobProperties 对象。The operation then immediately returns a JobProperties object.

以下 C# 代码段演示如何创建导出作业:The following C# code snippet shows how to create an export job:

// Call an export job on the IoT Hub to retrieve all devices
JobProperties exportJob = await registryManager.ExportDevicesAsync(containerSasUri, false);

备注

若要在 C# 代码中使用 RegistryManager 类,请将 Microsoft.Azure.Devices NuGet 包添加到项目。To use the RegistryManager class in your C# code, add the Microsoft.Azure.Devices NuGet package to your project. RegistryManager 类位于 Microsoft.Azure.Devices 命名空间。The RegistryManager class is in the Microsoft.Azure.Devices namespace.

可使用 RegistryManager 类,查询使用返回的 JobProperties 元数据的作业的状态。You can use the RegistryManager class to query the state of the Job using the returned JobProperties metadata. 若要创建 RegistryManager 类的实例,请使用 CreateFromConnectionString 方法:To create an instance of the RegistryManager class, use the CreateFromConnectionString method:

RegistryManager registryManager = RegistryManager.CreateFromConnectionString("{your IoT Hub connection string}");

若要查找 IoT 中心的连接字符串,请在 Azure 门户中执行以下操作:To find the connection string for your IoT hub, in the Azure portal:

  • 导航到 IoT 中心。Navigate to your IoT hub.
  • 选择“共享访问策略” 。Select Shared access policies.
  • 选择一个策略(考虑到所需的权限)。Select a policy, taking into account the permissions you need.
  • 从屏幕右侧的面板中复制 connectionstring。Copy the connectionstring from the panel on the right-hand side of the screen.

以下 C# 代码段演示如何每隔五秒轮询一次以查看作业是否已完成执行:The following C# code snippet shows how to poll every five seconds to see if the job has finished executing:

// Wait until job is finished
while(true)
{
  exportJob = await registryManager.GetJobAsync(exportJob.JobId);
  if (exportJob.Status == JobStatus.Completed || 
      exportJob.Status == JobStatus.Failed ||
      exportJob.Status == JobStatus.Cancelled)
  {
    // Job has finished executing
    break;
  }

  await Task.Delay(TimeSpan.FromSeconds(5));
}

设备导入/导出作业限制Device import/export job limits

所有 IoT 中心层级一次只允许 1 个活动的设备导入或导出作业。Only 1 active device import or export job is allowed at a time for all IoT Hub tiers. IoT 中心也限制作业操作的速率。IoT Hub also has limits for rate of jobs operations. 若要了解详细信息,请参阅参考 - IoT 中心配额和限制To learn more, see Reference - IoT Hub quotas and throttling.

导出设备Export devices

使用 ExportDevicesAsync 方法,将整个 IoT 中心标识注册表导出到使用共享访问签名 (SAS) 的 Azure 存储 Blob 容器。Use the ExportDevicesAsync method to export the entirety of an IoT hub identity registry to an Azure Storage blob container using a shared access signature (SAS). 有关共享访问签名的详细信息,请参阅使用共享访问签名 (SAS) 授予对 Azure 存储资源的有限访问权限For more information about shared access signatures, see Grant limited access to Azure Storage resources using shared access signatures (SAS).

使用此方法可在所控制的 Blob 容器中创建可靠的设备信息备份。This method enables you to create reliable backups of your device information in a blob container that you control.

ExportDevicesAsync 方法需要两个参数:The ExportDevicesAsync method requires two parameters:

  • 包含 Blob 容器 URI 的 字符串A string that contains a URI of a blob container. 此 URI 必须包含可授予容器写入权限的 SAS 令牌。This URI must contain a SAS token that grants write access to the container. 作业在此容器中创建用于存储序列化导出设备数据的块 Blob。The job creates a block blob in this container to store the serialized export device data. SAS 令牌必须包含这些权限:The SAS token must include these permissions:

    SharedAccessBlobPermissions.Write | SharedAccessBlobPermissions.Read | SharedAccessBlobPermissions.Delete
    
  • 指示你是否要在导出数据中排除身份验证密钥的 布尔值A boolean that indicates if you want to exclude authentication keys from your export data. 如果为 false,则身份验证密钥包含在导出输出中。If false, authentication keys are included in export output. 否则,密钥导出为 nullOtherwise, keys are exported as null.

下面的 C# 代码段演示了如何启动在导出数据中包含设备身份验证密钥的导出作业,并对完成情况进行轮询:The following C# code snippet shows how to initiate an export job that includes device authentication keys in the export data and then poll for completion:

// Call an export job on the IoT Hub to retrieve all devices
JobProperties exportJob = await registryManager.ExportDevicesAsync(containerSasUri, false);

// Wait until job is finished
while(true)
{
    exportJob = await registryManager.GetJobAsync(exportJob.JobId);
    if (exportJob.Status == JobStatus.Completed || 
        exportJob.Status == JobStatus.Failed ||
        exportJob.Status == JobStatus.Cancelled)
    {
    // Job has finished executing
    break;
    }

    await Task.Delay(TimeSpan.FromSeconds(5));
}

该作业将其输出以名为 devices.txt 的块 Blob 的形式存储在提供的 Blob 容器中。The job stores its output in the provided blob container as a block blob with the name devices.txt. 输出数据包含 JSON 序列化设备数据,每行代表一个设备。The output data consists of JSON serialized device data, with one device per line.

以下示例显示输出数据:The following example shows the output data:

{"id":"Device1","eTag":"MA==","status":"enabled","authentication":{"symmetricKey":{"primaryKey":"abc=","secondaryKey":"def="}}}
{"id":"Device2","eTag":"MA==","status":"enabled","authentication":{"symmetricKey":{"primaryKey":"abc=","secondaryKey":"def="}}}
{"id":"Device3","eTag":"MA==","status":"disabled","authentication":{"symmetricKey":{"primaryKey":"abc=","secondaryKey":"def="}}}
{"id":"Device4","eTag":"MA==","status":"disabled","authentication":{"symmetricKey":{"primaryKey":"abc=","secondaryKey":"def="}}}
{"id":"Device5","eTag":"MA==","status":"enabled","authentication":{"symmetricKey":{"primaryKey":"abc=","secondaryKey":"def="}}}

如果设备具有孪生数据,则孪生数据也将随设备数据一起导出。 以下示例显示了此格式。 从“twinETag”行开始直至结尾的所有数据都是孪生数据。All data from the "twinETag" line until the end is twin data.

{
   "id":"export-6d84f075-0",
   "eTag":"MQ==",
   "status":"enabled",
   "statusReason":"firstUpdate",
   "authentication":null,
   "twinETag":"AAAAAAAAAAI=",
   "tags":{
      "Location":"LivingRoom"
   },
   "properties":{
      "desired":{
         "Thermostat":{
            "Temperature":75.1,
            "Unit":"F"
         },
         "$metadata":{
            "$lastUpdated":"2017-03-09T18:30:52.3167248Z",
            "$lastUpdatedVersion":2,
            "Thermostat":{
               "$lastUpdated":"2017-03-09T18:30:52.3167248Z",
               "$lastUpdatedVersion":2,
               "Temperature":{
                  "$lastUpdated":"2017-03-09T18:30:52.3167248Z",
                  "$lastUpdatedVersion":2
               },
               "Unit":{
                  "$lastUpdated":"2017-03-09T18:30:52.3167248Z",
                  "$lastUpdatedVersion":2
               }
            }
         },
         "$version":2
      },
      "reported":{
         "$metadata":{
            "$lastUpdated":"2017-03-09T18:30:51.1309437Z"
         },
         "$version":1
      }
   }
}

如果需要在代码中访问此数据,可以使用 ExportImportDevice 类轻松反序列化此数据。If you need access to this data in code, you can easily deserialize this data using the ExportImportDevice class. 以下 C# 代码段演示如何读取前面导出到块 Blob 的设备信息:The following C# code snippet shows how to read device information that was previously exported to a block blob:

var exportedDevices = new List<ExportImportDevice>();

using (var streamReader = new StreamReader(await blob.OpenReadAsync(AccessCondition.GenerateIfExistsCondition(), null, null), Encoding.UTF8))
{
  while (streamReader.Peek() != -1)
  {
    string line = await streamReader.ReadLineAsync();
    var device = JsonConvert.DeserializeObject<ExportImportDevice>(line);
    exportedDevices.Add(device);
  }
}

导入设备Import devices

通过 RegistryManager 类中的 ImportDevicesAsync 方法,可以在 IoT 中心标识注册表中执行批量导入和同步操作。The ImportDevicesAsync method in the RegistryManager class enables you to perform bulk import and synchronization operations in an IoT hub identity registry. 如同 ExportDevicesAsync 方法,ImportDevicesAsync 方法也使用作业框架。Like the ExportDevicesAsync method, the ImportDevicesAsync method uses the Job framework.

请小心使用 ImportDevicesAsync 方法,因为除了在标识注册表中预配新设备以外,它还可以更新和删除现有设备。Take care using the ImportDevicesAsync method because in addition to provisioning new devices in your identity registry, it can also update and delete existing devices.

警告

导入操作不可撤消。An import operation cannot be undone. 请始终先使用 ExportDevicesAsync 方法将现有数据备份到其他 Blob 容器,再对标识注册表进行批量更改。Always back up your existing data using the ExportDevicesAsync method to another blob container before you make bulk changes to your identity registry.

ImportDevicesAsync 方法采用两个参数:The ImportDevicesAsync method takes two parameters:

  • 一个字符串,其中包含作为作业的输入使用的 Azure 存储 Blob 容器的 URI。A string that contains a URI of an Azure Storage blob container to use as input to the job. 此 URI 必须包含可授予容器读取权限的 SAS 令牌。This URI must contain a SAS token that grants read access to the container. 此容器必须包含名为 devices.txt 的 Blob,其中包含要导入标识注册表的序列化设备数据。This container must contain a blob with the name devices.txt that contains the serialized device data to import into your identity registry. 导入数据包含的设备信息必须采用 ExportImportDevice 作业在创建 devices.txt Blob 时使用的同一种 JSON 格式。The import data must contain device information in the same JSON format that the ExportImportDevice job uses when it creates a devices.txt blob. SAS 令牌必须包含这些权限:The SAS token must include these permissions:

    SharedAccessBlobPermissions.Read
    
  • 一个字符串,其中包含用作作业输出Azure 存储 Blob 容器的 URI。A string that contains a URI of an Azure storage blob container to use as output from the job. 作业在此容器中创建块 Blob,用于存储已完成的导入 作业中的任何错误信息。The job creates a block blob in this container to store any error information from the completed import Job. SAS 令牌必须包含这些权限:The SAS token must include these permissions:

    SharedAccessBlobPermissions.Write | SharedAccessBlobPermissions.Read | SharedAccessBlobPermissions.Delete
    

备注

这两个参数可以指向同一 Blob 容器。The two parameters can point to the same blob container. 单独的参数只会让你更好地控制数据,因为输出容器需要其他权限。The separate parameters simply enable more control over your data as the output container requires additional permissions.

以下 C# 代码片段演示如何启动导入作业:The following C# code snippet shows how to initiate an import job:

JobProperties importJob = await registryManager.ImportDevicesAsync(containerSasUri, containerSasUri);

还可以使用此方法导入设备孪生的数据。This method can also be used to import the data for the device twin. 数据输入的格式与 ExportDevicesAsync 部分中显示的格式相同。The format for the data input is the same as the format shown in the ExportDevicesAsync section. 这样,可以重新导入已导出的数据。In this way, you can reimport the exported data. $metadata 是可选项。The $metadata is optional.

导入行为Import behavior

可以使用 ImportDevicesAsync 方法在标识注册表中执行以下批量操作:You can use the ImportDevicesAsync method to perform the following bulk operations in your identity registry:

  • 批量注册新设备Bulk registration of new devices
  • 批量删除现有设备Bulk deletions of existing devices
  • 批量更改状态(启用或禁用设备)Bulk status changes (enable or disable devices)
  • 批量分配新设备身份验证密钥Bulk assignment of new device authentication keys
  • 批量自动重新生成设备身份验证密钥Bulk auto-regeneration of device authentication keys
  • 批量更新孪生数据Bulk update of twin data

可以在单个 ImportDevicesAsync 调用中执行上述操作的任意组合。You can perform any combination of the preceding operations within a single ImportDevicesAsync call. 例如,可以同时注册新设备并删除或更新现有设备。For example, you can register new devices and delete or update existing devices at the same time. ExportDevicesAsync 方法一起使用时,可以将一个 IoT 中心内的所有设备全部迁移到另一个 IoT 中心。When used along with the ExportDevicesAsync method, you can completely migrate all your devices from one IoT hub to another.

如果导入文件包括孪生元数据,则此元数据将覆盖现有的孪生元数据。If the import file includes twin metadata, then this metadata overwrites the existing twin metadata. 如果导入文件未包括孪生元数据,则只会使用当前时间更新 lastUpdateTime 元数据。If the import file does not include twin metadata, then only the lastUpdateTime metadata is updated using the current time.

可以在每个设备的导入序列化数据中使用可选 importMode 属性来控制每个设备的导入过程。Use the optional importMode property in the import serialization data for each device to control the import process per-device. importMode 属性具有以下选项:The importMode property has the following options:

importModeimportMode 说明Description
createOrUpdatecreateOrUpdate 如果不存在具有指定 ID 的设备,则表示是新注册的设备。If a device does not exist with the specified ID, it is newly registered.
如果设备已存在,则以所提供的输入数据覆盖现有信息,而不管 ETag 值为何。If the device already exists, existing information is overwritten with the provided input data without regard to the ETag value.
用户可以选择在指定设备数据的同时指定孪生数据。The user can optionally specify twin data along with the device data. 如果指定了孪生的 ETag,它的处理独立于设备 ETag 的处理。The twin's etag, if specified, is processed independently from the device's etag. 如果与现有孪生的 ETag 不匹配,则会将错误写入日志文件。If there is a mismatch with the existing twin's etag, an error is written to the log file.
createcreate 如果不存在具有指定 ID 的设备,则表示是新注册的设备。If a device does not exist with the specified ID, it is newly registered.
如果设备已存在,则在日志文件中写入错误。If the device already exists, an error is written to the log file.
用户可以选择在指定设备数据的同时指定孪生数据。The user can optionally specify twin data along with the device data. 如果指定了孪生的 ETag,它的处理独立于设备 ETag 的处理。The twin's etag, if specified, is processed independently from the device's etag. 如果与现有孪生的 ETag 不匹配,则会将错误写入日志文件。If there is a mismatch with the existing twin's etag, an error is written to the log file.
updateupdate 如果具有指定 ID 的设备已存在,则使用提供的输入数据覆盖现有信息,与 ETag 值无关 。If a device already exists with the specified ID, existing information is overwritten with the provided input data without regard to the ETag value.
如果设备不存在,则在日志文件中写入错误。If the device does not exist, an error is written to the log file.
updateIfMatchETagupdateIfMatchETag 如果具有指定 ID 的设备已存在,则只有当 ETag 下匹配时,才使用提供的输入数据覆盖现有信息 。If a device already exists with the specified ID, existing information is overwritten with the provided input data only if there is an ETag match.
如果设备不存在,则在日志文件中写入错误。If the device does not exist, an error is written to the log file.
如果 ETag 不匹配,则在日志文件中写入错误。If there is an ETag mismatch, an error is written to the log file.
createOrUpdateIfMatchETagcreateOrUpdateIfMatchETag 如果不存在具有指定 ID 的设备,则表示是新注册的设备。If a device does not exist with the specified ID, it is newly registered.
如果设备已存在,则仅当 ETag 匹配时,才以提供的输入数据覆盖现有信息。If the device already exists, existing information is overwritten with the provided input data only if there is an ETag match.
如果 ETag 不匹配,则在日志文件中写入错误。If there is an ETag mismatch, an error is written to the log file.
用户可以选择在指定设备数据的同时指定孪生数据。The user can optionally specify twin data along with the device data. 如果指定了孪生的 ETag,它的处理独立于设备 ETag 的处理。The twin's etag, if specified, is processed independently from the device's etag. 如果与现有孪生的 ETag 不匹配,则会将错误写入日志文件。If there is a mismatch with the existing twin's etag, an error is written to the log file.
deletedelete 如果具有指定 ID 的设备已存在,则删除该设备,与 ETag 值无关 。If a device already exists with the specified ID, it is deleted without regard to the ETag value.
如果设备不存在,则在日志文件中写入错误。If the device does not exist, an error is written to the log file.
deleteIfMatchETagdeleteIfMatchETag 如果具有指定 ID 的设备已存在,则只有当 ETag 匹配时才删除该设备 。If a device already exists with the specified ID, it is deleted only if there is an ETag match. 如果设备不存在,则在日志文件中写入错误。If the device does not exist, an error is written to the log file.
如果 ETag 不匹配,则将错误写入日志文件。If there is an ETag mismatch, an error is written to the log file.

备注

如果序列化数据未显式定义设备的 importMode 标志,则该标志在导入操作过程中默认为 createOrUpdateIf the serialization data does not explicitly define an importMode flag for a device, it defaults to createOrUpdate during the import operation.

导入设备示例 – 批量预配设备Import devices example – bulk device provisioning

以下 C# 代码示例说明了如何生成多个具有下述功能的设备标识:The following C# code sample illustrates how to generate multiple device identities that:

  • 包括身份验证密钥。Include authentication keys.
  • 将该设备信息写入块 blob。Write that device information to a block blob.
  • 将设备导入标识注册表。Import the devices into the identity registry.
// Provision 1,000 more devices
var serializedDevices = new List<string>();

for (var i = 0; i < 1000; i++)
{
  // Create a new ExportImportDevice
  // CryptoKeyGenerator is in the Microsoft.Azure.Devices.Common namespace
  var deviceToAdd = new ExportImportDevice()
  {
    Id = Guid.NewGuid().ToString(),
    Status = DeviceStatus.Enabled,
    Authentication = new AuthenticationMechanism()
    {
      SymmetricKey = new SymmetricKey()
      {
        PrimaryKey = CryptoKeyGenerator.GenerateKey(32),
        SecondaryKey = CryptoKeyGenerator.GenerateKey(32)
      }
    },
    ImportMode = ImportMode.Create
  };

  // Add device to the list
  serializedDevices.Add(JsonConvert.SerializeObject(deviceToAdd));
}

// Write the list to the blob
var sb = new StringBuilder();
serializedDevices.ForEach(serializedDevice => sb.AppendLine(serializedDevice));
await blob.DeleteIfExistsAsync();

using (CloudBlobStream stream = await blob.OpenWriteAsync())
{
  byte[] bytes = Encoding.UTF8.GetBytes(sb.ToString());
  for (var i = 0; i < bytes.Length; i += 500)
  {
    int length = Math.Min(bytes.Length - i, 500);
    await stream.WriteAsync(bytes, i, length);
  }
}

// Call import using the blob to add new devices
// Log information related to the job is written to the same container
// This normally takes 1 minute per 100 devices
JobProperties importJob = await registryManager.ImportDevicesAsync(containerSasUri, containerSasUri);

// Wait until job is finished
while(true)
{
  importJob = await registryManager.GetJobAsync(importJob.JobId);
  if (importJob.Status == JobStatus.Completed || 
      importJob.Status == JobStatus.Failed ||
      importJob.Status == JobStatus.Cancelled)
  {
    // Job has finished executing
    break;
  }

  await Task.Delay(TimeSpan.FromSeconds(5));
}

导入设备示例 – 批量删除Import devices example – bulk deletion

以下代码示例演示如何删除使用前面代码示例添加的设备:The following code sample shows you how to delete the devices you added using the previous code sample:

// Step 1: Update each device's ImportMode to be Delete
sb = new StringBuilder();
serializedDevices.ForEach(serializedDevice =>
{
  // Deserialize back to an ExportImportDevice
  var device = JsonConvert.DeserializeObject<ExportImportDevice>(serializedDevice);

  // Update property
  device.ImportMode = ImportMode.Delete;

  // Re-serialize
  sb.AppendLine(JsonConvert.SerializeObject(device));
});

// Step 2: Write the new import data back to the block blob
await blob.DeleteIfExistsAsync();
using (CloudBlobStream stream = await blob.OpenWriteAsync())
{
  byte[] bytes = Encoding.UTF8.GetBytes(sb.ToString());
  for (var i = 0; i < bytes.Length; i += 500)
  {
    int length = Math.Min(bytes.Length - i, 500);
    await stream.WriteAsync(bytes, i, length);
  }
}

// Step 3: Call import using the same blob to delete all devices
importJob = await registryManager.ImportDevicesAsync(containerSasUri, containerSasUri);

// Wait until job is finished
while(true)
{
  importJob = await registryManager.GetJobAsync(importJob.JobId);
  if (importJob.Status == JobStatus.Completed || 
      importJob.Status == JobStatus.Failed ||
      importJob.Status == JobStatus.Cancelled)
  {
    // Job has finished executing
    break;
  }

  await Task.Delay(TimeSpan.FromSeconds(5));
}

获取容器 SAS URIGet the container SAS URI

下面的代码示例演示如何使用 Blob 容器的读取、写入和删除权限生成 SAS URIThe following code sample shows you how to generate a SAS URI with read, write, and delete permissions for a blob container:

static string GetContainerSasUri(CloudBlobContainer container)
{
  // Set the expiry time and permissions for the container.
  // In this case no start time is specified, so the
  // shared access signature becomes valid immediately.
  var sasConstraints = new SharedAccessBlobPolicy();
  sasConstraints.SharedAccessExpiryTime = DateTime.UtcNow.AddHours(24);
  sasConstraints.Permissions = 
    SharedAccessBlobPermissions.Write | 
    SharedAccessBlobPermissions.Read | 
    SharedAccessBlobPermissions.Delete;

  // Generate the shared access signature on the container,
  // setting the constraints directly on the signature.
  string sasContainerToken = container.GetSharedAccessSignature(sasConstraints);

  // Return the URI string for the container,
  // including the SAS token.
  return container.Uri + sasContainerToken;
}

后续步骤Next steps

在本文中,已学习如何对 IoT 中心内的标识注册表执行批量操作。In this article, you learned how to perform bulk operations against the identity registry in an IoT hub. 其中许多操作(包括如何将设备从一个中心移到另一个中心)在“如何克隆 IoT 中心”的“管理注册到 IoT 中心的设备”部分中使用。Many of these operations, including how to move devices from one hub to another, are used in the Managing devices registered to the IoT hub section of How to Clone an IoT Hub.

克隆文章有一个与之关联的工作示例,位于本页的 IoT C# 示例中:适用于 C# 的 Azure IoT 示例,项目为 ImportExportDevicesSample。The cloning article has a working sample associated with it, which is located in the IoT C# samples on this page: Azure IoT Samples for C#, with the project being ImportExportDevicesSample. 可以下载该示例并进行试用;如何克隆 IoT 中心一文中提供了相关说明。You can download the sample and try it out; there are instructions in the How to Clone an IoT Hub article.

若要详细了解如何管理 Azure IoT 中心,请查看以下文章:To learn more about managing Azure IoT Hub, check out the following articles:

若要进一步探索 IoT 中心的功能,请参阅:To further explore the capabilities of IoT Hub, see: