Share via


DTDL 파서 라이브러리를 사용하여 모델 구문 분석 및 유효성 검사

이 문서에서는 .NET 파서 라이브러리를 사용하여 Azure Digital Twins 모델을 구문 분석하고 유효성을 검사하는 방법을 설명합니다.

Azure Digital Twins의 모델은 JSON-LD 기반 DTDL(디지털 트윈 정의 언어)을 사용하여 정의됩니다.

모델을 만든 후에는 Azure Digital Twins 인스턴스에 업로드하기 전에 오프라인으로 모델의 유효성을 검사하는 것이 좋습니다.

모델의 유효성을 검사하는 데 도움이 되도록 NuGet: DTDLParser에 .NET 클라이언트 측 DTDL 구문 분석 라이브러리가 제공됩니다. C# 코드에서 직접 파서 라이브러리를 사용할 수 있습니다. GitHub의 DTDLParserResolveSample에서 파서의 샘플 사용을 볼 수도 있습니다.

.NET 파서 라이브러리 정보

DTDLParser 라이브러리는 DTDL 정의에 대한 모델 액세스를 제공하며, 기본적으로 DTDL에 대한 C# 리플렉션과 동등한 역할을 합니다. 이 라이브러리는 특히 시각적 개체 또는 텍스트 편집기에서 DTDL 유효성 검사를 위해 Azure Digital Twins SDK와 별개로 사용할 수 있습니다. 모델 정의 파일을 서비스에 업로드하기 전에 해당 파일이 유효한지 확인하는 데 유용합니다.

파서 라이브러리 사용을 위해 DTDL 문서 집합을 제공합니다. 일반적으로 서비스에서 이러한 모델 문서를 검색하지만 클라이언트에서 먼저 서비스에 업로드해야 하는 경우 로컬에서 검색이 가능하도록 할 수 있습니다.

다음은 파서 사용에 대한 일반적인 워크플로입니다.

  1. 서비스에서 일부 또는 모든 DTDL 문서를 검색합니다.
  2. 반환된 메모리 내 DTDL 문서를 파서에 전달합니다.
  3. 파서에서 전달된 문서 집합의 유효성을 검사하고, 자세한 오류 정보를 반환합니다. 이 기능은 편집기 시나리오에서 유용합니다.
  4. 파서 API를 사용하여 문서 집합에 포함된 모델을 계속 분석할 수 있습니다.

파서 기능은 다음과 같습니다.

  • 모든 구현된 모델 인터페이스(인터페이스의 extends 섹션의 콘텐츠)를 가져옵니다.
  • 모델에 선언된 모든 속성, 원격 분석, 명령, 구성 요소 및 관계를 가져옵니다. 이 명령은 또한 이러한 정의에 포함된 메타데이터를 모두 가져오고, 상속(extends 섹션)을 고려합니다.
  • 모든 복합 모델 정의를 가져옵니다.
  • 모델을 다른 모델에서 할당할 수 있는지 여부를 확인합니다.

참고 항목

IoT 플러그 앤 플레이 디바이스는 작은 구문 변형을 사용하여 기능을 설명합니다. 이 구문 변형은 Azure Digital Twins에서 사용되는 DTDL의 의미 체계적으로 호환되는 하위 집합입니다. 파서 라이브러리를 사용하는 경우 디지털 쌍에 대한 DTDL을 만드는 데 사용된 구문 변형을 알 필요가 없습니다. 파서는 기본적으로 항상 IoT 플러그 앤 플레이와 Azure Digital Twins 구문 모두에 대해 동일한 모델을 반환합니다.

파서 라이브러리를 사용하는 코드

애플리케이션에서 모델의 유효성을 검사하거나 동적인 모델 기반 UI, 대시보드 및 보고서를 생성하는 등의 작업을 수행하기 위해 파서 라이브러리를 직접 사용할 수 있습니다.

아래의 파서 코드 예제를 지원하려면 Azure Digital Twins 인스턴스에 정의된 몇 가지 모델을 고려하세요.

