Teilen über


Integrieren Sie Ihren eigenen angepassten Kanal über Direct Line

Omnichannel for Customer Service bietet eine Reihe von Funktionalitäten, die die Möglichkeiten von Dynamics 365 Customer Service Enterprise erweitern und es Unternehmen ermöglichen, über digitale Nachrichtenkanäle sofort mit ihren Kunden in Kontakt zu treten und sich mit ihnen zu engagieren. Für den Zugriff auf Omnichannel for Customer Service ist eine zusätzliche Lizenz erforderlich. Weitere Informationen finden Sie auf den Seiten Dynamics 365 Customer Service – Preisübersicht und Dynamics 365 Customer Service – Preisplan.

Mit Omnichannel for Customer Service können Sie einen Connector implementieren, um benutzerdefinierte Messaging-Kanäle zu integrieren, indem Sie Direct Line API 3.0 verwenden, der Teil des .NET SDK ist. Der vollständige Beispielcode veranschaulicht, wie Sie Ihren eigenen Connector erstellen können. Um mehr über die Direct Line API 3.0 zu erfahren, siehe Schlüsselkonzepte in der Direct Line 3.0 API.

In diesem Artikel wird kurz erklärt, wie ein Kanal mit dem Microsoft Direct Line Bot Framework verbunden ist, der intern an Omnichannel for Customer Service angeschlossen ist. Der folgende Abschnitt enthält Codeschnipsel, die Direct Line API 3.0 verwenden, um einen Direct Line-Client und die IChannelAdapter-Schnittstelle zu erstellen, um einen Beispiel-Konnektor zu erstellen.

Notiz

Der Quellcode und die Dokumentation beschreiben den allgemeinen Flow, wie der Kanal über Direct Line mit Omnichannel for Customer Service verbunden werden kann, und gehen nicht auf Aspekte der Zuverlässigkeit und Skalierbarkeit ein.

Komponenten

Adapter Webhook API Service

Wenn der Benutzer eine Nachricht eingibt, wird die Adapter-API vom Kanal aus aufgerufen. Es verarbeitet die eingehende Anforderung und sendet entweder einen Erfolgs- oder den Fehlerstatus als Antwort. Der Adapter-API-Dienst muss die IChannelAdapter-Schnittstelle implementieren und sendet die eingehende Anfrage an den jeweiligen Kanaladapter, um die Anfrage 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);
    }

Kanaladapter

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 Signatur der Anforderung eingehender Nachrichten.

Die eingehende Anfrage vom Kanal wird anhand des Signaturschlüssels validiert. Wenn die Anforderung ungültig ist, wird eine Ausnahmemeldung „ungültige Signatur“ ausgegeben. Wenn die Anforderung gültig ist, fährt es wie folgt fort:

  /// <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 Nutzdaten für eingehende Anforderungen werden in eine Aktivität konvertiert, die Bot Framework verstehen kann.

Notiz

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

Dieses Aktivitätsobjekt enthält die folgenden Attribute:

Attribute Beschreibung des Dataflows
from Speichert die Kanal-Kontoinformationen, die aus dem eindeutigen Bezeichner des Benutzers und dem Namen (Kombination aus Vor- und Nachname, getrennt durch ein Leerzeichen) bestehen.
channelId Gibt die Kanal-Kennung an. Bei eingehenden Anfragen ist die Kanalkennung directline.
Dienst-URL Zeigt die Service-URL an. Für eingehende Anfragen ist die Service-URL https://directline.botframework.com/.
type Gibt den Typ der Aktivität an. Für Nachrichten-Aktivitäten ist der Typ message.
text Speichert den Inhalt der Nachricht.
id Gibt den Identifier an, mit dem der Adapter auf ausgehende Nachrichten antwortet.
Kanaldaten Gibt die Kanaldaten an, die aus channelType, conversationcontext und customercontext bestehen.
channelType Gibt den Kanalnamen an, über den der Kunde Nachrichten sendet. Zum Beispiel, MessageBird, KakaoTalk, Snapchat
Konversationskontext Verweist auf ein Wörterbuchobjekt, das die im Workstream definierten Kontextvariablen enthält. Omnichannel for Customer Service verwendet diese Informationen, um die Unterhaltung an den richtigen Agenten zu leiten. Beispiel:
"conversationcontext ":{ "ProductName" : "Xbox", "Issue":"Installation" }
In diesem Beispiel leitet der Kontext die Unterhaltung an den Agenten weiter, der sich mit der Installation Xbox beschäftigt.
Kundenkontext Verweist auf ein Wörterbuchobjekt, das die Kundendaten wie Telefonnummer und E-Mail-Adresse enthält. Omnichannel for Customer Service verwendet diese Informationen, um den Datensatz des Benutzers zu identifizieren.
„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;
  }

Der Beispiel-JSON-Payload sieht wie folgt aus:

{
    "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 Message-Relay-Prozessor.

Nachdem die Payload der Aktivität erstellt wurde, wird die PostActivityAsync-Methode des Message-Relay-Prozessors aufgerufen, um die Aktivität an Direct Line zu senden. Der Kanaladapter sollte auch den Event-Handler übergeben, den der Relay-Prozessor aufruft, wenn er eine ausgehende Nachricht von Omnichannel for Customer Service über Direct Line empfängt.

Ausgehende Aktivitäten verarbeiten

Der Relay-Prozessor ruft den Event-Handler 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 durch:

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

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

  /// <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 den Kanal REST-API.

Der Kanaladapter ruft 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);
          }
      }
  }

Nachrichten-Relay-Prozessor

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

Um festzustellen, ob die Konversation aktiv ist, verwaltet der Relay-Prozessor eine Sammlung aktiver Konversationen in einem Wörterbuch. Dieses Wörterbuch enthält als Schlüssel die Benutzer-ID, die den Benutzer eindeutig identifiziert und als Wert ein 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 Unterhaltung für die vom Relais-Prozessor empfangene Aktivität nicht aktiv ist, führt er folgende Schritte aus

  1. Startet eine Unterhaltung mit Direct Line und speichert das von Direct Line gesendete Unterhaltungsobjekt gegen die 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 Abfrageintervall abzufragen. Der Polling-Thread ist so lange aktiv, bis das Ende der Aktivitäten der Unterhaltung von 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);
    }
}

Notiz

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

Aktivität an Direct Line senden

 /// <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 Unterhaltung für die vom Relais-Prozessor empfangene Aktivität aktiv ist, sendet er die Aktivität an den Nachrichten-Relais-Prozessor.

Eine Unterhaltung beenden

Um das Gespräch zu beenden, siehe Beenden Sie ein Gespräch 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

Siehe auch

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