Delen via


Uw eigen aangepaste kanaal integreren met Direct Line

Met Dynamics 365 Contact Center kunt u een connector implementeren om aangepaste berichtenkanalen te integreren met behulp van Direct Line API 3.0, dat deel uitmaakt van .NET SDK. De volledige voorbeeldcode illustreert hoe u uw eigen connector kunt maken. Zie Belangrijkste concepten in de Direct Line 3.0 API voor meer informatie over de Direct Line API 3.0.

In dit artikel wordt uitgelegd hoe een kanaal is verbonden met het Microsoft Direct Line Bot Framework, dat intern is gekoppeld aan dynamics 365 Contact Center. De volgende sectie bevat codefragmenten die gebruikmaken van Direct Line API 3.0 om een Direct Line-client te maken en de IChannelAdapter interface om een voorbeeldconnector te bouwen.

Opmerking

De broncode en documentatie beschrijven de algehele stroom van hoe het kanaal via Direct Line verbinding kan maken met Dynamics 365 Contact Center en niet gericht is op aspecten van betrouwbaarheid en schaalbaarheid.

Onderdeel

Adapter Webhook API-dienst

Wanneer de gebruiker een bericht invoert, wordt de adapter-API aangeroepen vanuit het kanaal. Het verwerkt de binnenkomende aanvraag en verzendt een geslaagde of mislukte status als reactie. De adapter API-service moet de IChannelAdapter interface implementeren en stuurt de binnenkomende aanvraag naar de betreffende kanaaladapter om de aanvraag te verwerken.

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

Kanaaladapters

De kanaaladapter verwerkt de inkomende en uitgaande activiteiten en moet de IAdapterBuilder interface implementeren.

Inkomende activiteiten verwerken

De kanaaladapter voert de volgende inkomende activiteiten uit:

  1. Valideer de handtekening van het inkomende berichtsverzoek.

De inkomende aanvraag van het kanaal wordt gevalideerd op basis van de ondertekeningssleutel. Als het verzoek ongeldig is, wordt er een uitzonderingsbericht met de melding 'ongeldige handtekening' gegenereerd. Als het verzoek geldig is, gaat het als volgt te werk:

  /// <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. Converteer de binnenkomende aanvraag naar een botactiviteit.

De payload van de inkomende aanvraag wordt geconverteerd naar een activiteit die het Bot Framework kan begrijpen.

Opmerking

De nettolading van de activiteit mag de limiet voor de berichtgrootte van 28 kB niet overschrijden.

Dit activiteitsobject bevat de volgende kenmerken:

Eigenschap Beschrijving
van Slaat de kanaalaccountgegevens op die bestaan uit de unieke ID van de gebruiker en de naam (combinatie van voornaam en achternaam, gescheiden door een scheidingsteken).
kanaal-ID Geeft de kanaal-ID aan. Voor binnenkomende verzoeken is directlinede kanaal-ID .
serviceUrl Geeft de service-URL aan. Voor binnenkomende aanvragen is https://directline.botframework.com/de service-URL .
type Geeft het activiteitstype aan. Voor berichtactiviteiten is het type message.
tekst Slaat de inhoud van het bericht op.
ID Geeft de id aan die de adapter gebruikt om te reageren op uitgaande berichten.
kanaal gegevens Geeft kanaalgegevens aan die bestaan uit channelType, conversationcontext, en customercontext.
kanaaltype Geeft de kanaalnaam aan via welke de klant berichten verzendt. Bijvoorbeeld MessageBird, KakaoTalk, Snapchat
Conversatiecontext Verwijst naar een woordenboekobject dat de contextvariabelen bevat die in de werkstroom zijn gedefinieerd. Dynamics 365 Contact Center gebruikt deze informatie om het gesprek door te sturen naar de juiste klantenservicemedewerker (servicevertegenwoordiger of vertegenwoordiger). Voorbeeld:
"conversationcontext ":{ "ProductName": "Xbox", "Probleem ":"Installatie" }
In dit voorbeeld wordt het gesprek door de context doorgestuurd naar de servicevertegenwoordiger die zich bezighoudt met de Xbox-installatie.
customercontext Verwijst naar een woordenboekobject dat de klantgegevens bevat, zoals telefoonnummer en e-mailadres. Dynamics 365 Contact Center gebruikt deze gegevens om de contactpersoonrecord van de gebruiker te identificeren.
"customercontext":{ "email":email@email.com, "telefoonnummer":"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;
  }

De voorbeeld-JSON-payload is als volgt:

