2018-02-10 02:08:12 +03:00
|
|
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
|
|
|
// Licensed under the MIT License.
|
|
|
|
|
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
2018-03-08 02:37:08 +03:00
|
|
|
|
using System.Linq;
|
2018-07-02 20:15:59 +03:00
|
|
|
|
using System.Threading;
|
2018-02-10 02:08:12 +03:00
|
|
|
|
using System.Threading.Tasks;
|
2018-03-08 02:37:08 +03:00
|
|
|
|
using Microsoft.Bot.Schema;
|
2018-02-10 02:08:12 +03:00
|
|
|
|
|
|
|
|
|
namespace Microsoft.Bot.Builder
|
|
|
|
|
{
|
2018-03-29 22:39:47 +03:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Provides context for a turn of a bot.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <remarks>Context provides information needed to process an incoming activity.
|
2018-07-03 01:13:07 +03:00
|
|
|
|
/// The context object is created by a <see cref="BotAdapter"/> and persists for the
|
2018-03-29 22:39:47 +03:00
|
|
|
|
/// length of the turn.</remarks>
|
|
|
|
|
/// <seealso cref="IBot"/>
|
|
|
|
|
/// <seealso cref="IMiddleware"/>
|
2018-04-11 23:30:52 +03:00
|
|
|
|
public class TurnContext : ITurnContext, IDisposable
|
2018-02-10 02:08:12 +03:00
|
|
|
|
{
|
2020-08-06 02:07:21 +03:00
|
|
|
|
private const string TurnLocale = "turn.locale";
|
|
|
|
|
|
2018-03-08 02:37:08 +03:00
|
|
|
|
private readonly IList<SendActivitiesHandler> _onSendActivities = new List<SendActivitiesHandler>();
|
|
|
|
|
private readonly IList<UpdateActivityHandler> _onUpdateActivity = new List<UpdateActivityHandler>();
|
|
|
|
|
private readonly IList<DeleteActivityHandler> _onDeleteActivity = new List<DeleteActivityHandler>();
|
|
|
|
|
|
2018-03-29 22:39:47 +03:00
|
|
|
|
/// <summary>
|
2018-07-03 04:47:02 +03:00
|
|
|
|
/// Initializes a new instance of the <see cref="TurnContext"/> class.
|
2018-03-29 22:39:47 +03:00
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="adapter">The adapter creating the context.</param>
|
|
|
|
|
/// <param name="activity">The incoming activity for the turn;
|
|
|
|
|
/// or <c>null</c> for a turn for a proactive message.</param>
|
|
|
|
|
/// <exception cref="ArgumentNullException"><paramref name="activity"/> or
|
|
|
|
|
/// <paramref name="adapter"/> is <c>null</c>.</exception>
|
|
|
|
|
/// <remarks>For use by bot adapter implementations only.</remarks>
|
2018-03-23 00:20:04 +03:00
|
|
|
|
public TurnContext(BotAdapter adapter, Activity activity)
|
2018-02-10 02:08:12 +03:00
|
|
|
|
{
|
2018-06-28 01:33:22 +03:00
|
|
|
|
Adapter = adapter ?? throw new ArgumentNullException(nameof(adapter));
|
|
|
|
|
Activity = activity ?? throw new ArgumentNullException(nameof(activity));
|
2018-03-08 02:37:08 +03:00
|
|
|
|
}
|
2018-02-10 02:08:12 +03:00
|
|
|
|
|
2020-08-28 02:08:06 +03:00
|
|
|
|
/// <summary>
|
2020-09-04 03:56:15 +03:00
|
|
|
|
/// Initializes a new instance of the <see cref="TurnContext"/> class from another turncontext class to target an alternate Activity.
|
2020-08-28 02:08:06 +03:00
|
|
|
|
/// </summary>
|
2020-09-04 03:56:15 +03:00
|
|
|
|
/// <remarks>
|
|
|
|
|
/// For supporting calling legacy systems that always assume turncontext.Activity is the activity should be processed.
|
|
|
|
|
/// This class clones the turncontext and then replaces the original.activity with the passed in activity.
|
|
|
|
|
/// </remarks>
|
2020-08-28 02:08:06 +03:00
|
|
|
|
/// <param name="turnContext">context to clone.</param>
|
|
|
|
|
/// <param name="activity">activity to put into the new turn context.</param>
|
|
|
|
|
public TurnContext(ITurnContext turnContext, Activity activity)
|
|
|
|
|
{
|
|
|
|
|
if (turnContext == null)
|
|
|
|
|
{
|
|
|
|
|
throw new ArgumentNullException(nameof(turnContext));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Activity = activity ?? throw new ArgumentNullException(nameof(activity));
|
|
|
|
|
|
|
|
|
|
// all properties should be copied over except for activity.
|
|
|
|
|
Adapter = turnContext.Adapter;
|
|
|
|
|
TurnState = turnContext.TurnState;
|
|
|
|
|
Responded = turnContext.Responded;
|
|
|
|
|
|
|
|
|
|
if (turnContext is TurnContext tc)
|
|
|
|
|
{
|
|
|
|
|
BufferedReplyActivities = tc.BufferedReplyActivities;
|
|
|
|
|
|
|
|
|
|
// keep private middelware pipeline hooks.
|
|
|
|
|
_onSendActivities = tc._onSendActivities;
|
|
|
|
|
_onUpdateActivity = tc._onUpdateActivity;
|
|
|
|
|
_onDeleteActivity = tc._onDeleteActivity;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-04 01:52:45 +03:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets the bot adapter that created this context object.
|
|
|
|
|
/// </summary>
|
2018-07-06 21:02:43 +03:00
|
|
|
|
/// <value>The bot adapter that created this context object.</value>
|
2018-07-04 01:52:45 +03:00
|
|
|
|
public BotAdapter Adapter { get; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets the services registered on this context object.
|
|
|
|
|
/// </summary>
|
2018-07-06 21:02:43 +03:00
|
|
|
|
/// <value>The services registered on this context object.</value>
|
2018-08-17 08:50:34 +03:00
|
|
|
|
public TurnContextStateCollection TurnState { get; } = new TurnContextStateCollection();
|
2018-07-04 01:52:45 +03:00
|
|
|
|
|
2020-08-06 02:07:21 +03:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets or sets the locale on this context object.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <value>The string of locale on this context object.</value>
|
|
|
|
|
public string Locale
|
|
|
|
|
{
|
|
|
|
|
get => this.TurnState.Get<string>(TurnLocale);
|
|
|
|
|
set { this.TurnState.Set(TurnLocale, value); }
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-04 01:52:45 +03:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets the activity associated with this turn; or <c>null</c> when processing
|
|
|
|
|
/// a proactive message.
|
|
|
|
|
/// </summary>
|
2018-07-06 21:02:43 +03:00
|
|
|
|
/// <value>The activity associated with this turn.</value>
|
2018-07-04 01:52:45 +03:00
|
|
|
|
public Activity Activity { get; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2018-07-06 21:02:43 +03:00
|
|
|
|
/// Gets a value indicating whether at least one response was sent for the current turn.
|
2018-07-04 01:52:45 +03:00
|
|
|
|
/// </summary>
|
|
|
|
|
/// <value><c>true</c> if at least one response was sent for the current turn.</value>
|
2018-07-06 21:02:43 +03:00
|
|
|
|
/// <remarks><see cref="ITraceActivity"/> activities on their own do not set this flag.</remarks>
|
2018-07-04 01:52:45 +03:00
|
|
|
|
public bool Responded
|
|
|
|
|
{
|
|
|
|
|
get;
|
|
|
|
|
private set;
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-27 03:53:15 +03:00
|
|
|
|
/// <summary>
|
2020-03-07 05:55:30 +03:00
|
|
|
|
/// Gets a list of activities to send when `context.Activity.DeliveryMode == 'expectReplies'.
|
2020-02-27 03:53:15 +03:00
|
|
|
|
/// </summary>
|
2020-03-20 23:53:46 +03:00
|
|
|
|
/// <value>A list of activities.</value>
|
2020-03-07 06:12:16 +03:00
|
|
|
|
public List<Activity> BufferedReplyActivities { get; } = new List<Activity>();
|
2020-02-27 03:53:15 +03:00
|
|
|
|
|
2018-03-29 22:39:47 +03:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Adds a response handler for send activity operations.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="handler">The handler to add to the context object.</param>
|
|
|
|
|
/// <returns>The updated context object.</returns>
|
|
|
|
|
/// <exception cref="ArgumentNullException"><paramref name="handler"/> is <c>null</c>.</exception>
|
2018-07-06 21:02:43 +03:00
|
|
|
|
/// <remarks>When the context's <see cref="SendActivityAsync(IActivity, CancellationToken)"/>
|
|
|
|
|
/// or <see cref="SendActivitiesAsync(IActivity[], CancellationToken)"/> methods are called,
|
2018-07-03 01:13:07 +03:00
|
|
|
|
/// the adapter calls the registered handlers in the order in which they were
|
2018-03-29 22:39:47 +03:00
|
|
|
|
/// added to the context object.
|
|
|
|
|
/// </remarks>
|
2018-03-21 03:17:11 +03:00
|
|
|
|
public ITurnContext OnSendActivities(SendActivitiesHandler handler)
|
2018-03-08 04:44:26 +03:00
|
|
|
|
{
|
|
|
|
|
if (handler == null)
|
2018-07-03 04:47:02 +03:00
|
|
|
|
{
|
2018-03-08 04:44:26 +03:00
|
|
|
|
throw new ArgumentNullException(nameof(handler));
|
2018-07-03 04:47:02 +03:00
|
|
|
|
}
|
2018-03-08 04:44:26 +03:00
|
|
|
|
|
2018-03-09 05:15:47 +03:00
|
|
|
|
_onSendActivities.Add(handler);
|
2018-03-08 04:44:26 +03:00
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-29 22:39:47 +03:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Adds a response handler for update activity operations.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="handler">The handler to add to the context object.</param>
|
|
|
|
|
/// <returns>The updated context object.</returns>
|
|
|
|
|
/// <exception cref="ArgumentNullException"><paramref name="handler"/> is <c>null</c>.</exception>
|
2018-07-06 21:02:43 +03:00
|
|
|
|
/// <remarks>When the context's <see cref="UpdateActivityAsync(IActivity, CancellationToken)"/> is called,
|
2018-07-03 01:13:07 +03:00
|
|
|
|
/// the adapter calls the registered handlers in the order in which they were
|
2018-03-29 22:39:47 +03:00
|
|
|
|
/// added to the context object.
|
|
|
|
|
/// </remarks>
|
2018-03-21 03:17:11 +03:00
|
|
|
|
public ITurnContext OnUpdateActivity(UpdateActivityHandler handler)
|
2018-03-08 04:44:26 +03:00
|
|
|
|
{
|
|
|
|
|
if (handler == null)
|
2018-07-03 04:47:02 +03:00
|
|
|
|
{
|
2018-03-08 04:44:26 +03:00
|
|
|
|
throw new ArgumentNullException(nameof(handler));
|
2018-07-03 04:47:02 +03:00
|
|
|
|
}
|
2018-03-08 04:44:26 +03:00
|
|
|
|
|
|
|
|
|
_onUpdateActivity.Add(handler);
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-29 22:39:47 +03:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Adds a response handler for delete activity operations.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="handler">The handler to add to the context object.</param>
|
|
|
|
|
/// <returns>The updated context object.</returns>
|
|
|
|
|
/// <exception cref="ArgumentNullException"><paramref name="handler"/> is <c>null</c>.</exception>
|
2018-07-06 21:02:43 +03:00
|
|
|
|
/// <remarks>When the context's <see cref="DeleteActivityAsync(ConversationReference, CancellationToken)"/>
|
|
|
|
|
/// or <see cref="DeleteActivityAsync(string, CancellationToken)"/> is called,
|
2018-07-03 01:13:07 +03:00
|
|
|
|
/// the adapter calls the registered handlers in the order in which they were
|
2018-03-29 22:39:47 +03:00
|
|
|
|
/// added to the context object.
|
|
|
|
|
/// </remarks>
|
2018-03-21 03:17:11 +03:00
|
|
|
|
public ITurnContext OnDeleteActivity(DeleteActivityHandler handler)
|
2018-03-08 04:44:26 +03:00
|
|
|
|
{
|
|
|
|
|
if (handler == null)
|
2018-07-03 04:47:02 +03:00
|
|
|
|
{
|
2018-03-08 04:44:26 +03:00
|
|
|
|
throw new ArgumentNullException(nameof(handler));
|
2018-07-03 04:47:02 +03:00
|
|
|
|
}
|
2018-03-08 04:44:26 +03:00
|
|
|
|
|
|
|
|
|
_onDeleteActivity.Add(handler);
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-29 22:39:47 +03:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Sends a message activity to the sender of the incoming activity.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="textReplyToSend">The text of the message to send.</param>
|
2018-07-03 01:13:07 +03:00
|
|
|
|
/// <param name="speak">Optional, text to be spoken by your bot on a speech-enabled
|
2018-04-02 18:57:04 +03:00
|
|
|
|
/// channel.</param>
|
2018-07-03 01:13:07 +03:00
|
|
|
|
/// <param name="inputHint">Optional, indicates whether your bot is accepting,
|
2018-04-02 18:57:04 +03:00
|
|
|
|
/// expecting, or ignoring user input after the message is delivered to the client.
|
|
|
|
|
/// One of: "acceptingInput", "ignoringInput", or "expectingInput".
|
|
|
|
|
/// Default is null.</param>
|
2018-07-03 01:13:07 +03:00
|
|
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
2018-03-29 22:39:47 +03:00
|
|
|
|
/// <returns>A task that represents the work queued to execute.</returns>
|
|
|
|
|
/// <exception cref="ArgumentNullException">
|
|
|
|
|
/// <paramref name="textReplyToSend"/> is <c>null</c> or whitespace.</exception>
|
|
|
|
|
/// <remarks>If the activity is successfully sent, the task result contains
|
2018-07-03 01:13:07 +03:00
|
|
|
|
/// a <see cref="ResourceResponse"/> object containing the ID that the receiving
|
2018-04-02 18:57:04 +03:00
|
|
|
|
/// channel assigned to the activity.
|
2018-07-03 01:13:07 +03:00
|
|
|
|
/// <para>See the channel's documentation for limits imposed upon the contents of
|
2018-04-02 18:57:04 +03:00
|
|
|
|
/// <paramref name="textReplyToSend"/>.</para>
|
2018-07-03 01:13:07 +03:00
|
|
|
|
/// <para>To control various characteristics of your bot's speech such as voice,
|
|
|
|
|
/// rate, volume, pronunciation, and pitch, specify <paramref name="speak"/> in
|
2018-04-02 18:57:04 +03:00
|
|
|
|
/// Speech Synthesis Markup Language (SSML) format.</para>
|
|
|
|
|
/// </remarks>
|
2018-07-04 01:52:45 +03:00
|
|
|
|
public async Task<ResourceResponse> SendActivityAsync(string textReplyToSend, string speak = null, string inputHint = null, CancellationToken cancellationToken = default(CancellationToken))
|
2018-03-09 05:15:47 +03:00
|
|
|
|
{
|
2018-03-20 01:53:56 +03:00
|
|
|
|
if (string.IsNullOrWhiteSpace(textReplyToSend))
|
2018-07-03 04:47:02 +03:00
|
|
|
|
{
|
2018-03-29 22:39:47 +03:00
|
|
|
|
throw new ArgumentNullException(nameof(textReplyToSend));
|
2018-07-03 04:47:02 +03:00
|
|
|
|
}
|
2018-03-29 22:39:47 +03:00
|
|
|
|
|
2018-03-20 01:53:56 +03:00
|
|
|
|
var activityToSend = new Activity(ActivityTypes.Message) { Text = textReplyToSend };
|
|
|
|
|
|
2018-03-30 01:51:05 +03:00
|
|
|
|
if (!string.IsNullOrEmpty(speak))
|
2018-07-03 04:47:02 +03:00
|
|
|
|
{
|
2018-03-30 01:51:05 +03:00
|
|
|
|
activityToSend.Speak = speak;
|
2018-07-03 04:47:02 +03:00
|
|
|
|
}
|
2018-03-30 01:51:05 +03:00
|
|
|
|
|
|
|
|
|
if (!string.IsNullOrEmpty(inputHint))
|
2018-07-03 04:47:02 +03:00
|
|
|
|
{
|
2018-03-30 01:51:05 +03:00
|
|
|
|
activityToSend.InputHint = inputHint;
|
2018-07-03 04:47:02 +03:00
|
|
|
|
}
|
2018-03-30 01:51:05 +03:00
|
|
|
|
|
2018-07-04 01:52:45 +03:00
|
|
|
|
return await SendActivityAsync(activityToSend, cancellationToken).ConfigureAwait(false);
|
2018-03-20 01:53:56 +03:00
|
|
|
|
}
|
|
|
|
|
|
2018-03-29 22:39:47 +03:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Sends an activity to the sender of the incoming activity.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="activity">The activity to send.</param>
|
2018-07-03 04:47:02 +03:00
|
|
|
|
/// <param name="cancellationToken">Cancellation token.</param>
|
2018-03-29 22:39:47 +03:00
|
|
|
|
/// <returns>A task that represents the work queued to execute.</returns>
|
|
|
|
|
/// <exception cref="ArgumentNullException"><paramref name="activity"/> is <c>null</c>.</exception>
|
|
|
|
|
/// <remarks>If the activity is successfully sent, the task result contains
|
2018-07-03 01:13:07 +03:00
|
|
|
|
/// a <see cref="ResourceResponse"/> object containing the ID that the receiving
|
2018-03-29 22:39:47 +03:00
|
|
|
|
/// channel assigned to the activity.</remarks>
|
2018-07-04 01:52:45 +03:00
|
|
|
|
public async Task<ResourceResponse> SendActivityAsync(IActivity activity, CancellationToken cancellationToken = default(CancellationToken))
|
2018-03-20 01:53:56 +03:00
|
|
|
|
{
|
2018-06-29 01:20:11 +03:00
|
|
|
|
BotAssert.ActivityNotNull(activity);
|
2018-03-20 01:53:56 +03:00
|
|
|
|
|
2018-07-04 01:52:45 +03:00
|
|
|
|
ResourceResponse[] responses = await SendActivitiesAsync(new[] { activity }, cancellationToken).ConfigureAwait(false);
|
2018-03-20 01:53:56 +03:00
|
|
|
|
if (responses == null || responses.Length == 0)
|
2018-03-09 05:15:47 +03:00
|
|
|
|
{
|
2018-07-03 01:13:07 +03:00
|
|
|
|
// It's possible an interceptor prevented the activity from having been sent.
|
|
|
|
|
// Just return an empty response in that case.
|
2018-03-20 01:53:56 +03:00
|
|
|
|
return new ResourceResponse();
|
2018-03-09 05:15:47 +03:00
|
|
|
|
}
|
2018-03-20 01:53:56 +03:00
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return responses[0];
|
2018-03-29 22:39:47 +03:00
|
|
|
|
}
|
2018-03-09 05:15:47 +03:00
|
|
|
|
}
|
|
|
|
|
|
2018-03-29 22:39:47 +03:00
|
|
|
|
/// <summary>
|
2018-04-02 18:57:04 +03:00
|
|
|
|
/// Sends a set of activities to the sender of the incoming activity.
|
2018-03-29 22:39:47 +03:00
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="activities">The activities to send.</param>
|
2018-07-03 04:47:02 +03:00
|
|
|
|
/// <param name="cancellationToken">Cancellation token.</param>
|
2018-03-29 22:39:47 +03:00
|
|
|
|
/// <returns>A task that represents the work queued to execute.</returns>
|
|
|
|
|
/// <remarks>If the activities are successfully sent, the task result contains
|
2018-07-03 01:13:07 +03:00
|
|
|
|
/// an array of <see cref="ResourceResponse"/> objects containing the IDs that
|
2018-03-29 22:39:47 +03:00
|
|
|
|
/// the receiving channel assigned to the activities.</remarks>
|
2018-07-04 01:52:45 +03:00
|
|
|
|
public Task<ResourceResponse[]> SendActivitiesAsync(IActivity[] activities, CancellationToken cancellationToken = default(CancellationToken))
|
2018-02-10 02:08:12 +03:00
|
|
|
|
{
|
2018-05-26 00:40:32 +03:00
|
|
|
|
if (activities == null)
|
2018-07-03 04:47:02 +03:00
|
|
|
|
{
|
2018-05-26 00:40:32 +03:00
|
|
|
|
throw new ArgumentNullException(nameof(activities));
|
2018-07-03 04:47:02 +03:00
|
|
|
|
}
|
2018-05-26 00:40:32 +03:00
|
|
|
|
|
|
|
|
|
if (activities.Length == 0)
|
2018-07-03 04:47:02 +03:00
|
|
|
|
{
|
2018-05-26 00:40:32 +03:00
|
|
|
|
throw new ArgumentException("Expecting one or more activities, but the array was empty.", nameof(activities));
|
2018-07-03 04:47:02 +03:00
|
|
|
|
}
|
2018-05-26 00:40:32 +03:00
|
|
|
|
|
2018-06-28 03:25:09 +03:00
|
|
|
|
var conversationReference = this.Activity.GetConversationReference();
|
2018-05-24 00:50:35 +03:00
|
|
|
|
|
2018-05-24 23:39:04 +03:00
|
|
|
|
var bufferedActivities = new List<Activity>(activities.Length);
|
2018-05-24 00:50:35 +03:00
|
|
|
|
|
2018-05-24 23:39:04 +03:00
|
|
|
|
for (var index = 0; index < activities.Length; index++)
|
2018-03-29 22:39:47 +03:00
|
|
|
|
{
|
2018-05-24 23:39:04 +03:00
|
|
|
|
// Buffer the incoming activities into a List<T> since we allow the set to be manipulated by the callbacks
|
2018-07-03 01:13:07 +03:00
|
|
|
|
// Bind the relevant Conversation Reference properties, such as URLs and
|
2018-05-24 00:50:35 +03:00
|
|
|
|
// ChannelId's, to the activity we're about to send
|
2018-06-28 03:25:09 +03:00
|
|
|
|
bufferedActivities.Add(activities[index].ApplyConversationReference(conversationReference));
|
2018-03-08 02:37:08 +03:00
|
|
|
|
}
|
|
|
|
|
|
2018-05-24 00:50:35 +03:00
|
|
|
|
// If there are no callbacks registered, bypass the overhead of invoking them and send directly to the adapter
|
|
|
|
|
if (_onSendActivities.Count == 0)
|
|
|
|
|
{
|
|
|
|
|
return SendActivitiesThroughAdapter();
|
|
|
|
|
}
|
2018-03-13 21:40:33 +03:00
|
|
|
|
|
2018-05-24 00:50:35 +03:00
|
|
|
|
// Send through the full callback pipeline
|
|
|
|
|
return SendActivitiesThroughCallbackPipeline();
|
2018-03-08 02:37:08 +03:00
|
|
|
|
|
2018-05-24 00:50:35 +03:00
|
|
|
|
Task<ResourceResponse[]> SendActivitiesThroughCallbackPipeline(int nextCallbackIndex = 0)
|
2018-03-08 02:37:08 +03:00
|
|
|
|
{
|
2018-05-24 00:50:35 +03:00
|
|
|
|
// If we've executed the last callback, we now send straight to the adapter
|
|
|
|
|
if (nextCallbackIndex == _onSendActivities.Count)
|
2018-04-14 01:09:44 +03:00
|
|
|
|
{
|
2018-05-24 00:50:35 +03:00
|
|
|
|
return SendActivitiesThroughAdapter();
|
2018-04-14 01:09:44 +03:00
|
|
|
|
}
|
|
|
|
|
|
2018-05-24 00:50:35 +03:00
|
|
|
|
return _onSendActivities[nextCallbackIndex].Invoke(this, bufferedActivities, () => SendActivitiesThroughCallbackPipeline(nextCallbackIndex + 1));
|
|
|
|
|
}
|
2018-03-08 02:37:08 +03:00
|
|
|
|
|
2018-05-24 00:50:35 +03:00
|
|
|
|
async Task<ResourceResponse[]> SendActivitiesThroughAdapter()
|
|
|
|
|
{
|
2020-03-07 05:55:30 +03:00
|
|
|
|
if (Activity.DeliveryMode == DeliveryModes.ExpectReplies)
|
2018-04-11 23:31:44 +03:00
|
|
|
|
{
|
2020-02-27 03:53:15 +03:00
|
|
|
|
var responses = new ResourceResponse[bufferedActivities.Count];
|
|
|
|
|
var sentNonTraceActivity = false;
|
2018-05-24 03:02:07 +03:00
|
|
|
|
|
2020-02-27 03:53:15 +03:00
|
|
|
|
for (var index = 0; index < responses.Length; index++)
|
|
|
|
|
{
|
|
|
|
|
var activity = bufferedActivities[index];
|
2020-03-07 06:12:16 +03:00
|
|
|
|
BufferedReplyActivities.Add(activity);
|
2020-10-26 22:51:52 +03:00
|
|
|
|
|
|
|
|
|
// Ensure the TurnState has the InvokeResponseKey, since this activity
|
|
|
|
|
// is not being sent through the adapter, where it would be added to TurnState.
|
|
|
|
|
if (activity.Type == ActivityTypesEx.InvokeResponse)
|
|
|
|
|
{
|
|
|
|
|
TurnState.Add(BotFrameworkAdapter.InvokeResponseKey, activity);
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-27 03:53:15 +03:00
|
|
|
|
responses[index] = new ResourceResponse();
|
2018-05-24 00:50:35 +03:00
|
|
|
|
|
2020-02-27 03:53:15 +03:00
|
|
|
|
sentNonTraceActivity |= activity.Type != ActivityTypes.Trace;
|
|
|
|
|
}
|
2018-03-08 02:37:08 +03:00
|
|
|
|
|
2020-02-27 03:53:15 +03:00
|
|
|
|
if (sentNonTraceActivity)
|
|
|
|
|
{
|
|
|
|
|
Responded = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return responses;
|
2018-04-14 01:09:44 +03:00
|
|
|
|
}
|
2020-02-27 03:53:15 +03:00
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Send from the list which may have been manipulated via the event handlers.
|
|
|
|
|
// Note that 'responses' was captured from the root of the call, and will be
|
|
|
|
|
// returned to the original caller.
|
|
|
|
|
var responses = await Adapter.SendActivitiesAsync(this, bufferedActivities.ToArray(), cancellationToken).ConfigureAwait(false);
|
|
|
|
|
var sentNonTraceActivity = false;
|
|
|
|
|
|
|
|
|
|
for (var index = 0; index < responses.Length; index++)
|
|
|
|
|
{
|
|
|
|
|
var activity = bufferedActivities[index];
|
|
|
|
|
|
|
|
|
|
activity.Id = responses[index].Id;
|
2018-04-14 01:09:44 +03:00
|
|
|
|
|
2020-02-27 03:53:15 +03:00
|
|
|
|
sentNonTraceActivity |= activity.Type != ActivityTypes.Trace;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (sentNonTraceActivity)
|
|
|
|
|
{
|
|
|
|
|
Responded = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return responses;
|
|
|
|
|
}
|
2018-03-08 02:37:08 +03:00
|
|
|
|
}
|
2018-02-10 02:08:12 +03:00
|
|
|
|
}
|
|
|
|
|
|
2018-03-08 02:37:08 +03:00
|
|
|
|
/// <summary>
|
2018-07-03 01:13:07 +03:00
|
|
|
|
/// Replaces an existing activity.
|
2018-03-08 02:37:08 +03:00
|
|
|
|
/// </summary>
|
2018-07-03 01:13:07 +03:00
|
|
|
|
/// <param name="activity">New replacement activity.</param>
|
2018-07-03 04:47:02 +03:00
|
|
|
|
/// <param name="cancellationToken">Cancellation token.</param>
|
2018-03-29 22:39:47 +03:00
|
|
|
|
/// <returns>A task that represents the work queued to execute.</returns>
|
|
|
|
|
/// <exception cref="Microsoft.Bot.Schema.ErrorResponseException">
|
|
|
|
|
/// The HTTP operation failed and the response contained additional information.</exception>
|
|
|
|
|
/// <exception cref="System.AggregateException">
|
|
|
|
|
/// One or more exceptions occurred during the operation.</exception>
|
|
|
|
|
/// <remarks>If the activity is successfully sent, the task result contains
|
2018-07-03 01:13:07 +03:00
|
|
|
|
/// a <see cref="ResourceResponse"/> object containing the ID that the receiving
|
2018-03-29 22:39:47 +03:00
|
|
|
|
/// channel assigned to the activity.
|
|
|
|
|
/// <para>Before calling this, set the ID of the replacement activity to the ID
|
|
|
|
|
/// of the activity to replace.</para></remarks>
|
2019-09-18 04:03:41 +03:00
|
|
|
|
public async Task<ResourceResponse> UpdateActivityAsync(IActivity activity, CancellationToken cancellationToken = default)
|
2018-03-08 02:37:08 +03:00
|
|
|
|
{
|
2019-09-18 04:03:41 +03:00
|
|
|
|
BotAssert.ActivityNotNull(activity);
|
|
|
|
|
|
|
|
|
|
var conversationReference = Activity.GetConversationReference();
|
|
|
|
|
var a = activity.ApplyConversationReference(conversationReference);
|
2018-03-13 21:40:33 +03:00
|
|
|
|
|
2018-04-11 23:31:44 +03:00
|
|
|
|
async Task<ResourceResponse> ActuallyUpdateStuff()
|
2018-03-08 02:37:08 +03:00
|
|
|
|
{
|
2018-07-04 01:52:45 +03:00
|
|
|
|
return await Adapter.UpdateActivityAsync(this, a, cancellationToken).ConfigureAwait(false);
|
2018-03-08 02:37:08 +03:00
|
|
|
|
}
|
|
|
|
|
|
2018-07-04 01:52:45 +03:00
|
|
|
|
return await UpdateActivityInternalAsync(a, _onUpdateActivity, ActuallyUpdateStuff, cancellationToken).ConfigureAwait(false);
|
2018-03-08 02:37:08 +03:00
|
|
|
|
}
|
2018-02-10 02:08:12 +03:00
|
|
|
|
|
2018-03-29 22:39:47 +03:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Deletes an existing activity.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="activityId">The ID of the activity to delete.</param>
|
2018-07-03 04:47:02 +03:00
|
|
|
|
/// <param name="cancellationToken">Cancellation token.</param>
|
2018-03-29 22:39:47 +03:00
|
|
|
|
/// <returns>A task that represents the work queued to execute.</returns>
|
|
|
|
|
/// <exception cref="Microsoft.Bot.Schema.ErrorResponseException">
|
|
|
|
|
/// The HTTP operation failed and the response contained additional information.</exception>
|
2018-07-04 01:52:45 +03:00
|
|
|
|
public async Task DeleteActivityAsync(string activityId, CancellationToken cancellationToken = default(CancellationToken))
|
2018-03-08 02:37:08 +03:00
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrWhiteSpace(activityId))
|
2018-07-03 04:47:02 +03:00
|
|
|
|
{
|
2018-03-08 02:37:08 +03:00
|
|
|
|
throw new ArgumentNullException(nameof(activityId));
|
2018-07-03 04:47:02 +03:00
|
|
|
|
}
|
2018-03-08 02:37:08 +03:00
|
|
|
|
|
2018-07-03 04:47:02 +03:00
|
|
|
|
var cr = Activity.GetConversationReference();
|
2018-03-08 02:37:08 +03:00
|
|
|
|
cr.ActivityId = activityId;
|
|
|
|
|
|
|
|
|
|
async Task ActuallyDeleteStuff()
|
|
|
|
|
{
|
2018-07-04 01:52:45 +03:00
|
|
|
|
await Adapter.DeleteActivityAsync(this, cr, cancellationToken).ConfigureAwait(false);
|
2018-03-08 02:37:08 +03:00
|
|
|
|
}
|
2018-02-10 02:08:12 +03:00
|
|
|
|
|
2018-07-04 01:52:45 +03:00
|
|
|
|
await DeleteActivityInternalAsync(cr, _onDeleteActivity, ActuallyDeleteStuff, cancellationToken).ConfigureAwait(false);
|
2018-03-08 02:37:08 +03:00
|
|
|
|
}
|
2018-02-10 02:08:12 +03:00
|
|
|
|
|
2018-04-02 18:57:04 +03:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Deletes an existing activity.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="conversationReference">The conversation containing the activity to delete.</param>
|
2018-07-03 04:47:02 +03:00
|
|
|
|
/// <param name="cancellationToken">Cancellation token.</param>
|
2018-04-02 18:57:04 +03:00
|
|
|
|
/// <returns>A task that represents the work queued to execute.</returns>
|
|
|
|
|
/// <exception cref="Microsoft.Bot.Schema.ErrorResponseException">
|
|
|
|
|
/// The HTTP operation failed and the response contained additional information.</exception>
|
2018-07-03 01:13:07 +03:00
|
|
|
|
/// <remarks>The conversation reference's <see cref="ConversationReference.ActivityId"/>
|
2018-04-02 18:57:04 +03:00
|
|
|
|
/// indicates the activity in the conversation to delete.</remarks>
|
2018-07-04 01:52:45 +03:00
|
|
|
|
public async Task DeleteActivityAsync(ConversationReference conversationReference, CancellationToken cancellationToken = default(CancellationToken))
|
2018-03-30 02:41:59 +03:00
|
|
|
|
{
|
|
|
|
|
if (conversationReference == null)
|
2018-07-03 04:47:02 +03:00
|
|
|
|
{
|
2018-03-30 02:41:59 +03:00
|
|
|
|
throw new ArgumentNullException(nameof(conversationReference));
|
2018-07-03 04:47:02 +03:00
|
|
|
|
}
|
2018-04-11 23:31:44 +03:00
|
|
|
|
|
2018-03-30 02:41:59 +03:00
|
|
|
|
async Task ActuallyDeleteStuff()
|
|
|
|
|
{
|
2018-07-04 01:52:45 +03:00
|
|
|
|
await Adapter.DeleteActivityAsync(this, conversationReference, cancellationToken).ConfigureAwait(false);
|
2018-03-30 02:41:59 +03:00
|
|
|
|
}
|
|
|
|
|
|
2018-07-04 01:52:45 +03:00
|
|
|
|
await DeleteActivityInternalAsync(conversationReference, _onDeleteActivity, ActuallyDeleteStuff, cancellationToken).ConfigureAwait(false);
|
2018-03-30 02:41:59 +03:00
|
|
|
|
}
|
|
|
|
|
|
2018-07-13 00:47:57 +03:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Frees resources.
|
|
|
|
|
/// </summary>
|
2018-07-03 01:13:07 +03:00
|
|
|
|
public void Dispose()
|
|
|
|
|
{
|
2020-06-25 06:02:33 +03:00
|
|
|
|
Dispose(true);
|
|
|
|
|
GC.SuppressFinalize(this);
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-25 03:11:18 +03:00
|
|
|
|
/// <summary>
|
2020-07-25 04:45:44 +03:00
|
|
|
|
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
2020-07-25 03:11:18 +03:00
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="disposing">Boolean value that determines whether to free resources or not.</param>
|
2020-06-25 06:02:33 +03:00
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
|
|
|
{
|
|
|
|
|
if (disposing)
|
|
|
|
|
{
|
|
|
|
|
// Dispose any disposable objects owned by the class here.
|
|
|
|
|
}
|
2018-07-03 01:13:07 +03:00
|
|
|
|
}
|
|
|
|
|
|
2018-07-04 01:52:45 +03:00
|
|
|
|
private async Task<ResourceResponse> UpdateActivityInternalAsync(
|
2018-07-03 01:13:07 +03:00
|
|
|
|
Activity activity,
|
2018-03-08 02:37:08 +03:00
|
|
|
|
IEnumerable<UpdateActivityHandler> updateHandlers,
|
2018-07-02 20:15:59 +03:00
|
|
|
|
Func<Task<ResourceResponse>> callAtBottom,
|
|
|
|
|
CancellationToken cancellationToken)
|
2018-02-25 21:44:04 +03:00
|
|
|
|
{
|
|
|
|
|
BotAssert.ActivityNotNull(activity);
|
2018-03-08 02:37:08 +03:00
|
|
|
|
if (updateHandlers == null)
|
2018-07-03 04:47:02 +03:00
|
|
|
|
{
|
2020-06-25 06:02:33 +03:00
|
|
|
|
throw new ArgumentException($"{nameof(updateHandlers)} is null.", nameof(updateHandlers));
|
2018-07-03 04:47:02 +03:00
|
|
|
|
}
|
2018-03-08 02:37:08 +03:00
|
|
|
|
|
2018-07-03 04:47:02 +03:00
|
|
|
|
// No middleware to run.
|
2020-06-25 06:02:33 +03:00
|
|
|
|
if (!updateHandlers.Any())
|
2018-03-08 02:37:08 +03:00
|
|
|
|
{
|
|
|
|
|
if (callAtBottom != null)
|
|
|
|
|
{
|
2018-06-29 01:20:11 +03:00
|
|
|
|
return await callAtBottom().ConfigureAwait(false);
|
2018-03-08 02:37:08 +03:00
|
|
|
|
}
|
|
|
|
|
|
2018-04-11 23:31:44 +03:00
|
|
|
|
return null;
|
2018-03-08 02:37:08 +03:00
|
|
|
|
}
|
|
|
|
|
|
2018-03-29 22:39:47 +03:00
|
|
|
|
// Default to "No more Middleware after this".
|
2018-07-04 01:52:45 +03:00
|
|
|
|
async Task<ResourceResponse> Next()
|
2018-03-08 02:37:08 +03:00
|
|
|
|
{
|
|
|
|
|
// Remove the first item from the list of middleware to call,
|
2018-07-03 01:13:07 +03:00
|
|
|
|
// so that the next call just has the remaining items to worry about.
|
2018-03-08 02:37:08 +03:00
|
|
|
|
IEnumerable<UpdateActivityHandler> remaining = updateHandlers.Skip(1);
|
2018-07-04 01:52:45 +03:00
|
|
|
|
var result = await UpdateActivityInternalAsync(activity, remaining, callAtBottom, cancellationToken).ConfigureAwait(false);
|
2018-04-11 23:31:44 +03:00
|
|
|
|
activity.Id = result.Id;
|
|
|
|
|
return result;
|
2018-03-08 02:37:08 +03:00
|
|
|
|
}
|
|
|
|
|
|
2018-07-03 01:13:07 +03:00
|
|
|
|
// Grab the current middleware, which is the 1st element in the array, and execute it
|
2018-03-08 02:37:08 +03:00
|
|
|
|
UpdateActivityHandler toCall = updateHandlers.First();
|
2018-07-04 01:52:45 +03:00
|
|
|
|
return await toCall(this, activity, Next).ConfigureAwait(false);
|
2018-03-08 02:37:08 +03:00
|
|
|
|
}
|
|
|
|
|
|
2018-07-04 01:52:45 +03:00
|
|
|
|
private async Task DeleteActivityInternalAsync(
|
2018-07-03 01:13:07 +03:00
|
|
|
|
ConversationReference cr,
|
2020-04-22 02:31:27 +03:00
|
|
|
|
IEnumerable<DeleteActivityHandler> deleteHandlers,
|
2018-07-03 01:13:07 +03:00
|
|
|
|
Func<Task> callAtBottom,
|
|
|
|
|
CancellationToken cancellationToken)
|
2018-03-08 02:37:08 +03:00
|
|
|
|
{
|
|
|
|
|
BotAssert.ConversationReferenceNotNull(cr);
|
2018-06-29 01:20:11 +03:00
|
|
|
|
|
2020-04-22 02:31:27 +03:00
|
|
|
|
if (deleteHandlers == null)
|
2018-07-03 04:47:02 +03:00
|
|
|
|
{
|
2020-06-25 06:02:33 +03:00
|
|
|
|
throw new ArgumentException($"{nameof(deleteHandlers)} is null", nameof(deleteHandlers));
|
2018-07-03 04:47:02 +03:00
|
|
|
|
}
|
2018-03-08 02:37:08 +03:00
|
|
|
|
|
2018-07-03 04:47:02 +03:00
|
|
|
|
// No middleware to run.
|
2020-06-25 06:02:33 +03:00
|
|
|
|
if (!deleteHandlers.Any())
|
2018-03-08 02:37:08 +03:00
|
|
|
|
{
|
|
|
|
|
if (callAtBottom != null)
|
|
|
|
|
{
|
2018-06-29 01:20:11 +03:00
|
|
|
|
await callAtBottom().ConfigureAwait(false);
|
2018-03-08 02:37:08 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-29 22:39:47 +03:00
|
|
|
|
// Default to "No more Middleware after this".
|
2018-07-04 01:52:45 +03:00
|
|
|
|
async Task Next()
|
2018-03-08 02:37:08 +03:00
|
|
|
|
{
|
|
|
|
|
// Remove the first item from the list of middleware to call,
|
2018-07-03 01:13:07 +03:00
|
|
|
|
// so that the next call just has the remaining items to worry about.
|
2020-04-22 02:31:27 +03:00
|
|
|
|
IEnumerable<DeleteActivityHandler> remaining = deleteHandlers.Skip(1);
|
2018-07-04 01:52:45 +03:00
|
|
|
|
await DeleteActivityInternalAsync(cr, remaining, callAtBottom, cancellationToken).ConfigureAwait(false);
|
2018-03-08 02:37:08 +03:00
|
|
|
|
}
|
|
|
|
|
|
2018-03-29 22:39:47 +03:00
|
|
|
|
// Grab the current middleware, which is the 1st element in the array, and execute it.
|
2020-04-22 02:31:27 +03:00
|
|
|
|
DeleteActivityHandler toCall = deleteHandlers.First();
|
2018-07-04 01:52:45 +03:00
|
|
|
|
await toCall(this, cr, Next).ConfigureAwait(false);
|
2018-02-25 21:44:04 +03:00
|
|
|
|
}
|
2018-03-08 02:37:08 +03:00
|
|
|
|
}
|
2018-02-10 02:08:12 +03:00
|
|
|
|
}
|