[
    {
      "@context": "dtmi:dtdl:context;3",
      "@id": "dtmi:com:contoso:coffeeMaker;1",
      "@type": "Interface",
      "contents": [
        {
          "@type": "Component",
          "name": "coffeeMaker",
          "schema": "dtmi:com:contoso:coffeeMakerInterface;1"
        }
      ]
    },
    {
      "@context": "dtmi:dtdl:context;3",
      "@id": "dtmi:com:contoso:coffeeMakerInterface;1",
      "@type": "Interface",
      "contents": [
        {
          "@type": "Property",
          "name": "waterTemp",
          "schema": "double"
        }
      ]
    },
    {
      "@context": "dtmi:dtdl:context;3",
      "@id": "dtmi:com:contoso:coffeeBar;1",
      "@type": "Interface",
      "contents": [
        {
          "@type": "Relationship",
          "name": "foo",
          "target": "dtmi:com:contoso:coffeeMaker;1"
        },
        {
          "@type": "Property",
          "name": "capacity",
          "schema": "integer"
        }
      ]
    }
  ]

다음 코드에서는 파서 라이브러리를 사용하여 C#에서 이러한 정의를 반영하는 방법의 예를 보여줍니다.

using Azure;
using Azure.DigitalTwins.Core;
using DTDLParser;
using DTDLParser.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DigitalTwins_Samples
{
    public static class ListExtensions
    {
        public static async IAsyncEnumerable<T> AsAsyncEnumerable<T>(this IEnumerable<T> input)
        {
            foreach (var value in input)
            {
                yield return value;
            }
            await Task.Yield();
        }
    }

    public class ParseModelsSample
    {
        public async Task ParseDemoAsync(DigitalTwinsClient client)
        {
            try
            {
                AsyncPageable<DigitalTwinsModelData> mdata = client.GetModelsAsync(new GetModelsOptions { IncludeModelDefinition = true });
                var models = new List<string>();
                await foreach (DigitalTwinsModelData md in mdata)
                    models.Add(md.DtdlModel);
                var parser = new ModelParser();
                IReadOnlyDictionary<Dtmi, DTEntityInfo> dtdlOM = await parser.ParseAsync(models.AsAsyncEnumerable());

                var interfaces = new List<DTInterfaceInfo>();
                IEnumerable<DTInterfaceInfo> ifenum =
                    from entity in dtdlOM.Values
                    where entity.EntityKind == DTEntityKind.Interface
                    select entity as DTInterfaceInfo;
                interfaces.AddRange(ifenum);
                foreach (DTInterfaceInfo dtif in interfaces)
                {
                    PrintInterfaceContent(dtif, dtdlOM);
                }
            }
            catch (RequestFailedException ex)
            {
                Console.WriteLine($"Failed due to {ex}");
                throw;
            }
        }

        public void PrintInterfaceContent(DTInterfaceInfo dtif, IReadOnlyDictionary<Dtmi, DTEntityInfo> dtdlOM, int indent = 0)
        {
            var sb = new StringBuilder();
            for (int i = 0; i < indent; i++) sb.Append("  ");
            Console.WriteLine($"{sb}Interface: {dtif.Id} | {dtif.DisplayName}");
            IReadOnlyDictionary<string, DTContentInfo> contents = dtif.Contents;

            foreach (DTContentInfo item in contents.Values)
            {
                switch (item.EntityKind)
                {
                    case DTEntityKind.Property:
                        DTPropertyInfo pi = item as DTPropertyInfo;
                        Console.WriteLine($"{sb}--Property: {pi.Name} with schema {pi.Schema}");
                        break;
                    case DTEntityKind.Relationship:
                        DTRelationshipInfo ri = item as DTRelationshipInfo;
                        Console.WriteLine($"{sb}--Relationship: {ri.Name} with target {ri.Target}");
                        break;
                    case DTEntityKind.Telemetry:
                        DTTelemetryInfo ti = item as DTTelemetryInfo;
                        Console.WriteLine($"{sb}--Telemetry: {ti.Name} with schema {ti.Schema}");
                        break;
                    case DTEntityKind.Component:
                        DTComponentInfo ci = item as DTComponentInfo;
                        Console.WriteLine($"{sb}--Component: {ci.Id} | {ci.Name}");
                        DTInterfaceInfo component = ci.Schema;
                        PrintInterfaceContent(component, dtdlOM, indent + 1);
                        break;                
                }
            }
        }
    }
}

다음 단계

모델 작성을 완료한 후에는 Azure DigitalTwinsModels API를 사용하여 모델을 업로드하는 방법(및 다른 관리 작업을 수행하는 방법)을 참조하세요.