次の方法で共有


Direct Line を使用して独自のカスタム チャネルを統合する

Dynamics 365 Contact Center では、.NET SDK の一部である Direct Line API 3.0 を使用してカスタム メッセージング チャネルを統合するコネクタを実装できます。 完全な サンプル コードは 、独自のコネクタを作成する方法を示しています。 Direct Line API 3.0 の詳細については、「 Direct Line 3.0 API の主要な概念」を参照してください。

この記事では、Dynamics 365 Contact Center に内部的に接続されている Microsoft Direct Line Bot Framework にチャネルがどのように接続されているかについて説明します。 次のセクションでは、Direct Line API 3.0 を使用して Direct Line クライアントを作成するコード スニペットと、サンプル コネクタをビルドするための IChannelAdapter インターフェイスについて説明します。

ソース コードとドキュメントでは、チャネルが Direct Line 経由で Dynamics 365 Contact Center に接続する方法の全体的なフローについて説明しており、信頼性とスケーラビリティの側面には焦点を当てません。

コンポーネント

アダプタ Webhook API サービス

ユーザーがメッセージを入力すると、チャネルからアダプター API が呼び出されます。 受信要求を処理し、成功または失敗のステータスを応答として送信します。 アダプター API サービスは、 IChannelAdapter インターフェースを実装する必要があり、受信要求をそれぞれのチャネル アダプターに送信して要求を処理する必要があります。

/// <summary>
/// Accept an incoming web-hook request from MessageBird Channel
/// </summary>
/// <param name="requestPayload">Inbound request Object</param>
/// <returns>Executes the result operation of the action method asynchronously.</returns>
    [HttpPost("postactivityasync")]
    public async Task<IActionResult> PostActivityAsync(JToken requestPayload)
    {
        if (requestPayload == null)
        {
            return BadRequest("Request payload is invalid.");
        }

        try
        {
            await _messageBirdAdapter.ProcessInboundActivitiesAsync(requestPayload, Request).ConfigureAwait(false);
        }
        catch (Exception ex)
        {
            _logger.LogError($"postactivityasync: {ex}");
            return StatusCode(500, "An error occured while handling your request.");
        }

        return StatusCode(200);
    }

チャネル・アダプター

チャネル・アダプターは、インバウンド・アクティビティーとアウトバウンド・アクティビティーを処理し、 IAdapterBuilder インターフェースを実装する必要があります。

インバウンド活動の処理

チャネル・アダプターは、以下のインバウンド・アクティビティーを実行します。

  1. 受信メッセージ要求の署名を検証します。

チャネルからの受信要求は、署名キーに基づいて検証されます。 要求が無効な場合、"無効な署名" 例外メッセージがスローされます。 要求が有効な場合、要求は次のように進行します。

  /// <summary>
  /// Validate Message Bird Request
  /// </summary>
  /// <param name="content">Request Content</param>
  /// <param name="request">HTTP Request</param>
  /// <param name="messageBirdSigningKey">Message Bird Signing Key</param>
  /// <returns>True if there request is valid, false if there aren't.</returns>
  public static bool ValidateMessageBirdRequest(string content, HttpRequest request, string messageBirdSigningKey)
  {
      if (string.IsNullOrWhiteSpace(messageBirdSigningKey))
      {
          throw new ArgumentNullException(nameof(messageBirdSigningKey));
      }
      if (request == null)
      {
          throw new ArgumentNullException(nameof(request));
      }
      if (string.IsNullOrWhiteSpace(content))
      {
          throw new ArgumentNullException(nameof(content));
      }
      var messageBirdRequest = new MessageBirdRequest(
          request.Headers?["Messagebird-Request-Timestamp"],
          request.QueryString.Value?.Equals("?",
              StringComparison.CurrentCulture) != null
              ? string.Empty
              : request.QueryString.Value,
          GetBytes(content));

      var messageBirdRequestSigner = new MessageBirdRequestSigner(GetBytes(messageBirdSigningKey));
      string expectedSignature = request.Headers?["Messagebird-Signature"];
      return messageBirdRequestSigner.IsMatch(expectedSignature, messageBirdRequest);
  }
  1. 受信要求をボット アクティビティに変換します。

