Freigeben über


Eigenen benutzerdefinierten Kanal mittels Direct Line einbinden

Mit Omnichannel for Customer Service können Sie einen Connector implementieren, um benutzerdefinierte Messagingkanäle mithilfe der Direct Line-API 3.0 zu integrieren, die Teil des .NET SDK ist. Der vollständige Beispielcode veranschaulicht, wie Sie einen eigenen Connector erstellen können. Weitere Informationen zur Direct Line API 3.0 finden Sie unter Wichtige Konzepte in der Direct Line 3.0 API.

In diesem Artikel wird erläutert, wie ein Kanal mit dem Microsoft Direct Line Bot Framework verbunden wird, das intern an Omnichannel for Customer Service angefügt ist. Der folgende Abschnitt enthält Codeausschnitte, die Direct Line API 3.0 verwenden, um einen Direct Line-Client zu erstellen, und die IChannelAdapter Schnittstelle zum Erstellen eines Beispielconnectors.

Hinweis

Der Quellcode und die Dokumentation beschreiben den Gesamtablauf, wie der Kanal über Direct Line eine Verbindung zu Omnichannel for Customer Service herstellen kann, und konzentrieren sich nicht auf Aspekte der Zuverlässigkeit und Skalierbarkeit.

Komponenten

Adapter-Webhook-API-Dienst

Wenn der Benutzer eine Nachricht eingibt, wird die Adapter-API über den Kanal aufgerufen. Er verarbeitet die eingehende Anforderung und sendet einen Erfolgs- oder Fehlerstatus als Antwort. Der Adapter-API-Dienst muss die IChannelAdapter Schnittstelle implementieren und sendet die eingehende Anforderung an den jeweiligen Kanaladapter, um die Anforderung zu verarbeiten.

/// <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);
    }

Kanal-Adapter

Der Kanaladapter verarbeitet die eingehenden und ausgehenden Aktivitäten und muss die IAdapterBuilder Schnittstelle implementieren.

Eingehende Aktivitäten verarbeiten

Die Kanaladapter führen die folgenden eingehenden Aktivitäten durch:

  1. Überprüfen Sie die Anforderungssignatur für eingehende Nachrichten.

Die eingehende Anforderung vom Kanal wird basierend auf dem Signaturschlüssel überprüft. Wenn die Anforderung ungültig ist, wird die Ausnahmemeldung "Ungültige Signatur" ausgegeben. Wenn die Anforderung gültig ist, geht sie wie folgt vor:

  /// <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. Konvertieren Sie die eingehende Anforderung in eine Bot-Aktivität.

Die Nutzlast der eingehenden Anforderung wird in eine Aktivität konvertiert, die das Bot Framework verstehen kann.

Hinweis

Die Aktivitätsnutzlast darf die Nachrichtengrößenbeschränkung von 28 KB nicht überschreiten.

Dieses Activity-Objekt enthält die folgenden Attribute:

Merkmal BESCHREIBUNG
von Speichert die Informationen zum Kanalkonto, die aus der eindeutigen Kennung des Benutzers und dem Namen bestehen (Kombination aus Vorname und Nachname, getrennt durch ein Leerzeichen).
channelId (Kanal-ID) Gibt die Kanalkennung an. Bei eingehenden Anforderungen lautet die Kanal-ID directline.
serviceUrl Gibt die Dienst-URL an. Für eingehende Anfragen ist die Service-URL https://directline.botframework.com/.
Typ Gibt den Aktivitätstyp an. Für Nachrichtenaktivitäten ist der Typ message.
Text Speichert den Nachrichteninhalt.
id Gibt den Bezeichner an, den der Adapter verwendet, um auf ausgehende Nachrichten zu antworten.
channel-Daten Gibt Kanaldaten an, die aus channelType, conversationcontextund customercontextbestehen.
channelType Gibt den Namen des Kanals an, über den der Kunde Nachrichten sendet. Zum Beispiel MessageBird, KakaoTalk, Snapchat
conversationcontext Verweist auf ein Dictionary-Objekt, das die im Workstream definierten Kontextvariablen enthält. Omnichannel for Customer Service verwendet diese Informationen, um das Gespräch an den richtigen Kundendienstmitarbeiter (Servicemitarbeiter oder Vertreter) weiterzuleiten. Beispiel:
"conversationcontext ":{ "ProductName" : "Xbox", "Issue":"Installation" }
In diesem Beispiel leitet der Kontext die Konversation an den Servicemitarbeiter weiter, der sich um die Xbox-Installation kümmert.
KundenKontext Bezieht sich auf ein Wörterbuchobjekt, das die Kundendetails wie Telefonnummer und E-Mail-Adresse enthält. Omnichannel for Customer Service verwendet diese Informationen, um den Kontaktdatensatz des Benutzers zu identifizieren.
"customercontext":{ "email":email@email.com, "telefonnummer":"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;
  }