{
    "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. Stuur de activiteit naar de verwerker van het berichtenrelais.

Nadat de activiteitspayload is gebouwd, wordt de PostActivityAsync-methode van de berichtdoorvoerprocessor aangeroepen om de activiteit naar Direct Line te verzenden. De kanaaladapter moet ook de eventhandler doorgeven die de relayprocessor aanroept wanneer er via Direct Line een uitgaand bericht van Dynamics 365 Contact Center wordt ontvangen.

Uitgaande activiteiten verwerken

De relaisprocessor roept de gebeurtenishandler aan om uitgaande activiteiten naar de betreffende kanaaladapter te verzenden, waarna de adapter de uitgaande activiteiten verwerkt. De kanaaladapter voert de volgende uitgaande activiteiten uit:

  1. Uitgaande omzetten activiteiten in het kanaalresponsmodel.

De activiteiten van de Directe Lijn worden geconverteerd naar het kanaalspecifieke responsmodel.

  /// <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. Stuur antwoorden via de REST API van het kanaal.

De kanaaladapter roept de REST API aan om een uitgaande reactie naar het kanaal te verzenden, die vervolgens naar de gebruiker wordt verzonden.

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

Verwerker van het berichtrelais

De verwerker van het berichtrelais ontvangt de inkomende activiteit van de kanaaladapter en voert de validatie van het activiteitenmodel uit. De relayprocessor controleert of het gesprek actief is voor de specifieke activiteit, voordat deze activiteit naar Direct Line wordt verzonden

Om op te zoeken of het gesprek actief is, houdt de relaisprocessor een verzameling actieve gesprekken bij in een woordenboek. Deze woordenlijst bevat de sleutel als gebruikers-id, waarmee de gebruiker en waarde op unieke wijze worden geïdentificeerd als een object van de volgende 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; }
}

Als het gesprek niet actief is voor de activiteit die door de relayprocessor is ontvangen, worden de volgende stappen uitgevoerd:

  1. Start een gesprek met Direct Line en slaat het gespreksobject op dat per Direct Line is verzonden bij de gebruikers-id in het woordenboek.
 /// <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. Start een nieuwe thread om de uitgaande activiteiten van de Direct Line-bot te pollen op basis van het polling-interval dat in het configuratiebestand is geconfigureerd. De polling-thread is actief totdat het einde van de gespreksactiviteit wordt ontvangen vanuit 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);
    }
}

Opmerking

De kern van de code die het bericht ontvangt, is de GetActivitiesAsync-methode die ConversationId en watermark als parameters neemt. Het doel van de watermark parameter is om de berichten op te halen die niet worden geleverd door Direct Line. Als de watermerkparameter is opgegeven, wordt het gesprek opnieuw afgespeeld vanaf het watermerk, zodat er geen berichten verloren gaan.

Stuur de activiteit naar 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);
 }

Als de conversatie actief is voor de activiteit die door de relay-processor is ontvangen, wordt de activiteit naar de berichtenrelay-processor verzonden.

Een gesprek beëindigen

Zie Een gesprek beëindigen in een directe lijn om het gesprek te beëindigen.

Markdown-indelingen in aangepaste kanalen

U kunt berichten verzenden en ontvangen die zijn opgemaakt met Markdown in aangepaste berichtenkanalen met behulp van Direct Line API 3.0. Als u begrijpt hoe de Markdown-indeling wordt doorgegeven via het kanaal en de details van de indeling weet, kunt u de HTML-stijl en tags in uw eigen gebruikersinterface bijwerken.

Wanneer een klantenservicevertegenwoordiger (servicevertegenwoordiger of vertegenwoordiger) in het Direct Line-kanaal een bericht (uitgaand) verzendt dat is opgemaakt met Markdown naar een Direct Line-bot, ontvangt de bot het bericht in een specifieke indeling. Als een bot nu een opgemaakt bericht van een klant ontvangt (binnenkomend), moet het bericht dat is opgemaakt met Markdown correct kunnen interpreteren. Als ontwikkelaar moet u Markdown op de juiste manier gebruiken, zodat het bericht correct is opgemaakt voor uw servicemedewerkers en klanten.

Meer informatie over Markdown-indelingen in Markdown-indelingen voor chatberichten.

Opmerking

  • Momenteel bieden we geen ondersteuning voor de toetsencombinatie <Shift + Enter> om meerdere regeleinden toe te voegen.
  • Voor binnenkomende berichten stelt u de Markdown-tekst in op de eigenschap van het text.
  • Voor uitgaande berichten wordt de Markdown-tekst ontvangen in de eigenschap van het text (vergelijkbaar met een normaal bericht).

Volgende stappen

Ondersteuning voor live chat en asynchrone kanalen

Aangepast berichtenkanaal configureren
MessageBird API-referentie
Best practices voor het configureren van bots