受信要求ペイロードは、Bot Framework が理解できるアクティビティに変換されます。

アクティビティ ペイロードは、メッセージ サイズの制限である 28 KB を超えてはなりません。

この Activity オブジェクトには、次の属性が含まれています。

特性 説明
フロム ユーザーと名前の一意の識別子 (名と姓の組み合わせ、スペース区切り文字で区切られたもの) で構成されるチャネル アカウント情報を格納します。
チャネルID チャネル識別子を示します。 受信要求の場合、チャネル ID は directline です。
サービスURL サービスの URL を示します。 受信要求の場合、サービス URL は https://directline.botframework.com/ です。
タイプ アクティビティの種類を示します。 メッセージ アクティビティの場合、タイプは message です。
テキスト メッセージの内容を格納します。
ID アダプターが送信メッセージに応答するために使用する識別子を示します。
チャンネルデータ channelTypeconversationcontext、および customercontext で構成されるチャネル データを示します。
チャンネルタイプ 顧客がメッセージを送信しているチャネル名を示します。 たとえば、MessageBird、KakaoTalk、Snapchat
カンバセーション・コンテキスト ワークストリームで定義されたコンテキスト変数を保持するディクショナリオブジェクトを参照します。 Dynamics 365 Contact Center では、この情報を使用して、適切な顧客サービス担当者 (サービス担当者または担当者) に会話をルーティングします。 例えば次が挙げられます。
"会話コンテキスト":{ "ProductName": "Xbox","問題":"インストール" }
この例では、コンテキストは、Xbox のインストールを処理するサービス担当者に会話をルーティングします。
カスタマーコンテキスト 電話番号やメールアドレスなどの顧客の詳細を保持する辞書オブジェクトを指します。 Dynamics 365 Contact Center では、この情報を使用してユーザーの連絡先レコードを識別します。
"customercontext":{ "email":email@email.com, "phonenumber":"1234567890" }
  /// <summary>
  /// Build Bot Activity type from the inbound MessageBird request payload<see cref="Activity"/>
  /// </summary>
  /// <param name = "messagePayload"> Message Bird Activity Payload</param>
  /// <returns>Direct Line Activity</returns>
  public static Activity PayloadToActivity(MessageBirdRequestModel messagePayload)
  {
  if (messagePayload == null)
  {
      throw new ArgumentNullException(nameof(messagePayload));
  }
  if (messagePayload.Message?.Direction == ConversationMessageDirection.Sent ||
  messagePayload.Type == ConversationWebhookMessageType.MessageUpdated)
  {
      return null;
  }
  var channelData = new ActivityExtension
  {
      ChannelType = ChannelType.MessageBird,
      // Add Conversation Context in below dictionary object. Please refer the document for more information.
      ConversationContext = new Dictionary<string, string>(),
      // Add Customer Context in below dictionary object. Please refer the document for more information.
      CustomerContext = new Dictionary<string, string>()
  };
  var activity = new Activity
      {
          From = new ChannelAccount(messagePayload.Message?.From, messagePayload.Contact?.DisplayName),
          Text = messagePayload.Message?.Content?.Text,
          Type = ActivityTypes.Message,
          Id = messagePayload.Message?.ChannelId,
          ServiceUrl = Constant.DirectLineBotServiceUrl,
          ChannelData = channelData
      };

      return activity;
  }

サンプル JSON ペイロードは次のとおりです。