Die JSON-Beispielnutzlast lautet wie folgt:

{
    "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. Senden Sie die Aktivität an den Nachrichtenrelayprozessor.

Nach dem Erstellen der Aktivitätsnutzlast ruft sie die PostActivityAsync-Methode des Nachrichtenrelayprozessors auf, um die Aktivität an Direct Line zu senden. Der Kanaladapter sollte auch den Ereignishandler übergeben, den der Relayprozessor aufruft, wenn er eine ausgehende Nachricht von Omnichannel for Customer Service über Direct Line empfängt.

Ausgehende Aktivitäten verarbeiten

Der Relayprozessor ruft den Ereignishandler auf, um ausgehende Aktivitäten an den jeweiligen Kanaladapter zu senden, und der Adapter verarbeitet dann die ausgehenden Aktivitäten. Der Kanaladapter führt die folgenden ausgehenden Aktivitäten aus:

  1. Konvertieren Sie ausgehende Aktivitäten in das Kanalantwortmodell.

Die Direct Line-Aktivitäten werden in das kanalspezifische Antwortmodell konvertiert.

  /// <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. Senden Sie Antworten über die REST-API des Kanals.

Der Kanaladapter ruft die REST-API auf, um eine ausgehende Antwort an den Kanal zu senden, die dann an den Benutzer gesendet wird.

  /// <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);
          }
      }
  }

Nachrichtenrelais-Prozessor

Der Nachrichtenrelayprozessor empfängt die eingehende Aktivität vom Kanaladapter und führt die Überprüfung des Aktivitätsmodells durch. Bevor diese Aktivität an Direct Line gesendet wird, prüft der Relay-Prozessor, ob die Konversation für die jeweilige Aktivität aktiv ist.

Um nachzuschlagen, ob die Konversation aktiv ist, verwaltet der Relayprozessor eine Sammlung aktiver Konversationen in einem Wörterbuch. Dieses Wörterbuch enthält den Schlüssel als Benutzer-ID, der den Benutzer eindeutig identifiziert, und Value als Objekt der folgenden Klasse:

 /// <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; }
}

Wenn die Konversation für die vom Relayprozessor empfangene Aktivität nicht aktiv ist, führt er die folgenden Schritte aus:

  1. Startet eine Konversation mit Direct Line und speichert das von Direct Line gesendete Konversationsobjekt unter der Benutzer-ID im Wörterbuch.
 /// <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. Startet einen neuen Thread, um die ausgehenden Aktivitäten vom Direct Line-Bot basierend auf dem in der Konfigurationsdatei konfigurierten Abrufintervall abzufragen. Der Abfragethread ist aktiv, bis das Ende der Unterhaltungsaktivität von der Direct Line empfangen wird.
/// <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);
    }
}

Hinweis

Das Herzstück des Codes, der die Nachricht empfängt, ist die GetActivitiesAsync-Methode, die ConversationId und watermark als Parameter akzeptiert. Der Zweck des watermark Parameters besteht darin, die Nachrichten abzurufen, die noch nicht von Direct Line zugestellt wurden. Wenn der Parameter watermark angegeben ist, wird die Unterhaltung ab dem Wasserzeichen wiedergegeben, sodass keine Nachrichten verloren gehen.

Senden der Aktivität an 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);
 }

Wenn die Konversation für die vom Relayprozessor empfangene Aktivität aktiv ist, sendet sie die Aktivität an den Nachrichtenrelayprozessor.

Eine Unterhaltung beenden

Informationen zum Beenden der Konversation finden Sie unter Beenden einer Konversation in Direct Line.

Nächste Schritte

Unterstützung für Live-Chat und asynchrone Kanäle
Markdown-Formate in angepassten Kanälen, die Direct Line verwenden

Benutzerdefinierter Nachrichtenkanal konfigurieren
MessageBird-API-Referenz
Bewährte Methoden für Azure Botkonfiguration