Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,21 @@ public class SkillConversationIdFactory : SkillConversationIdFactoryBase
{
private readonly ConcurrentDictionary<string, string> _conversationRefs = new ConcurrentDictionary<string, string>();

public override Task<string> CreateSkillConversationIdAsync(ConversationReference conversationReference, CancellationToken cancellationToken)
public override Task<string> CreateSkillConversationIdAsync(SkillConversationIdFactoryOptions options, CancellationToken cancellationToken)
{
var crJson = JsonConvert.SerializeObject(conversationReference);
var key = $"{conversationReference.Conversation.Id}-{conversationReference.ChannelId}-skillconvo";
_conversationRefs.GetOrAdd(key, crJson);
var skillConversationReference = new SkillConversationReference
{
ConversationReference = options.Activity.GetConversationReference(),
OAuthScope = options.FromBotOAuthScope
};
var key = $"{options.FromBotId}-{options.BotFrameworkSkill.AppId}-{skillConversationReference.ConversationReference.Conversation.Id}-{skillConversationReference.ConversationReference.ChannelId}-skillconvo";
_conversationRefs.GetOrAdd(key, JsonConvert.SerializeObject(skillConversationReference));
return Task.FromResult(key);
}

public override Task<ConversationReference> GetConversationReferenceAsync(string skillConversationId, CancellationToken cancellationToken)
public override Task<SkillConversationReference> GetSkillConversationReferenceAsync(string skillConversationId, CancellationToken cancellationToken)
{
var conversationReference = JsonConvert.DeserializeObject<ConversationReference>(_conversationRefs[skillConversationId]);
var conversationReference = JsonConvert.DeserializeObject<SkillConversationReference>(_conversationRefs[skillConversationId]);
return Task.FromResult(conversationReference);
}

Expand Down
2 changes: 2 additions & 0 deletions libraries/Microsoft.Bot.Builder/ChannelServiceHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ public ChannelServiceHandler(
_channelProvider = channelProvider;
}

protected IChannelProvider ChannelProvider => _channelProvider;

public async Task<ResourceResponse> HandleSendToConversationAsync(string authHeader, string conversationId, Activity activity, CancellationToken cancellationToken = default)
{
var claimsIdentity = await AuthenticateAsync(authHeader).ConfigureAwait(false);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Schema;
Expand All @@ -21,20 +22,53 @@ public abstract class SkillConversationIdFactoryBase
/// <remarks>
/// It should be possible to use the returned string on a request URL and it should not contain special characters.
/// </remarks>
public abstract Task<string> CreateSkillConversationIdAsync(ConversationReference conversationReference, CancellationToken cancellationToken);
[Obsolete("Method is deprecated, please use CreateSkillConversationIdAsync() with SkillConversationIdFactoryOptions instead.", false)]
public virtual Task<string> CreateSkillConversationIdAsync(ConversationReference conversationReference, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}

/// <summary>
/// Gets the <see cref="ConversationReference"/> created using <see cref="CreateSkillConversationIdAsync"/> for a skillConversationId.
/// Creates a conversation id for a skill conversation.
/// </summary>
/// <param name="skillConversationId">A skill conversationId created using <see cref="CreateSkillConversationIdAsync"/>.</param>
/// <param name="options">A <see cref="SkillConversationIdFactoryOptions"/> instance containing parameters for creating the conversation ID.</param>
/// <param name="cancellationToken">A cancellation token.</param>
/// <returns>A unique conversation ID used to communicate with the skill.</returns>
/// <remarks>
/// It should be possible to use the returned string on a request URL and it should not contain special characters.
/// </remarks>
public virtual Task<string> CreateSkillConversationIdAsync(SkillConversationIdFactoryOptions options, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}

/// <summary>
/// Gets the <see cref="ConversationReference"/> created using <see cref="CreateSkillConversationIdAsync(Microsoft.Bot.Schema.ConversationReference,System.Threading.CancellationToken)"/> for a skillConversationId.
/// </summary>
/// <param name="skillConversationId">A skill conversationId created using <see cref="CreateSkillConversationIdAsync(Microsoft.Bot.Schema.ConversationReference,System.Threading.CancellationToken)"/>.</param>
/// <param name="cancellationToken">A cancellation token.</param>
/// <returns>The caller's <see cref="ConversationReference"/> for a skillConversationId. null if not found.</returns>
public abstract Task<ConversationReference> GetConversationReferenceAsync(string skillConversationId, CancellationToken cancellationToken);
[Obsolete("Method is deprecated, please use GetSkillConversationReferenceAsync() instead.", false)]
public virtual Task<ConversationReference> GetConversationReferenceAsync(string skillConversationId, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}

/// <summary>
/// Gets the <see cref="SkillConversationReference"/> used during <see cref="CreateSkillConversationIdAsync(SkillConversationIdFactoryOptions,System.Threading.CancellationToken)"/> for a skillConversationId.
/// </summary>
/// <param name="skillConversationId">A skill conversationId created using <see cref="CreateSkillConversationIdAsync(SkillConversationIdFactoryOptions,System.Threading.CancellationToken)"/>.</param>
/// <param name="cancellationToken">A cancellation token.</param>
/// <returns>The caller's <see cref="ConversationReference"/> for a skillConversationId, with originatingAudience. Null if not found.</returns>
public virtual Task<SkillConversationReference> GetSkillConversationReferenceAsync(string skillConversationId, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}

/// <summary>
/// Deletes a <see cref="ConversationReference"/>.
/// </summary>
/// <param name="skillConversationId">A skill conversationId created using <see cref="CreateSkillConversationIdAsync"/>.</param>
/// <param name="skillConversationId">A skill conversationId created using <see cref="CreateSkillConversationIdAsync(SkillConversationIdFactoryOptions,System.Threading.CancellationToken)"/>.</param>
/// <param name="cancellationToken">A cancellation token.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public abstract Task DeleteConversationReferenceAsync(string skillConversationId, CancellationToken cancellationToken);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Microsoft.Bot.Schema;

namespace Microsoft.Bot.Builder.Skills
{
/// <summary>
/// A class defining the parameters used in <see cref="SkillConversationIdFactoryBase.CreateSkillConversationIdAsync(SkillConversationIdFactoryOptions,System.Threading.CancellationToken)"/>.
/// </summary>
public class SkillConversationIdFactoryOptions
{
/// <summary>
/// Gets or sets the oauth audience scope, used during token retrieval (either https://api.botframework.com or bot app id).
/// </summary>
/// <value>
/// The oauth audience scope, used during token retrieval (either https://api.botframework.com or bot app id if this is a skill calling another skill).
/// </value>
public string FromBotOAuthScope { get; set; }

/// <summary>
/// Gets or sets the id of the parent bot that is messaging the skill.
/// </summary>
/// <value>
/// The id of the parent bot that is messaging the skill.
/// </value>
public string FromBotId { get; set; }

/// <summary>
/// Gets or sets the activity which will be sent to the skill.
/// </summary>
/// <value>
/// The activity which will be sent to the skill.
/// </value>
public Activity Activity { get; set; }

/// <summary>
/// Gets or sets the skill to create the conversation Id for.
/// </summary>
/// <value>
/// The skill to create the conversation Id for.
/// </value>
public BotFrameworkSkill BotFrameworkSkill { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Microsoft.Bot.Schema;

namespace Microsoft.Bot.Builder.Skills
{
public class SkillConversationReference
{
public ConversationReference ConversationReference { get; set; }

public string OAuthScope { get; set; }
}
}
35 changes: 26 additions & 9 deletions libraries/Microsoft.Bot.Builder/Skills/SkillHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public class SkillHandler : ChannelServiceHandler

private readonly BotAdapter _adapter;
private readonly IBot _bot;
private readonly SkillConversationIdFactoryBase _conversationIdIdFactory;
private readonly SkillConversationIdFactoryBase _conversationIdFactory;
private readonly ILogger _logger;

/// <summary>
Expand Down Expand Up @@ -54,7 +54,7 @@ public SkillHandler(
{
_adapter = adapter ?? throw new ArgumentNullException(nameof(adapter));
_bot = bot ?? throw new ArgumentNullException(nameof(bot));
_conversationIdIdFactory = conversationIdFactory ?? throw new ArgumentNullException(nameof(conversationIdFactory));
_conversationIdFactory = conversationIdFactory ?? throw new ArgumentNullException(nameof(conversationIdFactory));
_logger = logger ?? NullLogger.Instance;
}

Expand Down Expand Up @@ -150,26 +150,43 @@ private static void ApplyEventToTurnContextActivity(ITurnContext turnContext, Ac

private async Task<ResourceResponse> ProcessActivityAsync(ClaimsIdentity claimsIdentity, string conversationId, string replyToActivityId, Activity activity, CancellationToken cancellationToken)
{
var conversationReference = await _conversationIdIdFactory.GetConversationReferenceAsync(conversationId, CancellationToken.None).ConfigureAwait(false);
SkillConversationReference skillConversationReference = null;
try
{
skillConversationReference = await _conversationIdFactory.GetSkillConversationReferenceAsync(conversationId, cancellationToken).ConfigureAwait(false);
}
catch (NotImplementedException)
{
// Attempt to get SkillConversationReference using deprecated method.
// this catch should be removed once we remove the deprecated method.
#pragma warning disable 618 // We need to use the deprecated method for backward compatibility.
var conversationReference = await _conversationIdFactory.GetConversationReferenceAsync(conversationId, cancellationToken).ConfigureAwait(false);
#pragma warning restore 618
skillConversationReference = new SkillConversationReference
{
ConversationReference = conversationReference,
OAuthScope = ChannelProvider != null && ChannelProvider.IsGovernment() ? GovernmentAuthenticationConstants.ToChannelFromBotOAuthScope : AuthenticationConstants.ToChannelFromBotOAuthScope
};
}

if (conversationReference == null)
if (skillConversationReference == null)
{
throw new KeyNotFoundException();
}

var skillConversationReference = activity.GetConversationReference();
var activityConversationReference = activity.GetConversationReference();

var callback = new BotCallbackHandler(async (turnContext, ct) =>
{
turnContext.TurnState.Add(SkillConversationReferenceKey, skillConversationReference);
turnContext.TurnState.Add(SkillConversationReferenceKey, activityConversationReference);

activity.ApplyConversationReference(conversationReference);
activity.ApplyConversationReference(skillConversationReference.ConversationReference);

turnContext.Activity.Id = replyToActivityId;
switch (activity.Type)
{
case ActivityTypes.EndOfConversation:
await _conversationIdIdFactory.DeleteConversationReferenceAsync(conversationId, cancellationToken).ConfigureAwait(false);
await _conversationIdFactory.DeleteConversationReferenceAsync(conversationId, cancellationToken).ConfigureAwait(false);
ApplyEoCToTurnContextActivity(turnContext, activity);
await _bot.OnTurnAsync(turnContext, ct).ConfigureAwait(false);
break;
Expand All @@ -183,7 +200,7 @@ private async Task<ResourceResponse> ProcessActivityAsync(ClaimsIdentity claimsI
}
});

await _adapter.ContinueConversationAsync(claimsIdentity, conversationReference, callback, cancellationToken).ConfigureAwait(false);
await _adapter.ContinueConversationAsync(claimsIdentity, skillConversationReference.ConversationReference, skillConversationReference.OAuthScope, callback, cancellationToken).ConfigureAwait(false);
return new ResourceResponse(Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,45 @@ public SkillHttpClient(HttpClient httpClient, ICredentialProvider credentialProv
_conversationIdFactory = conversationIdFactory;
}

public async Task<InvokeResponse> PostActivityAsync(string fromBotId, BotFrameworkSkill toSkill, Uri callbackUrl, Activity activity, CancellationToken cancellationToken)
/// <summary>
/// Uses the SkillConversationIdFactory to create or retrieve a Skill Conversation Id, and sends the activity.
/// </summary>
/// <param name="originatingAudience">The oauth audience scope, used during token retrieval. (Either https://api.botframework.com or bot app id.)</param>
/// <param name="fromBotId">The MicrosoftAppId of the bot sending the activity.</param>
/// <param name="toSkill">The skill to create the conversation Id for.</param>
/// <param name="callbackUrl">The callback Url for the skill host.</param>
/// <param name="activity">The activity to send.</param>
/// <param name="cancellationToken">A cancellation token.</param>
/// <returns>Async task with invokeResponse.</returns>
public async Task<InvokeResponse> PostActivityAsync(string originatingAudience, string fromBotId, BotFrameworkSkill toSkill, Uri callbackUrl, Activity activity, CancellationToken cancellationToken)
{
var skillConversationId = await _conversationIdFactory.CreateSkillConversationIdAsync(activity.GetConversationReference(), cancellationToken).ConfigureAwait(false);
string skillConversationId;
try
{
var options = new SkillConversationIdFactoryOptions
{
FromBotOAuthScope = originatingAudience,
FromBotId = fromBotId,
Activity = activity,
BotFrameworkSkill = toSkill
};
skillConversationId = await _conversationIdFactory.CreateSkillConversationIdAsync(options, cancellationToken).ConfigureAwait(false);
}
catch (NotImplementedException)
{
// Attempt to create the ID using deprecated method.
#pragma warning disable 618 // Keeping this for backward compat, this catch should be removed when the deprecated method is removed.
skillConversationId = await _conversationIdFactory.CreateSkillConversationIdAsync(activity.GetConversationReference(), cancellationToken).ConfigureAwait(false);
#pragma warning restore 618
}

return await PostActivityAsync(fromBotId, toSkill.AppId, toSkill.SkillEndpoint, callbackUrl, skillConversationId, activity, cancellationToken).ConfigureAwait(false);
}

public async Task<InvokeResponse> PostActivityAsync(string fromBotId, BotFrameworkSkill toSkill, Uri callbackUrl, Activity activity, CancellationToken cancellationToken)
{
var originatingAudience = ChannelProvider != null && ChannelProvider.IsGovernment() ? GovernmentAuthenticationConstants.ToChannelFromBotOAuthScope : AuthenticationConstants.ToChannelFromBotOAuthScope;
return await PostActivityAsync(originatingAudience, fromBotId, toSkill, callbackUrl, activity, cancellationToken).ConfigureAwait(false);
}
}
}
20 changes: 10 additions & 10 deletions tests/Microsoft.Bot.Builder.TestProtocol/MyConversationIdFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,27 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Skills;
using Microsoft.Bot.Schema;
using Newtonsoft.Json;

namespace Microsoft.Bot.Builder.TestProtocol
{
public class MyConversationIdFactory : SkillConversationIdFactoryBase
{
private readonly ConcurrentDictionary<string, string> _conversationRefs = new ConcurrentDictionary<string, string>();
private readonly ConcurrentDictionary<string, SkillConversationReference> _conversationRefs = new ConcurrentDictionary<string, SkillConversationReference>();

public override Task<string> CreateSkillConversationIdAsync(ConversationReference conversationReference, CancellationToken cancellationToken)
public override Task<string> CreateSkillConversationIdAsync(SkillConversationIdFactoryOptions options, CancellationToken cancellationToken = default)
{
var crJson = JsonConvert.SerializeObject(conversationReference);
var key = (conversationReference.Conversation.Id + conversationReference.ServiceUrl).GetHashCode().ToString(CultureInfo.InvariantCulture);
_conversationRefs.GetOrAdd(key, crJson);
var key = (options.Activity.Conversation.Id + options.Activity.ServiceUrl).GetHashCode().ToString(CultureInfo.InvariantCulture);
_conversationRefs.GetOrAdd(key, new SkillConversationReference
{
ConversationReference = options.Activity.GetConversationReference(),
OAuthScope = options.FromBotOAuthScope
});
return Task.FromResult(key);
}

public override Task<ConversationReference> GetConversationReferenceAsync(string skillConversationId, CancellationToken cancellationToken)
public override Task<SkillConversationReference> GetSkillConversationReferenceAsync(string skillConversationId, CancellationToken cancellationToken)
{
var conversationReference = JsonConvert.DeserializeObject<ConversationReference>(_conversationRefs[skillConversationId]);
return Task.FromResult(conversationReference);
return Task.FromResult(_conversationRefs[skillConversationId]);
}

public override Task DeleteConversationReferenceAsync(string skillConversationId, CancellationToken cancellationToken)
Expand Down
Loading