{
    "type": "message",
    "id": "bf3cc9a2f5de...",    
    "serviceUrl": https://directline.botframework.com/,
    "channelId": "directline",
    "from": {
        "id": "1234abcd",// userid which uniquely identify the user
        "name": "customer name" // customer name as First Name <space> Last Name
    },
    "text": "Hi,how are you today.",
    "channeldata":{
        "channeltype":"messageBird",
        "conversationcontext ":{ // this holds context variables defined in Workstream
            "ProductName" : "XBox",
            "Issue":"Installation"
        },
        "customercontext":{            
            "email":email@email.com,
            "phonenumber":"1234567890"           
        }
    }
}

  1. アクティビティをメッセージ・リレー・プロセッサーに送信します。

アクティビティ ペイロードを作成した後、メッセージ リレー プロセッサの PostActivityAsync メソッドを呼び出して、アクティビティを Direct Line に送信します。 チャネル アダプターは、Dynamics 365 Contact Center から Direct Line 経由で送信メッセージを受信したときにリレー プロセッサが呼び出すイベント ハンドラーも渡す必要があります。

アウトバウンド活動の処理

リレー プロセッサは、イベント ハンドラを呼び出して送信アクティビティをそれぞれのチャネル アダプタに送信し、アダプタは送信アクティビティを処理します。 チャネル・アダプターは、以下のアウトバウンド・アクティビティーを実行します。

  1. アウトバウンド・アクティビティーをチャネル応答モデルに変換します。

Direct Line アクティビティは、チャネル固有の応答モデルに変換されます。

  /// <summary>
  /// Creates MessageBird response object from a Bot Framework <see cref="Activity"/>.
  /// </summary>
  /// <param name="activities">The outbound activities.</param>
  /// <param name="replyToId">Reply Id of Message Bird user.</param>
  /// <returns>List of MessageBird Responses.</returns>
  public static List<MessageBirdResponseModel> ActivityToMessageBird(IList<Activity> activities, string replyToId)
  {
      if (string.IsNullOrWhiteSpace(replyToId))
      {
          throw new ArgumentNullException(nameof(replyToId));
      }

      if (activities == null)
      {
          throw new ArgumentNullException(nameof(activities));
      }

      return activities.Select(activity => new MessageBirdResponseModel
      {
          To = replyToId,
          From = activity.ChannelId,
          Type = "text",
          Content = new Content
          {
              Text = activity.Text
          }
      }).ToList();
  }
  1. チャネル REST API を介して応答を送信します。

チャネル アダプターは REST API を呼び出してチャネルに送信応答を送信し、送信応答をユーザーに送信します。

  /// <summary>
  /// Send Outbound Messages to Message Bird
  /// </summary>
  /// <param name="messageBirdResponses">Message Bird Response object</param>
  /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
  public async Task SendMessagesToMessageBird(IList<MessageBirdResponseModel> messageBirdResponses)
  {
      if (messageBirdResponses == null)
      {
          throw new ArgumentNullException(nameof(messageBirdResponses));
      }

      foreach (var messageBirdResponse in messageBirdResponses)
      {
          using (var request = new HttpRequestMessage(HttpMethod.Post, $"{MessageBirdDefaultApi}/send"))
          {
              var content = JsonConvert.SerializeObject(messageBirdResponse);
              request.Content = new StringContent(content, Encoding.UTF8, "application/json");
              await _httpClient.SendAsync(request).ConfigureAwait(false);
          }
      }
  }

メッセージ・リレー・プロセッサー

メッセージ・リレー・プロセッサーは、チャネル・アダプターからインバウンド・アクティビティーを受信し、アクティビティー・モデルの検証を行います。 リレー プロセッサは、特定のアクティビティについて会話がアクティブかどうかを確認した後、このアクティビティを Direct Line に送信します

会話がアクティブであるかどうかを調べるために、リレー プロセッサはアクティブな会話のコレクションをディクショナリに保持します。 このディクショナリには、ユーザーを一意に識別するユーザー ID と、次のクラスのオブジェクトとしての Value を一意に識別するキーが含まれています。

 /// <summary>
/// Direct Line Conversation to store as an Active Conversation
/// </summary>
public class DirectLineConversation
{
    /// <summary>
    /// .NET SDK Client to connect to Direct Line Bot
    /// </summary>
    public DirectLineClient DirectLineClient { get; set; }

    /// <summary>
    /// Direct Line response after start a new conversation
    /// </summary>
    public Conversation Conversation { get; set; }

    /// <summary>
    /// Watermark to guarantee that no messages are lost
    /// </summary>
    public string WaterMark { get; set; }
}

リレー プロセッサが受信した活動に対して会話がアクティブでない場合は、次の手順を実行します:

  1. Direct Line との会話を開始し、Direct Line によってユーザー ID に対して送信された会話オブジェクトをディクショナリに格納します。
 /// <summary>
 /// Initiate Conversation with Direct Line Bot
 /// </summary>
 /// <param name="inboundActivity">Inbound message from Aggregator/Channel</param>
 /// <param name="adapterCallBackHandler">Call Back to send activities to Messaging API</param>
 /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
 private async Task InitiateConversation(Activity inboundActivity, EventHandler<IList<Activity>> adapterCallBackHandler)
 {
     var directLineConversation = new DirectLineConversation
     {
         DirectLineClient = new DirectLineClient(_relayProcessorConfiguration.Value.DirectLineSecret)
     };
     // Start a conversation with Direct Line Bot
     directLineConversation.Conversation = await directLineConversation.DirectLineClient.Conversations.
         StartConversationAsync().ConfigureAwait(false);

     await directLineConversation.DirectLineClient.Conversations.
         StartConversationAsync().ConfigureAwait(false);
     if (directLineConversation.Conversation == null)
     {
         throw new Exception(
             "An error occurred while starting the Conversation with direct line. Please validate the direct line secret in the configuration file.");
     }

     // Adding the Direct Line Conversation object to the lookup dictionary and starting a thread to poll the activities from the direct line bot.
     if (ActiveConversationCache.ActiveConversations.TryAdd(inboundActivity.From.Id, directLineConversation))
     {
         // Starts a new thread to poll the activities from Direct Line Bot
         new Thread(async () => await PollActivitiesFromBotAsync(
             directLineConversation.Conversation.ConversationId, inboundActivity, adapterCallBackHandler).ConfigureAwait(false))
         .Start();
     }
 }
  1. 新しいスレッドを開始して、構成ファイルで構成されたポーリング間隔に基づいて Direct Line ボットからの送信アクティビティをポーリングします。 ポーリング スレッドは、Direct Line から会話終了の活動を受信するまでアクティブとなります。
/// <summary>
/// Polling the activities from BOT for the active conversation
/// </summary>
/// <param name="conversationId">Direct Line Conversation Id</param>
/// <param name="inboundActivity">Inbound Activity from Channel/Aggregator</param>
/// <param name="lineActivitiesReceived">Call Back to send activities to Messaging API</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
private async Task PollActivitiesFromBotAsync(string conversationId, Activity inboundActivity, EventHandler<IList<Activity>> lineActivitiesReceived)
{
    if (!int.TryParse(_relayProcessorConfiguration.Value.PollingIntervalInMilliseconds, out var pollingInterval))
    {
        throw new FormatException($"Invalid Configuration value of PollingIntervalInMilliseconds: {_relayProcessorConfiguration.Value.PollingIntervalInMilliseconds}");
    }
    if (!ActiveConversationCache.ActiveConversations.TryGetValue(inboundActivity.From.Id,
        out var conversationContext))
    {
        throw new KeyNotFoundException($"No active conversation found for {inboundActivity.From.Id}");
    }
    while (true)
    {
        var watermark = conversationContext.WaterMark;
        // Retrieve the activity set from the bot.
        var activitySet = await conversationContext.DirectLineClient.Conversations.
            GetActivitiesAsync(conversationId, watermark).ConfigureAwait(false);
        // Set the watermark to the message received
        watermark = activitySet?.Watermark;

        // Extract the activities sent from our bot.
        if (activitySet != null)
        {
            var activities = (from activity in activitySet.Activities
                              where activity.From.Id == _relayProcessorConfiguration.Value.BotHandle
                              select activity).ToList();
            if (activities.Count > 0)
            {
                SendReplyActivity(activities, inboundActivity, lineActivitiesReceived);
            }
            // Update Watermark
            ActiveConversationCache.ActiveConversations[inboundActivity.From.Id].WaterMark = watermark;
            if (activities.Exists(a => a.Type.Equals("endOfConversation", StringComparison.InvariantCulture)))
            {
                if (ActiveConversationCache.ActiveConversations.TryRemove(inboundActivity.From.Id, out _))
                {
                    Thread.CurrentThread.Abort();
                }
            }
        }
        await Task.Delay(TimeSpan.FromMilliseconds(pollingInterval)).ConfigureAwait(false);
    }
}

メッセージを受信するコードの中心にあるのは、 ConversationIdwatermark をパラメーターとして受け取る GetActivitiesAsync メソッドです。 watermark パラメーターの目的は、Direct Line でまだ配信されていないメッセージを取得することです。 watermark パラメーターを指定すると、会話はウォーターマークから再生されるため、メッセージが失われることはありません。

アクティビティを Direct Line に送信する

 /// <summary>
 /// Send the activity to the bot using Direct Line client
 /// </summary>
 /// <param name="inboundActivity">Inbound message from Aggregator/Channel</param>
 /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
 private static async Task SendActivityToBotAsync(Activity inboundActivity)
 {
     if (!ActiveConversationCache.ActiveConversations.TryGetValue(inboundActivity.From.Id,
         out var conversationContext))
     {
         throw new KeyNotFoundException($"No active conversation found for {inboundActivity.From.Id}");
     }
     await conversationContext.DirectLineClient.Conversations.PostActivityAsync(
         conversationContext.Conversation.ConversationId, inboundActivity).ConfigureAwait(false);
 }

リレー プロセッサが受信したアクティビティに対して会話がアクティブである場合は、そのアクティビティをメッセージ リレー プロセッサに送信します。

会話の終了

会話を終了するには、「 Direct Line で会話を終了する」を参照してください。

カスタム チャンネルにおける Markdown 形式

Direct Line API 3.0 を使用して、カスタム メッセージング チャネルで Markdown でフォーマットされたメッセージを送受信することができます。 Markdown 形式がチャネルを介してどのようにパスされるかを理解し、形式の詳細を知ることは、独自のユーザーインターフェイスで HTML スタイルとタグを更新する際に役立ちます。

Direct Line チャネルでは、顧客サービス担当者 (サービス担当者または担当者) が Markdown で書式設定されたメッセージを Direct Line ボットに送信 (送信) すると、ボットは特定の形式でメッセージを受信します。 ここで、ボットが顧客からの (インバウンド) フォーマットされたメッセージを受信した場合、Markdown でフォーマットされたメッセージを正しく解釈できる必要があります。 開発者は、サービス担当者と顧客向けにメッセージが正しくフォーマットされるように、Markdown を適切に使用する必要があります。

チャット メッセージの Markdown 形式のマークダウン形式の詳細について説明します。

  • 現在、複数の改行を追加するための <Shift + Enter> キーの組み合わせはサポートされていません。
  • インバウンド メッセージの場合、Markdown テキストを アクティビティ オブジェクトの text プロパティに設定します。
  • アウトバウンド メッセージの場合、Markdown テキストは アクティビティ オブジェクトの text プロパティ (通常のメッセージと同様) で受信されます。

次のステップ

ライブ チャットと非同期チャネルのサポート

カスタム メッセージング チャネルを構成する
MessageBird API リファレンス
ボットを構成するためのベストプラクティス