add v3bridge
This commit is contained in:
Родитель
3fc3e38288
Коммит
28ad01b4e3
|
@ -42,7 +42,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Samples.Ai.Qn
|
|||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Integration", "Integration", "{276EBE79-A13A-46BD-A566-B01DC0477A9B}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Bot.Builder.LUIS", "libraries\Microsoft.Bot.Builder.LUIS\Microsoft.Bot.Builder.LUIS.csproj", "{67AA3C00-E2C5-4D13-BA5E-72EB0E5B8DAA}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.LUIS", "libraries\Microsoft.Bot.Builder.LUIS\Microsoft.Bot.Builder.LUIS.csproj", "{67AA3C00-E2C5-4D13-BA5E-72EB0E5B8DAA}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.LUIS.Tests", "tests\Microsoft.Bot.Builder.LUIS.Tests\Microsoft.Bot.Builder.LUIS.Tests.csproj", "{7BCEBDC1-D57F-4717-9B15-4FACD5473489}"
|
||||
EndProject
|
||||
|
@ -56,6 +56,18 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.Integ
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.Integration.AspNet.Core", "libraries\integration\Microsoft.Bot.Builder.Integration.AspNet.Core\Microsoft.Bot.Builder.Integration.AspNet.Core.csproj", "{053BD8B8-B5C1-4C45-81D4-C9BA8D5B3CE2}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "V3Bridge", "V3Bridge", "{2A46FEEF-4FD9-4DD5-8650-39C36CFDF2BB}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Bot.Builder.V3Bridge", "libraries\v3bridge\Microsoft.Bot.Builder.V3Bridge\Microsoft.Bot.Builder.V3Bridge.csproj", "{CDFEC7D6-847E-4C13-956B-0A960AE3EB60}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Bot.Builder.V3Bridge.Autofac", "libraries\v3bridge\Microsoft.Bot.Builder.V3Bridge.Autofac\Microsoft.Bot.Builder.V3Bridge.Autofac.csproj", "{2C145824-38DD-409C-AB52-B3538997726C}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Bot.Builder.V3Bridge.FormFlow.Json", "libraries\v3bridge\Microsoft.Bot.Builder.V3Bridge.FormFlow.Json\Microsoft.Bot.Builder.V3Bridge.FormFlow.Json.csproj", "{8674F271-4437-4178-B4E2-15F91B322E5D}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Bot.Builder.V3Bridge.History", "libraries\v3bridge\Microsoft.Bot.Builder.V3Bridge.History\Microsoft.Bot.Builder.V3Bridge.History.csproj", "{D2834D71-D7A2-451B-9A61-1488AF049526}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Bot.Builder.V3Bridge.Tests", "tests\Microsoft.Bot.Builder.V3Bridge.Tests\Microsoft.Bot.Builder.V3Bridge.Tests.csproj", "{508661A7-F47A-4D60-8ED6-69E378ED0E74}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -126,6 +138,14 @@ Global
|
|||
{A8CE6B0F-E054-45F7-B4D7-23D2C65D2D26}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A8CE6B0F-E054-45F7-B4D7-23D2C65D2D26}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A8CE6B0F-E054-45F7-B4D7-23D2C65D2D26}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{67AA3C00-E2C5-4D13-BA5E-72EB0E5B8DAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{67AA3C00-E2C5-4D13-BA5E-72EB0E5B8DAA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{67AA3C00-E2C5-4D13-BA5E-72EB0E5B8DAA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{67AA3C00-E2C5-4D13-BA5E-72EB0E5B8DAA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7BCEBDC1-D57F-4717-9B15-4FACD5473489}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7BCEBDC1-D57F-4717-9B15-4FACD5473489}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7BCEBDC1-D57F-4717-9B15-4FACD5473489}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7BCEBDC1-D57F-4717-9B15-4FACD5473489}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7A197F41-3411-47BF-87A6-5BC8A44CDB74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7A197F41-3411-47BF-87A6-5BC8A44CDB74}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7A197F41-3411-47BF-87A6-5BC8A44CDB74}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
@ -146,14 +166,26 @@ Global
|
|||
{053BD8B8-B5C1-4C45-81D4-C9BA8D5B3CE2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{053BD8B8-B5C1-4C45-81D4-C9BA8D5B3CE2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{053BD8B8-B5C1-4C45-81D4-C9BA8D5B3CE2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{67AA3C00-E2C5-4D13-BA5E-72EB0E5B8DAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{67AA3C00-E2C5-4D13-BA5E-72EB0E5B8DAA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{67AA3C00-E2C5-4D13-BA5E-72EB0E5B8DAA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{67AA3C00-E2C5-4D13-BA5E-72EB0E5B8DAA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7BCEBDC1-D57F-4717-9B15-4FACD5473489}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7BCEBDC1-D57F-4717-9B15-4FACD5473489}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7BCEBDC1-D57F-4717-9B15-4FACD5473489}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7BCEBDC1-D57F-4717-9B15-4FACD5473489}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{CDFEC7D6-847E-4C13-956B-0A960AE3EB60}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{CDFEC7D6-847E-4C13-956B-0A960AE3EB60}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{CDFEC7D6-847E-4C13-956B-0A960AE3EB60}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{CDFEC7D6-847E-4C13-956B-0A960AE3EB60}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{2C145824-38DD-409C-AB52-B3538997726C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{2C145824-38DD-409C-AB52-B3538997726C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2C145824-38DD-409C-AB52-B3538997726C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2C145824-38DD-409C-AB52-B3538997726C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8674F271-4437-4178-B4E2-15F91B322E5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8674F271-4437-4178-B4E2-15F91B322E5D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8674F271-4437-4178-B4E2-15F91B322E5D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8674F271-4437-4178-B4E2-15F91B322E5D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{D2834D71-D7A2-451B-9A61-1488AF049526}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D2834D71-D7A2-451B-9A61-1488AF049526}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D2834D71-D7A2-451B-9A61-1488AF049526}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D2834D71-D7A2-451B-9A61-1488AF049526}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{508661A7-F47A-4D60-8ED6-69E378ED0E74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{508661A7-F47A-4D60-8ED6-69E378ED0E74}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{508661A7-F47A-4D60-8ED6-69E378ED0E74}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{508661A7-F47A-4D60-8ED6-69E378ED0E74}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -183,6 +215,12 @@ Global
|
|||
{30B77905-0BAB-44C1-A011-0245F220C30C} = {3ADFB27A-95FA-4330-B211-1D66A29A17AB}
|
||||
{BD0B82EF-1601-4E87-B78A-B43DE7EB36B0} = {276EBE79-A13A-46BD-A566-B01DC0477A9B}
|
||||
{053BD8B8-B5C1-4C45-81D4-C9BA8D5B3CE2} = {276EBE79-A13A-46BD-A566-B01DC0477A9B}
|
||||
{2A46FEEF-4FD9-4DD5-8650-39C36CFDF2BB} = {4269F3C3-6B42-419B-B64A-3E6DC0F1574A}
|
||||
{CDFEC7D6-847E-4C13-956B-0A960AE3EB60} = {2A46FEEF-4FD9-4DD5-8650-39C36CFDF2BB}
|
||||
{2C145824-38DD-409C-AB52-B3538997726C} = {2A46FEEF-4FD9-4DD5-8650-39C36CFDF2BB}
|
||||
{8674F271-4437-4178-B4E2-15F91B322E5D} = {2A46FEEF-4FD9-4DD5-8650-39C36CFDF2BB}
|
||||
{D2834D71-D7A2-451B-9A61-1488AF049526} = {2A46FEEF-4FD9-4DD5-8650-39C36CFDF2BB}
|
||||
{508661A7-F47A-4D60-8ED6-69E378ED0E74} = {AD743B78-D61F-4FBF-B620-FA83CE599A50}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {7173C9F3-A7F9-496E-9078-9156E35D6E16}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
@rep -find:"[assembly: AssemblyKeyFileAttribute" -replace:"//[assembly: AssemblyKeyFileAttribute" -r AssemblyInfo.cs
|
||||
|
||||
@rep -find:"[assembly: AssemblyDelaySignAttribute(true)]" -replace:"//[assembly: AssemblyDelaySignAttribute(true)]" -r AssemblyInfo.cs
|
||||
|
||||
@rep -find:"//[assembly: InternalsVisibleTo(" -replace:"[assembly: InternalsVisibleTo(" -r AssemblyInfo.cs
|
|
@ -0,0 +1,5 @@
|
|||
@rep -find:"//[assembly: AssemblyKeyFileAttribute" -replace:"[assembly: AssemblyKeyFileAttribute" -r AssemblyInfo.cs
|
||||
|
||||
@rep -find:"//[assembly: AssemblyDelaySignAttribute(true)]" -replace:"[assembly: AssemblyDelaySignAttribute(true)]" -r AssemblyInfo.cs
|
||||
|
||||
@rep -find:"[assembly: InternalsVisibleTo(" -replace:"//[assembly: InternalsVisibleTo(" -r AssemblyInfo.cs
|
|
@ -0,0 +1,99 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using Autofac;
|
||||
using Autofac.Builder;
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Autofac.Base
|
||||
{
|
||||
public static partial class Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Register a component to be created through reflection, and
|
||||
/// provide a key that can be used to retrieve the component.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method leverages Autofac's autowiring of components through reflection,
|
||||
/// providing a key that directly reflects that component type, so that it might
|
||||
/// be retrieved by that key and possibly replaced in an adapter chain.
|
||||
/// </remarks>
|
||||
/// <typeparam name="TImplementer">The type of the component implementation.</typeparam>
|
||||
/// <typeparam name="TLimit">The service type provided by the component.</typeparam>
|
||||
/// <param name="builder">Container builder.</param>
|
||||
/// <returns>Registration builder allowing the registration to be configured.</returns>
|
||||
public static
|
||||
IRegistrationBuilder<TImplementer, ConcreteReflectionActivatorData, SingleRegistrationStyle>
|
||||
RegisterKeyedType<TImplementer, TLimit>(this ContainerBuilder builder)
|
||||
{
|
||||
// use Autofac to autowire the implementer of the service
|
||||
// with a key of that implementer type so that it can be overridden
|
||||
return
|
||||
builder
|
||||
.RegisterType<TImplementer>()
|
||||
.Keyed<TLimit>(typeof(TImplementer));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register an adapter chain of components, exposing a shared service interface.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This registers a factory method to create a adapter chain of components, based on wrapping each
|
||||
/// inner component with an adapter outer component.
|
||||
/// </remarks>
|
||||
/// <typeparam name="TLimit">The service type provided by the component.</typeparam>
|
||||
/// <param name="builder">Container builder.</param>
|
||||
/// <param name="types">The services type keys that can be used to retrieve the components in the chain.</param>
|
||||
/// <returns>Registration builder allowing the registration to be configured.</returns>
|
||||
public static
|
||||
IRegistrationBuilder<TLimit, SimpleActivatorData, SingleRegistrationStyle>
|
||||
RegisterAdapterChain<TLimit>(this ContainerBuilder builder, params Type[] types)
|
||||
{
|
||||
return
|
||||
builder
|
||||
.Register(c =>
|
||||
{
|
||||
// http://stackoverflow.com/questions/23406641/how-to-mix-decorators-in-autofac
|
||||
TLimit service = default(TLimit);
|
||||
for (int index = 0; index < types.Length; ++index)
|
||||
{
|
||||
// resolve the keyed adapter, passing the previous service as the inner parameter
|
||||
service = c.ResolveKeyed<TLimit>(types[index], TypedParameter.From(service));
|
||||
}
|
||||
|
||||
return service;
|
||||
})
|
||||
.As<TLimit>();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,185 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Autofac;
|
||||
using Microsoft.Bot.Builder.V3Bridge.ConnectorEx;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Dialogs.Internals;
|
||||
using Microsoft.Bot.Schema;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Dialogs
|
||||
{
|
||||
/// <summary>
|
||||
/// The top level composition root for the SDK.
|
||||
/// </summary>
|
||||
public static partial class Conversation
|
||||
{
|
||||
private static readonly object gate = new object();
|
||||
private static IContainer container;
|
||||
|
||||
static Conversation()
|
||||
{
|
||||
UpdateContainer(builder =>
|
||||
{
|
||||
});
|
||||
}
|
||||
|
||||
public static IContainer Container
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (gate)
|
||||
{
|
||||
return container;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the Autofac container.
|
||||
/// </summary>
|
||||
/// <param name="update">The delegate that represents the update to apply.</param>
|
||||
public static void UpdateContainer(Action<ContainerBuilder> update)
|
||||
{
|
||||
lock (gate)
|
||||
{
|
||||
var builder = new ContainerBuilder();
|
||||
builder.RegisterModule(new DialogModule_MakeRoot());
|
||||
update(builder);
|
||||
container = builder.Build();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process an incoming message within the conversation.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method:
|
||||
/// 1. Instantiates and composes the required components.
|
||||
/// 2. Deserializes the dialog state (the dialog stack and each dialog's state) from the <paramref name="toBot"/> <see cref="IMessageActivity"/>.
|
||||
/// 3. Resumes the conversation processes where the dialog suspended to wait for a <see cref="IMessageActivity"/>.
|
||||
/// 4. Queues <see cref="IMessageActivity"/>s to be sent to the user.
|
||||
/// 5. Serializes the updated dialog state in the messages to be sent to the user.
|
||||
///
|
||||
/// The <paramref name="MakeRoot"/> factory method is invoked for new conversations only,
|
||||
/// because existing conversations have the dialog stack and state serialized in the <see cref="IMessageActivity"/> data.
|
||||
/// </remarks>
|
||||
/// <param name="toBot">The message sent to the bot.</param>
|
||||
/// <param name="MakeRoot">The factory method to make the root dialog.</param>
|
||||
/// <param name="token">The cancellation token.</param>
|
||||
/// <returns>A task that represents the message to send inline back to the user.</returns>
|
||||
public static async Task SendAsync(IMessageActivity toBot, Func<IDialog<object>> MakeRoot, CancellationToken token = default(CancellationToken))
|
||||
{
|
||||
using (var scope = DialogModule.BeginLifetimeScope(Container, toBot))
|
||||
{
|
||||
DialogModule_MakeRoot.Register(scope, MakeRoot);
|
||||
await SendAsync(scope, toBot, token);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resume a conversation and post the data to the dialog waiting.
|
||||
/// </summary>
|
||||
/// <param name="resumptionCookie"> The resumption cookie.</param>
|
||||
/// <param name="toBot"> The data sent to bot.</param>
|
||||
/// <param name="token"> The cancellation token.</param>
|
||||
/// <returns> A task that represent the message to send back to the user after resumption of the conversation.</returns>
|
||||
[Obsolete("Use the overload that uses ConversationReference instead of ResumptionCookie")]
|
||||
public static async Task ResumeAsync(ResumptionCookie resumptionCookie, IActivity toBot, CancellationToken token = default(CancellationToken))
|
||||
{
|
||||
var conversationRef = resumptionCookie.ToConversationReference();
|
||||
await ResumeAsync(conversationRef, toBot, token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resume a conversation and post the data to the dialog waiting.
|
||||
/// </summary>
|
||||
/// <param name="conversationReference"> The resumption cookie.</param>
|
||||
/// <param name="toBot"> The data sent to bot.</param>
|
||||
/// <param name="token"> The cancellation token.</param>
|
||||
/// <returns> A task that represent the message to send back to the user after resumption of the conversation.</returns>
|
||||
public static async Task ResumeAsync(ConversationReference conversationReference, IActivity toBot, CancellationToken token = default(CancellationToken))
|
||||
{
|
||||
var continuationMessage = conversationReference.GetPostToBotMessage();
|
||||
using (var scope = DialogModule.BeginLifetimeScope(Container, continuationMessage))
|
||||
{
|
||||
Func<IDialog<object>> MakeRoot = () => { throw new InvalidOperationException(); };
|
||||
DialogModule_MakeRoot.Register(scope, MakeRoot);
|
||||
|
||||
await SendAsync(scope, toBot, token);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disable a specific service type by replacing it with a pass through implementation.
|
||||
/// </summary>
|
||||
/// <param name="type">The service type.</param>
|
||||
/// <param name="builder">The container builder.</param>
|
||||
public static void Disable(Type type, ContainerBuilder builder)
|
||||
{
|
||||
if (typeof(IBotToUser).IsAssignableFrom(type))
|
||||
{
|
||||
builder
|
||||
.RegisterType<PassBotToUser>()
|
||||
.Keyed<IBotToUser>(type);
|
||||
}
|
||||
|
||||
if (typeof(IPostToBot).IsAssignableFrom(type))
|
||||
{
|
||||
builder
|
||||
.RegisterType<PassPostToBot>()
|
||||
.Keyed<IPostToBot>(type);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disable a specific service type by replacing it with a pass through implementation.
|
||||
/// </summary>
|
||||
/// <param name="type">The service type.</param>
|
||||
public static void Disable(Type type)
|
||||
{
|
||||
UpdateContainer(builder =>
|
||||
{
|
||||
Disable(type, builder);
|
||||
});
|
||||
}
|
||||
|
||||
public static async Task SendAsync(ILifetimeScope scope, IActivity toBot, CancellationToken token = default(CancellationToken))
|
||||
{
|
||||
var task = scope.Resolve<IPostToBot>();
|
||||
await task.PostAsync(toBot, token);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,452 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Resources;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using Autofac;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Autofac.Base;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Base;
|
||||
using Microsoft.Bot.Builder.V3Bridge;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Resource;
|
||||
using Microsoft.Bot.Builder.V3Bridge.ConnectorEx;
|
||||
using Microsoft.Bot.Builder.V3Bridge.History;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Internals.Fibers;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Scorables;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Scorables.Internals;
|
||||
using Microsoft.Bot.Connector;
|
||||
using Microsoft.Bot.Connector.Authentication;
|
||||
using Microsoft.Bot.Schema;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Dialogs.Internals
|
||||
{
|
||||
/// <summary>
|
||||
/// Autofac module for Dialog components.
|
||||
/// </summary>
|
||||
public sealed class DialogModule : Module
|
||||
{
|
||||
public const string BlobKey = "DialogState";
|
||||
public static readonly object LifetimeScopeTag = typeof(DialogModule);
|
||||
|
||||
public static readonly object Key_DeleteProfile_Regex = new object();
|
||||
public static readonly object Key_Dialog_Router = new object();
|
||||
|
||||
public static ILifetimeScope BeginLifetimeScope(ILifetimeScope scope, IMessageActivity message)
|
||||
{
|
||||
var inner = scope.BeginLifetimeScope(LifetimeScopeTag);
|
||||
inner.Resolve<IMessageActivity>(TypedParameter.From(message));
|
||||
return inner;
|
||||
}
|
||||
|
||||
protected override void Load(ContainerBuilder builder)
|
||||
{
|
||||
base.Load(builder);
|
||||
|
||||
builder.RegisterModule(new FiberModule<DialogTask>());
|
||||
|
||||
// singleton components
|
||||
|
||||
builder
|
||||
.Register(c => new ResourceManager("Microsoft.Bot.Builder.V3Bridge.Resource.Resources", typeof(Resources).Assembly))
|
||||
.As<ResourceManager>()
|
||||
.SingleInstance();
|
||||
|
||||
// every lifetime scope is driven by a message
|
||||
|
||||
builder
|
||||
.Register((c, p) => p.TypedAs<IMessageActivity>())
|
||||
.AsSelf()
|
||||
.AsImplementedInterfaces()
|
||||
.InstancePerMatchingLifetimeScope(LifetimeScopeTag);
|
||||
|
||||
// make the address and cookie available for the lifetime scope
|
||||
|
||||
builder
|
||||
.Register(c => Address.FromActivity(c.Resolve<IActivity>()))
|
||||
.AsImplementedInterfaces()
|
||||
.InstancePerMatchingLifetimeScope(LifetimeScopeTag);
|
||||
|
||||
#pragma warning disable CS0618
|
||||
builder
|
||||
.RegisterType<ResumptionCookie>()
|
||||
.AsSelf()
|
||||
.InstancePerMatchingLifetimeScope(LifetimeScopeTag);
|
||||
#pragma warning restore CS0618
|
||||
|
||||
builder
|
||||
.Register(c => c.Resolve<IActivity>().ToConversationReference())
|
||||
.AsSelf()
|
||||
.InstancePerMatchingLifetimeScope(LifetimeScopeTag);
|
||||
|
||||
// components not marked as [Serializable]
|
||||
builder
|
||||
.RegisterType<MicrosoftAppCredentials>()
|
||||
.AsSelf()
|
||||
.SingleInstance();
|
||||
|
||||
builder
|
||||
// not resolving IEqualityComparer<IAddress> from container because it's a very local policy
|
||||
// and yet too broad of an interface. could explore using tags for registration overrides.
|
||||
.Register(c => new LocalMutualExclusion<IAddress>(new ConversationAddressComparer()))
|
||||
.As<IScope<IAddress>>()
|
||||
.SingleInstance();
|
||||
|
||||
builder
|
||||
.Register(c => new ConnectorClientFactory(c.Resolve<IAddress>(), c.Resolve<MicrosoftAppCredentials>()))
|
||||
.As<IConnectorClientFactory>()
|
||||
.InstancePerLifetimeScope();
|
||||
|
||||
builder
|
||||
.Register(c => c.Resolve<IConnectorClientFactory>().MakeConnectorClient())
|
||||
.As<IConnectorClient>()
|
||||
.InstancePerLifetimeScope();
|
||||
|
||||
builder
|
||||
.RegisterType<ChannelCapability>()
|
||||
.AsImplementedInterfaces()
|
||||
.InstancePerLifetimeScope();
|
||||
|
||||
builder
|
||||
.RegisterKeyedType<InMemoryDataStore, IBotDataStore<BotData>>()
|
||||
.SingleInstance();
|
||||
|
||||
builder
|
||||
.RegisterKeyedType<CachingBotDataStore, IBotDataStore<BotData>>()
|
||||
.WithParameter((pi, c) => pi.ParameterType == typeof(CachingBotDataStoreConsistencyPolicy),
|
||||
(pi, c) => CachingBotDataStoreConsistencyPolicy.ETagBasedConsistency)
|
||||
.InstancePerLifetimeScope();
|
||||
|
||||
builder
|
||||
.RegisterAdapterChain<IBotDataStore<BotData>>
|
||||
(
|
||||
typeof(InMemoryDataStore),
|
||||
typeof(CachingBotDataStore)
|
||||
)
|
||||
.InstancePerLifetimeScope();
|
||||
|
||||
builder
|
||||
.RegisterKeyedType<JObjectBotData, IBotData>()
|
||||
.AsSelf()
|
||||
.InstancePerLifetimeScope();
|
||||
|
||||
builder
|
||||
.RegisterKeyedType<DialogTaskManagerBotDataLoader, IBotData>()
|
||||
.InstancePerLifetimeScope();
|
||||
|
||||
builder
|
||||
.RegisterAdapterChain<IBotData>
|
||||
(
|
||||
typeof(JObjectBotData),
|
||||
typeof(DialogTaskManagerBotDataLoader)
|
||||
)
|
||||
.InstancePerLifetimeScope();
|
||||
|
||||
builder
|
||||
.Register((c, p) => new BotDataBagStream(p.TypedAs<IBotDataBag>(), p.TypedAs<string>()))
|
||||
.As<Stream>()
|
||||
.InstancePerDependency();
|
||||
|
||||
builder
|
||||
.Register(c => new DialogTaskManager(DialogModule.BlobKey,
|
||||
c.Resolve<JObjectBotData>(),
|
||||
c.Resolve<IStackStoreFactory<DialogTask>>(),
|
||||
c.Resolve<Func<IDialogStack, CancellationToken, IDialogContext>>(),
|
||||
c.Resolve<IEventProducer<IActivity>>()))
|
||||
.AsSelf()
|
||||
.As<IDialogTaskManager>()
|
||||
.As<IDialogTasks>()
|
||||
.InstancePerLifetimeScope();
|
||||
|
||||
builder
|
||||
.RegisterType<DialogSystem>()
|
||||
.As<IDialogSystem>();
|
||||
|
||||
builder
|
||||
.RegisterType<DialogContext>()
|
||||
.As<IDialogContext>()
|
||||
.InstancePerDependency();
|
||||
|
||||
builder
|
||||
.Register(c =>
|
||||
{
|
||||
var cc = c.Resolve<IComponentContext>();
|
||||
|
||||
Func<string, IBotDataBag, IStore<IFiberLoop<DialogTask>>> make = (taskId, botDataBag) =>
|
||||
{
|
||||
var stream = cc.Resolve<Stream>(TypedParameter.From(botDataBag), TypedParameter.From(taskId));
|
||||
return cc.Resolve<IStore<IFiberLoop<DialogTask>>>(TypedParameter.From(stream));
|
||||
};
|
||||
|
||||
return make;
|
||||
})
|
||||
.As<Func<string, IBotDataBag, IStore<IFiberLoop<DialogTask>>>>()
|
||||
.InstancePerDependency();
|
||||
|
||||
|
||||
builder.Register(c => c.Resolve<IDialogTaskManager>().DialogTasks[0])
|
||||
.As<IDialogStack>()
|
||||
.As<IDialogTask>()
|
||||
.InstancePerLifetimeScope();
|
||||
|
||||
// Scorable implementing "/deleteprofile"
|
||||
builder
|
||||
.Register(c => new Regex("^(\\s)*/deleteprofile", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace))
|
||||
.Keyed<Regex>(Key_DeleteProfile_Regex)
|
||||
.SingleInstance();
|
||||
|
||||
builder
|
||||
.Register(c => new DeleteProfileScorable(c.Resolve<IDialogStack>(), c.Resolve<IBotData>(), c.Resolve<IBotToUser>(), c.ResolveKeyed<Regex>(Key_DeleteProfile_Regex)))
|
||||
.As<IScorable<IActivity, double>>()
|
||||
.InstancePerLifetimeScope();
|
||||
|
||||
// scorable implementing "end conversation"
|
||||
builder
|
||||
.RegisterInstance(EndConversationEvent.MakeScorable())
|
||||
.As<IScorable<IResolver, double>>()
|
||||
.SingleInstance();
|
||||
|
||||
builder
|
||||
.Register(c =>
|
||||
{
|
||||
var cc = c.Resolve<IComponentContext>();
|
||||
Func<IActivity, IResolver> make = activity =>
|
||||
{
|
||||
var resolver = NoneResolver.Instance;
|
||||
resolver = new EnumResolver(resolver);
|
||||
resolver = new AutofacResolver(cc, resolver);
|
||||
resolver = new ArrayResolver(resolver,
|
||||
activity,
|
||||
cc.Resolve<IBotToUser>(),
|
||||
cc.Resolve<IBotData>(),
|
||||
cc.Resolve<IDialogSystem>());
|
||||
resolver = new ActivityResolver(resolver);
|
||||
resolver = new EventActivityValueResolver(resolver);
|
||||
resolver = new InvokeActivityValueResolver(resolver);
|
||||
return resolver;
|
||||
};
|
||||
return make;
|
||||
})
|
||||
.AsSelf()
|
||||
.InstancePerLifetimeScope();
|
||||
|
||||
builder
|
||||
.RegisterType<DialogRouter>()
|
||||
.Keyed<IScorable<IActivity, double>>(Key_Dialog_Router)
|
||||
.InstancePerLifetimeScope();
|
||||
|
||||
builder
|
||||
.RegisterType<EventQueue<IActivity>>()
|
||||
.AsImplementedInterfaces()
|
||||
.InstancePerLifetimeScope();
|
||||
|
||||
builder
|
||||
.RegisterType<ReactiveDialogTask>()
|
||||
.AsSelf()
|
||||
.InstancePerLifetimeScope();
|
||||
|
||||
builder
|
||||
.Register(c => new ScoringEventLoop<double>(c.Resolve<ReactiveDialogTask>(), c.Resolve<ReactiveDialogTask>(), c.Resolve<IEventConsumer<IActivity>>(), c.ResolveKeyed<IScorable<IActivity, double>>(Key_Dialog_Router)))
|
||||
.As<IEventLoop>()
|
||||
.InstancePerLifetimeScope();
|
||||
|
||||
// register IDataBag that is used for to load/save ResumptionData
|
||||
builder
|
||||
.Register(c =>
|
||||
{
|
||||
var cc = c.Resolve<IComponentContext>();
|
||||
Func<IBotDataBag> make = () =>
|
||||
{
|
||||
return cc.Resolve<IBotData>().PrivateConversationData;
|
||||
};
|
||||
return make;
|
||||
})
|
||||
.As<Func<IBotDataBag>>()
|
||||
.InstancePerLifetimeScope();
|
||||
|
||||
builder
|
||||
.RegisterType<ResumptionContext>()
|
||||
.AsSelf()
|
||||
.InstancePerLifetimeScope();
|
||||
|
||||
builder
|
||||
.RegisterType<LocaleFinder>()
|
||||
.AsSelf()
|
||||
.AsImplementedInterfaces()
|
||||
.InstancePerLifetimeScope();
|
||||
|
||||
// IPostToBot services
|
||||
|
||||
builder
|
||||
.RegisterKeyedType<NullPostToBot, IPostToBot>()
|
||||
.SingleInstance();
|
||||
|
||||
builder
|
||||
.RegisterKeyedType<PassPostToBot, IPostToBot>()
|
||||
.InstancePerDependency();
|
||||
|
||||
builder
|
||||
.RegisterKeyedType<EventLoopDialogTask, IPostToBot>()
|
||||
.InstancePerLifetimeScope();
|
||||
|
||||
builder
|
||||
.RegisterKeyedType<PersistentDialogTask, IPostToBot>()
|
||||
.InstancePerLifetimeScope();
|
||||
|
||||
builder
|
||||
.RegisterKeyedType<ExceptionTranslationDialogTask, IPostToBot>()
|
||||
.InstancePerLifetimeScope();
|
||||
|
||||
builder
|
||||
.RegisterKeyedType<SerializeByConversation, IPostToBot>()
|
||||
.InstancePerLifetimeScope();
|
||||
|
||||
builder
|
||||
.RegisterKeyedType<SetAmbientThreadCulture, IPostToBot>()
|
||||
.InstancePerLifetimeScope();
|
||||
|
||||
builder
|
||||
.RegisterKeyedType<PostUnhandledExceptionToUser, IPostToBot>()
|
||||
.InstancePerLifetimeScope();
|
||||
|
||||
builder
|
||||
.RegisterKeyedType<LogPostToBot, IPostToBot>()
|
||||
.InstancePerLifetimeScope();
|
||||
|
||||
builder
|
||||
.RegisterKeyedType<QueueDrainingDialogTask, IPostToBot>()
|
||||
.InstancePerLifetimeScope();
|
||||
|
||||
builder
|
||||
.RegisterAdapterChain<IPostToBot>
|
||||
(
|
||||
typeof(EventLoopDialogTask),
|
||||
typeof(SetAmbientThreadCulture),
|
||||
typeof(QueueDrainingDialogTask),
|
||||
typeof(PersistentDialogTask),
|
||||
typeof(ExceptionTranslationDialogTask),
|
||||
typeof(SerializeByConversation),
|
||||
typeof(PostUnhandledExceptionToUser),
|
||||
typeof(LogPostToBot)
|
||||
)
|
||||
.InstancePerLifetimeScope();
|
||||
|
||||
// other
|
||||
|
||||
builder
|
||||
.RegisterType<NullActivityLogger>()
|
||||
.AsImplementedInterfaces()
|
||||
.SingleInstance();
|
||||
|
||||
builder
|
||||
.RegisterType<KeyboardCardMapper>()
|
||||
.AsImplementedInterfaces()
|
||||
.SingleInstance();
|
||||
|
||||
builder
|
||||
.RegisterType<SetLocalTimestampMapper>()
|
||||
.AsImplementedInterfaces()
|
||||
.SingleInstance();
|
||||
|
||||
// IBotToUser services
|
||||
builder
|
||||
.RegisterType<InputHintQueue>()
|
||||
.AsImplementedInterfaces()
|
||||
.InstancePerLifetimeScope();
|
||||
|
||||
builder
|
||||
.RegisterKeyedType<NullBotToUser, IBotToUser>()
|
||||
.SingleInstance();
|
||||
|
||||
builder
|
||||
.RegisterKeyedType<PassBotToUser, IBotToUser>()
|
||||
.InstancePerDependency();
|
||||
|
||||
builder
|
||||
.RegisterKeyedType<AlwaysSendDirect_BotToUser, IBotToUser>()
|
||||
.AsSelf()
|
||||
.InstancePerLifetimeScope();
|
||||
|
||||
builder
|
||||
.RegisterKeyedType<AutoInputHint_BotToUser, IBotToUser>()
|
||||
.InstancePerLifetimeScope();
|
||||
|
||||
builder
|
||||
.RegisterKeyedType<MapToChannelData_BotToUser, IBotToUser>()
|
||||
.InstancePerLifetimeScope();
|
||||
|
||||
builder
|
||||
.RegisterKeyedType<LogBotToUser, IBotToUser>()
|
||||
.InstancePerLifetimeScope();
|
||||
|
||||
#pragma warning disable CS1587
|
||||
/// <see cref="LogBotToUser"/> is composed around <see cref="MapToChannelData_BotToUser"/> is composed around
|
||||
/// <see cref="AlwaysSendDirect_BotToUser"/>. The complexity of registering each component is pushed to a separate
|
||||
/// registration method, and each of these components are replaceable without re-registering
|
||||
/// the entire adapter chain by registering a new component with the same component key.
|
||||
#pragma warning restore CS1587
|
||||
builder
|
||||
.RegisterAdapterChain<IBotToUser>
|
||||
(
|
||||
typeof(AlwaysSendDirect_BotToUser),
|
||||
typeof(AutoInputHint_BotToUser),
|
||||
typeof(MapToChannelData_BotToUser),
|
||||
typeof(LogBotToUser)
|
||||
)
|
||||
.InstancePerLifetimeScope();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class DialogModule_MakeRoot : Module
|
||||
{
|
||||
protected override void Load(ContainerBuilder builder)
|
||||
{
|
||||
base.Load(builder);
|
||||
|
||||
builder.RegisterModule(new DialogModule());
|
||||
|
||||
// TODO: let dialog resolve its dependencies from container
|
||||
builder
|
||||
.Register((c, p) => p.TypedAs<Func<IDialog<object>>>())
|
||||
.AsSelf()
|
||||
.InstancePerMatchingLifetimeScope(DialogModule.LifetimeScopeTag);
|
||||
}
|
||||
|
||||
public static void Register(ILifetimeScope scope, Func<IDialog<object>> MakeRoot)
|
||||
{
|
||||
// TODO: let dialog resolve its dependencies from container
|
||||
scope.Resolve<Func<IDialog<object>>>(TypedParameter.From(MakeRoot));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using Autofac;
|
||||
using Autofac.Core;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Scorables.Internals;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Internals.Fibers
|
||||
{
|
||||
public sealed class DoNotSerializeResolver : IResolver
|
||||
{
|
||||
private readonly IComponentContext context;
|
||||
private readonly IEnumerable<Parameter> parameters;
|
||||
public DoNotSerializeResolver(IComponentContext context, IEnumerable<Parameter> parameters)
|
||||
{
|
||||
SetField.NotNull(out this.context, nameof(context), context);
|
||||
SetField.NotNull(out this.parameters, nameof(parameters), parameters);
|
||||
}
|
||||
|
||||
public static IEnumerable<Type> Services(Type root)
|
||||
{
|
||||
var types = new Queue<Type>();
|
||||
types.Enqueue(root);
|
||||
|
||||
while (types.Count > 0)
|
||||
{
|
||||
var type = types.Dequeue();
|
||||
if (type == typeof(MulticastDelegate))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
yield return type;
|
||||
|
||||
foreach (var next in type.GetInterfaces())
|
||||
{
|
||||
types.Enqueue(next);
|
||||
}
|
||||
|
||||
while (type.BaseType != null)
|
||||
{
|
||||
type = type.BaseType;
|
||||
types.Enqueue(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static readonly ConcurrentDictionary<Type, IReadOnlyList<Type>> ServicesByType = new ConcurrentDictionary<Type, IReadOnlyList<Type>>();
|
||||
|
||||
bool IResolver.TryResolve(Type type, object tag, out object value)
|
||||
{
|
||||
if (tag == null)
|
||||
{
|
||||
var services = ServicesByType.GetOrAdd(type, t => Services(t).Distinct().ToArray());
|
||||
for (int index = 0; index < services.Count; ++index)
|
||||
{
|
||||
var serviceType = services[index];
|
||||
var service = new KeyedService(FiberModule.Key_DoNotSerialize, serviceType);
|
||||
|
||||
var registry = this.context.ComponentRegistry;
|
||||
|
||||
IComponentRegistration registration;
|
||||
if (registry.TryGetRegistration(service, out registration))
|
||||
{
|
||||
// Autofac will still generate "implicit relationship types" (e.g. Func or IEnumerable)
|
||||
// and ignore the key in KeyedService
|
||||
bool generated = registry.IsRegistered(new KeyedService(new object(), serviceType));
|
||||
if (!generated)
|
||||
{
|
||||
value = this.context.ResolveComponent(registration, this.parameters);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,182 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Runtime.Serialization.Formatters.Binary;
|
||||
|
||||
using Autofac;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Scorables.Internals;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Internals.Fibers
|
||||
{
|
||||
/// <summary>
|
||||
/// Autofac module for Fiber components.
|
||||
/// </summary>
|
||||
public abstract class FiberModule : Module
|
||||
{
|
||||
/// <summary>
|
||||
/// Services keyed with <see cref="Key_DoNotSerialize"/> will not be serialized.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Services marked with <see cref="Key_DoNotSerialize"/> will not serialize their dependencies either.
|
||||
/// </remarks>
|
||||
public static readonly object Key_DoNotSerialize = new object();
|
||||
|
||||
public static readonly object Key_SurrogateProvider = new object();
|
||||
|
||||
protected override void Load(ContainerBuilder builder)
|
||||
{
|
||||
base.Load(builder);
|
||||
|
||||
// singleton components
|
||||
|
||||
builder
|
||||
.RegisterType<DefaultTraceListener>()
|
||||
.As<TraceListener>()
|
||||
.SingleInstance();
|
||||
|
||||
builder
|
||||
.Register(c => Comparer<double>.Default)
|
||||
.As<IComparer<double>>();
|
||||
|
||||
builder
|
||||
.Register(c => NormalizedTraits.Instance)
|
||||
.As<ITraits<double>>()
|
||||
.SingleInstance();
|
||||
|
||||
builder
|
||||
.Register(c => new Serialization.StoreInstanceByTypeSurrogate(priority: int.MaxValue))
|
||||
.Keyed<Serialization.ISurrogateProvider>(Key_SurrogateProvider)
|
||||
.SingleInstance();
|
||||
|
||||
builder
|
||||
.Register(c => new Serialization.ClosureCaptureErrorSurrogate(priority: 1))
|
||||
.Keyed<Serialization.ISurrogateProvider>(Key_SurrogateProvider)
|
||||
.SingleInstance();
|
||||
|
||||
builder
|
||||
.Register(c => new Serialization.JObjectSurrogate(priority: 3))
|
||||
.Keyed<Serialization.ISurrogateProvider>(Key_SurrogateProvider)
|
||||
.SingleInstance();
|
||||
|
||||
builder
|
||||
.RegisterDecorator<Serialization.ISurrogateProvider>((c, inner) => new Serialization.SurrogateLogDecorator(inner, c.Resolve<TraceListener>()), fromKey: Key_SurrogateProvider);
|
||||
|
||||
builder
|
||||
.RegisterType<Serialization.SurrogateSelector>()
|
||||
.As<ISurrogateSelector>()
|
||||
.SingleInstance();
|
||||
|
||||
// per request, depends on resolution parameters through "p"
|
||||
builder
|
||||
.Register((c, p) =>
|
||||
{
|
||||
var cc = c.Resolve<IComponentContext>();
|
||||
// late bound workaround for https://github.com/autofac/Autofac/issues/852
|
||||
return new DoNotSerializeResolver(cc, p);
|
||||
})
|
||||
.As<IResolver>()
|
||||
.InstancePerLifetimeScope();
|
||||
|
||||
builder
|
||||
.Register((c, p) => new BinaryFormatter(c.Resolve<ISurrogateSelector>(), new StreamingContext(StreamingContextStates.All, c.Resolve<IResolver>(p))))
|
||||
.As<IFormatter>()
|
||||
.InstancePerLifetimeScope();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Autofac module for Fiber components.
|
||||
/// </summary>
|
||||
public sealed class FiberModule<C> : FiberModule
|
||||
{
|
||||
protected override void Load(ContainerBuilder builder)
|
||||
{
|
||||
base.Load(builder);
|
||||
|
||||
// [Serializable] components, serialized as type, deserialized through container resolution of type
|
||||
|
||||
builder
|
||||
.RegisterType<WaitFactory<C>>()
|
||||
.Keyed<IWaitFactory<C>>(Key_DoNotSerialize)
|
||||
.As<IWaitFactory<C>>()
|
||||
.SingleInstance();
|
||||
|
||||
builder
|
||||
.RegisterType<FrameFactory<C>>()
|
||||
.Keyed<IFrameFactory<C>>(Key_DoNotSerialize)
|
||||
.As<IFrameFactory<C>>()
|
||||
.SingleInstance();
|
||||
|
||||
builder
|
||||
.RegisterInstance(NullWait<C>.Instance)
|
||||
.Keyed<NullWait<C>>(Key_DoNotSerialize)
|
||||
.AsSelf()
|
||||
.SingleInstance();
|
||||
|
||||
// per request, no resolution parameter dependency
|
||||
|
||||
builder
|
||||
.RegisterType<Fiber<C>>()
|
||||
.As<IFiberLoop<C>>()
|
||||
.InstancePerDependency();
|
||||
|
||||
builder
|
||||
.Register((c, p) => new FactoryStore<IFiberLoop<C>>(new ErrorResilientStore<IFiberLoop<C>>(new FormatterStore<IFiberLoop<C>>(p.TypedAs<Stream>(), c.Resolve<IFormatter>(p))), c.Resolve<Func<IFiberLoop<C>>>(p)))
|
||||
.As<IStore<IFiberLoop<C>>>()
|
||||
.InstancePerDependency();
|
||||
|
||||
builder
|
||||
.RegisterType<StoreFromStack<C>>()
|
||||
.AsImplementedInterfaces()
|
||||
.InstancePerLifetimeScope();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ReflectionSurrogateModule : Module
|
||||
{
|
||||
protected override void Load(ContainerBuilder builder)
|
||||
{
|
||||
base.Load(builder);
|
||||
|
||||
builder
|
||||
.Register(c => new Serialization.StoreInstanceByFieldsSurrogate(priority: 2))
|
||||
.Keyed<Serialization.ISurrogateProvider>(FiberModule.Key_SurrogateProvider)
|
||||
.SingleInstance();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{2C145824-38DD-409C-AB52-B3538997726C}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Microsoft.Bot.Builder.V3Bridge.Autofac</RootNamespace>
|
||||
<AssemblyName>Microsoft.Bot.Builder.V3Bridge.Autofac</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<TargetFrameworkProfile />
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>TRACE;DEBUG</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<NoWarn>CS1998;CS1591;CS0419</NoWarn>
|
||||
<DocumentationFile>bin\Debug\Microsoft.Bot.Builder.V3Bridge.Autofac.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<NoWarn>CS1998;CS1591;CS0419</NoWarn>
|
||||
<DocumentationFile>bin\Release\Microsoft.Bot.Builder.V3Bridge.Autofac.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Autofac, Version=3.5.0.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\packages\Autofac.3.5.2\lib\net40\Autofac.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.IdentityModel.Logging, Version=5.2.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\packages\Microsoft.IdentityModel.Logging.5.2.1\lib\net451\Microsoft.IdentityModel.Logging.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.IdentityModel.Protocols, Version=5.2.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\packages\Microsoft.IdentityModel.Protocols.5.2.1\lib\net451\Microsoft.IdentityModel.Protocols.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect, Version=5.2.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\packages\Microsoft.IdentityModel.Protocols.OpenIdConnect.5.2.1\lib\net451\Microsoft.IdentityModel.Protocols.OpenIdConnect.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.IdentityModel.Tokens, Version=5.2.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\packages\Microsoft.IdentityModel.Tokens.5.2.1\lib\net451\Microsoft.IdentityModel.Tokens.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Rest.ClientRuntime, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\packages\Microsoft.Rest.ClientRuntime.2.3.10\lib\net452\Microsoft.Rest.ClientRuntime.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Configuration" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.IdentityModel.Tokens.Jwt, Version=5.2.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\packages\System.IdentityModel.Tokens.Jwt.5.2.1\lib\net451\System.IdentityModel.Tokens.Jwt.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Net" />
|
||||
<Reference Include="System.Net.Http.WebRequest" />
|
||||
<Reference Include="System.Runtime.Serialization" />
|
||||
<Reference Include="System.Web" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Base\Extensions.cs" />
|
||||
<Compile Include="Dialogs\Conversation.cs" />
|
||||
<Compile Include="Dialogs\DialogModule.cs" />
|
||||
<Compile Include="Fibers\DoNotSerializeResolver.cs" />
|
||||
<Compile Include="Fibers\FiberModule.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Scorables\Resolver.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="app.config">
|
||||
<SubType>Designer</SubType>
|
||||
</None>
|
||||
<None Include="packages.config">
|
||||
<SubType>Designer</SubType>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<WCFMetadata Include="Connected Services\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Microsoft.Bot.Connector\Microsoft.Bot.Connector.csproj">
|
||||
<Project>{6462da5d-27dc-4cd5-9467-5efb998fd838}</Project>
|
||||
<Name>Microsoft.Bot.Connector</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\Microsoft.Bot.Schema\Microsoft.Bot.Schema.csproj">
|
||||
<Project>{c1f54cdc-ad1d-45bb-8f7d-f49e411afaf1}</Project>
|
||||
<Name>Microsoft.Bot.Schema</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Microsoft.Bot.Builder.V3Bridge\Microsoft.Bot.Builder.V3Bridge.csproj">
|
||||
<Project>{cdfec7d6-847e-4c13-956b-0a960ae3eb60}</Project>
|
||||
<Name>Microsoft.Bot.Builder.V3Bridge</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
|
@ -0,0 +1,42 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("Microsoft.Bot.Builder.V3Bridge.Autofac")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Microsoft")]
|
||||
[assembly: AssemblyProduct("Microsoft Bot Builder")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2016")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("cdfec7d6-847e-4c13-956b-0a960ae3eb60")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("3.14.0.6")]
|
||||
[assembly: AssemblyFileVersion("3.14.0.6")]
|
||||
|
||||
[assembly: InternalsVisibleTo("Microsoft.Bot.Builder.V3Bridge.Tests")]
|
||||
//[assembly: InternalsVisibleTo("Microsoft.Bot.Sample.Tests")]
|
||||
|
||||
//[assembly: AssemblyKeyFileAttribute(@"..\\..\\..\\buildtools\\35MSSharedLib1024.snk")]
|
||||
//[assembly: AssemblyDelaySignAttribute(true)]
|
|
@ -0,0 +1,70 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using Autofac;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Internals.Fibers;
|
||||
using Microsoft.Bot.Schema;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Scorables.Internals
|
||||
{
|
||||
public sealed class AutofacResolver : DelegatingResolver
|
||||
{
|
||||
private readonly IComponentContext context;
|
||||
|
||||
public AutofacResolver(IComponentContext context, IResolver inner)
|
||||
: base(inner)
|
||||
{
|
||||
SetField.NotNull(out this.context, nameof(context), context);
|
||||
}
|
||||
|
||||
public override bool TryResolve(Type type, object tag, out object value)
|
||||
{
|
||||
if (tag != null && this.context.TryResolveKeyed(tag, type, out value))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (this.context.TryResolve(type, out value))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.TryResolve(type, tag, out value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-10.0.0.0" newVersion="10.0.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.IdentityModel.Tokens" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-5.2.1.0" newVersion="5.2.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.IdentityModel.Tokens.Jwt" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-5.2.1.0" newVersion="5.2.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.IdentityModel.Protocols" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-5.2.1.0" newVersion="5.2.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.IdentityModel.Protocols.OpenIdConnect" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-5.2.1.0" newVersion="5.2.1.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" /></startup></configuration>
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Autofac" version="3.5.2" targetFramework="net46" />
|
||||
<package id="Microsoft.IdentityModel.Logging" version="5.2.1" targetFramework="net461" />
|
||||
<package id="Microsoft.IdentityModel.Protocols" version="5.2.1" targetFramework="net461" />
|
||||
<package id="Microsoft.IdentityModel.Protocols.OpenIdConnect" version="5.2.1" targetFramework="net461" />
|
||||
<package id="Microsoft.IdentityModel.Tokens" version="5.2.1" targetFramework="net461" />
|
||||
<package id="Microsoft.Rest.ClientRuntime" version="2.3.10" targetFramework="net461" />
|
||||
<package id="Newtonsoft.Json" version="10.0.3" targetFramework="net461" />
|
||||
<package id="System.IdentityModel.Tokens.Jwt" version="5.2.1" targetFramework="net461" />
|
||||
</packages>
|
|
@ -0,0 +1,577 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using Microsoft.Bot.Builder.V3Bridge.Dialogs;
|
||||
using Microsoft.Bot.Builder.V3Bridge.FormFlow.Advanced;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.FormFlow.Json
|
||||
{
|
||||
// No need to document overrides of interface methods
|
||||
#pragma warning disable CS1591
|
||||
|
||||
/// <summary>
|
||||
/// %Field defined through JSON Schema.
|
||||
/// </summary>
|
||||
public class FieldJson : Field<JObject>
|
||||
{
|
||||
/// <summary>
|
||||
/// Construct a field from a JSON schema.
|
||||
/// </summary>
|
||||
/// <param name="builder">Form builder.</param>
|
||||
/// <param name="name">Name of field within schema.</param>
|
||||
public FieldJson(FormBuilderJson builder, string name)
|
||||
: base(name, FieldRole.Value)
|
||||
{
|
||||
_builder = builder;
|
||||
bool optional;
|
||||
var fieldSchema = FieldSchema(name, out optional);
|
||||
var eltSchema = ElementSchema(fieldSchema);
|
||||
ProcessAnnotations(fieldSchema, eltSchema);
|
||||
var fieldName = name.Split('.').Last();
|
||||
JToken date;
|
||||
if (eltSchema.TryGetValue("DateTime", out date) && date.Value<bool>())
|
||||
{
|
||||
SetType(typeof(DateTime));
|
||||
}
|
||||
else
|
||||
{
|
||||
SetType(eltSchema["enum"] != null && eltSchema["enum"].Any() ? null : ToType(eltSchema));
|
||||
}
|
||||
SetAllowsMultiple(IsType(fieldSchema, "array"));
|
||||
SetFieldDescription(ProcessDescription(fieldSchema, Language.CamelCase(fieldName)));
|
||||
var terms = Strings(fieldSchema, "Terms");
|
||||
JToken maxPhrase;
|
||||
if (terms != null && fieldSchema.TryGetValue("MaxPhrase", out maxPhrase))
|
||||
{
|
||||
terms = (from seed in terms
|
||||
from gen in Language.GenerateTerms(seed, (int)maxPhrase)
|
||||
select gen).ToArray<string>();
|
||||
}
|
||||
SetFieldTerms(terms ?? Language.GenerateTerms(Language.CamelCase(fieldName), 3));
|
||||
ProcessEnum(eltSchema);
|
||||
SetOptional(optional);
|
||||
SetIsNullable(IsType(fieldSchema, "null"));
|
||||
}
|
||||
|
||||
#region IFieldState
|
||||
public override object GetValue(JObject state)
|
||||
{
|
||||
object result = null;
|
||||
var val = state.SelectToken(_name);
|
||||
if (val != null)
|
||||
{
|
||||
if (_type == null)
|
||||
{
|
||||
if (_allowsMultiple)
|
||||
{
|
||||
result = val.ToObject<string[]>();
|
||||
}
|
||||
else
|
||||
{
|
||||
result = (string)val;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result = val.ToObject(_type);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public override void SetValue(JObject state, object value)
|
||||
{
|
||||
var jvalue = JToken.FromObject(value);
|
||||
var current = state.SelectToken(_name);
|
||||
if (current == null)
|
||||
{
|
||||
var step = state;
|
||||
var steps = _name.Split('.');
|
||||
foreach (var part in steps.Take(steps.Count() - 1))
|
||||
{
|
||||
var next = step.GetValue(part);
|
||||
if (next == null)
|
||||
{
|
||||
var nextStep = new JObject();
|
||||
step.Add(part, nextStep);
|
||||
step = nextStep;
|
||||
}
|
||||
else
|
||||
{
|
||||
step = (JObject)next;
|
||||
}
|
||||
}
|
||||
step.Add(steps.Last(), jvalue);
|
||||
}
|
||||
else
|
||||
{
|
||||
current.Replace(jvalue);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsUnknown(JObject state)
|
||||
{
|
||||
return state.SelectToken(_name) == null;
|
||||
}
|
||||
|
||||
public override void SetUnknown(JObject state)
|
||||
{
|
||||
var token = state.SelectToken(_name);
|
||||
if (token != null)
|
||||
{
|
||||
token.Parent.Remove();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
internal IEnumerable<MessageOrConfirmation> Before { get; set; }
|
||||
|
||||
internal IEnumerable<MessageOrConfirmation> After { get; set; }
|
||||
|
||||
protected JObject FieldSchema(string path, out bool optional)
|
||||
{
|
||||
var schema = _builder.Schema;
|
||||
var parts = path.Split('.');
|
||||
var required = true;
|
||||
foreach (var part in parts)
|
||||
{
|
||||
required = required && (schema["required"] == null || ((JArray)schema["required"]).Any((val) => (string)val == part));
|
||||
schema = (JObject)((JObject)schema["properties"])[part];
|
||||
if (part == null)
|
||||
{
|
||||
throw new MissingFieldException($"{part} is not a property in your schema.");
|
||||
}
|
||||
}
|
||||
optional = !required;
|
||||
return schema;
|
||||
}
|
||||
|
||||
protected Type ToType(JObject schema)
|
||||
{
|
||||
Type type = null;
|
||||
if (IsType(schema, "boolean")) type = typeof(bool);
|
||||
else if (IsType(schema, "integer")) type = typeof(long);
|
||||
else if (IsType(schema, "number")) type = typeof(double);
|
||||
else if (IsType(schema, "string")) type = typeof(string);
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"{schema} does not have a valid C# type.");
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
protected string[] Strings(JObject schema, string field)
|
||||
{
|
||||
string[] result = null;
|
||||
JToken array;
|
||||
if (schema.TryGetValue(field, out array))
|
||||
{
|
||||
result = array.ToObject<string[]>();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected string AString(JObject schema, string field)
|
||||
{
|
||||
string result = null;
|
||||
JToken astring;
|
||||
if (schema.TryGetValue(field, out astring))
|
||||
{
|
||||
result = (string)astring;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected void ProcessAnnotations(JObject fieldSchema, JObject eltSchema)
|
||||
{
|
||||
ProcessTemplates(_builder.Schema);
|
||||
Before = ProcessMessages("Before", fieldSchema);
|
||||
ProcessTemplates(fieldSchema);
|
||||
ProcessPrompt(fieldSchema);
|
||||
ProcessNumeric(fieldSchema);
|
||||
ProcessPattern(fieldSchema);
|
||||
ProcessActive(fieldSchema);
|
||||
ProcessDefine(fieldSchema);
|
||||
ProcessValidation(fieldSchema);
|
||||
ProcessNext(fieldSchema);
|
||||
After = ProcessMessages("After", fieldSchema);
|
||||
}
|
||||
|
||||
protected void ProcessDefine(JObject schema)
|
||||
{
|
||||
if (schema["Define"] != null)
|
||||
{
|
||||
SetDefine(_builder.DefineScript(this, (string)schema["Define"]));
|
||||
}
|
||||
}
|
||||
|
||||
protected void ProcessValidation(JObject schema)
|
||||
{
|
||||
if (schema["Validate"] != null)
|
||||
{
|
||||
SetValidate(_builder.ValidateScript(this, (string)schema["Validate"]));
|
||||
}
|
||||
}
|
||||
|
||||
protected void ProcessNext(JObject schema)
|
||||
{
|
||||
if (schema["Next"] != null)
|
||||
{
|
||||
SetNext(_builder.NextScript(this, (string)schema["Next"]));
|
||||
}
|
||||
}
|
||||
|
||||
protected void ProcessActive(JObject schema)
|
||||
{
|
||||
if (schema["Active"] != null)
|
||||
{
|
||||
var script = (string)schema["Active"];
|
||||
SetActive(_builder.ActiveScript(this, (string)schema["Active"]));
|
||||
}
|
||||
}
|
||||
|
||||
internal class MessageOrConfirmation
|
||||
{
|
||||
public bool IsMessage;
|
||||
public PromptAttribute Prompt;
|
||||
public string ActiveScript;
|
||||
public string MessageScript;
|
||||
public IEnumerable<string> Dependencies;
|
||||
}
|
||||
|
||||
internal IEnumerable<MessageOrConfirmation> ProcessMessages(string fieldName, JObject fieldSchema)
|
||||
{
|
||||
JToken array;
|
||||
if (fieldSchema.TryGetValue(fieldName, out array))
|
||||
{
|
||||
foreach (var message in array.Children<JObject>())
|
||||
{
|
||||
var info = new MessageOrConfirmation();
|
||||
if (GetPrompt("Message", message, info))
|
||||
{
|
||||
info.IsMessage = true;
|
||||
yield return info;
|
||||
}
|
||||
else if (GetPrompt("Confirm", message, info))
|
||||
{
|
||||
info.IsMessage = false;
|
||||
yield return info;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"{message} is not Message or Confirm");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal bool GetPrompt(string fieldName, JObject message, MessageOrConfirmation info)
|
||||
{
|
||||
bool found = false;
|
||||
JToken val;
|
||||
if (message.TryGetValue(fieldName, out val))
|
||||
{
|
||||
if (val is JValue)
|
||||
{
|
||||
info.MessageScript = (string)val;
|
||||
}
|
||||
else if (val is JArray)
|
||||
{
|
||||
info.Prompt = (PromptAttribute)ProcessTemplate(message, new PromptAttribute((from msg in val select (string)msg).ToArray()));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"{val} must be string or array of strings.");
|
||||
}
|
||||
if (message["Active"] != null)
|
||||
{
|
||||
info.ActiveScript = (string)message["Active"];
|
||||
}
|
||||
if (message["Dependencies"] != null)
|
||||
{
|
||||
info.Dependencies = (from dependent in message["Dependencies"] select (string)dependent);
|
||||
}
|
||||
found = true;
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
protected void ProcessTemplates(JObject schema)
|
||||
{
|
||||
JToken templates;
|
||||
if (schema.TryGetValue("Templates", out templates))
|
||||
{
|
||||
foreach (JProperty template in templates.Children())
|
||||
{
|
||||
TemplateUsage usage;
|
||||
if (Enum.TryParse<TemplateUsage>(template.Name, out usage))
|
||||
{
|
||||
ReplaceTemplate((TemplateAttribute)ProcessTemplate(template.Value, new TemplateAttribute(usage)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void ProcessPrompt(JObject schema)
|
||||
{
|
||||
JToken prompt;
|
||||
if (schema.TryGetValue("Prompt", out prompt))
|
||||
{
|
||||
SetPrompt((PromptAttribute)ProcessTemplate(prompt, new PromptAttribute()));
|
||||
}
|
||||
}
|
||||
|
||||
protected void ProcessNumeric(JObject schema)
|
||||
{
|
||||
JToken token;
|
||||
double min = -double.MaxValue, max = double.MaxValue;
|
||||
if (schema.TryGetValue("minimum", out token)) min = (double)token;
|
||||
if (schema.TryGetValue("maximum", out token)) max = (double)token;
|
||||
if (min != -double.MaxValue || max != double.MaxValue)
|
||||
{
|
||||
SetLimits(min, max);
|
||||
}
|
||||
}
|
||||
|
||||
protected void ProcessPattern(JObject schema)
|
||||
{
|
||||
JToken token;
|
||||
if (schema.TryGetValue("pattern", out token))
|
||||
{
|
||||
SetPattern((string)token);
|
||||
}
|
||||
}
|
||||
|
||||
protected void ProcessEnum(JObject schema)
|
||||
{
|
||||
if (schema["enum"] != null)
|
||||
{
|
||||
var enums = (from val in (JArray)schema["enum"] select (string)val);
|
||||
var toDescription = new Dictionary<string, DescribeAttribute>();
|
||||
var toTerms = new Dictionary<string, string[]>();
|
||||
var toMaxPhrase = new Dictionary<string, int>();
|
||||
JToken values;
|
||||
if (schema.TryGetValue("Values", out values))
|
||||
{
|
||||
foreach (JProperty prop in values.Children())
|
||||
{
|
||||
var key = prop.Name;
|
||||
if (!enums.Contains(key))
|
||||
{
|
||||
throw new ArgumentException($"{key} is not in enumeration.");
|
||||
}
|
||||
var desc = (JObject)prop.Value;
|
||||
JToken description;
|
||||
if (desc.TryGetValue("Describe", out description))
|
||||
{
|
||||
toDescription.Add(key, ProcessDescription(desc, Language.CamelCase(key)));
|
||||
}
|
||||
JToken terms;
|
||||
if (desc.TryGetValue("Terms", out terms))
|
||||
{
|
||||
toTerms.Add(key, terms.ToObject<string[]>());
|
||||
}
|
||||
JToken maxPhrase;
|
||||
if (desc.TryGetValue("MaxPhrase", out maxPhrase))
|
||||
{
|
||||
toMaxPhrase.Add(key, (int)maxPhrase);
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (var key in enums)
|
||||
{
|
||||
DescribeAttribute description;
|
||||
if (!toDescription.TryGetValue(key, out description))
|
||||
{
|
||||
description = new DescribeAttribute(Language.CamelCase(key));
|
||||
}
|
||||
AddDescription(key, description);
|
||||
|
||||
string[] terms;
|
||||
int maxPhrase;
|
||||
if (!toTerms.TryGetValue(key, out terms))
|
||||
{
|
||||
terms = Language.GenerateTerms(description.Description, 3);
|
||||
}
|
||||
else if (toMaxPhrase.TryGetValue(key, out maxPhrase))
|
||||
{
|
||||
terms = (from seed in terms
|
||||
from gen in Language.GenerateTerms(seed, maxPhrase)
|
||||
select gen).ToArray<string>();
|
||||
}
|
||||
AddTerms(key, terms);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected TemplateBaseAttribute ProcessTemplate(JToken template, TemplateBaseAttribute attribute)
|
||||
{
|
||||
if (template["Patterns"] != null)
|
||||
{
|
||||
attribute.Patterns = template["Patterns"].ToObject<string[]>();
|
||||
}
|
||||
attribute.AllowDefault = ProcessEnum<BoolDefault>(template, "AllowDefault");
|
||||
attribute.ChoiceCase = ProcessEnum<CaseNormalization>(template, "ChoiceCase");
|
||||
attribute.ChoiceFormat = (string)template["ChoiceFormat"];
|
||||
attribute.ChoiceLastSeparator = (string)template["ChoiceLastSeparator"];
|
||||
attribute.ChoiceParens = ProcessEnum<BoolDefault>(template, "ChoiceParens");
|
||||
attribute.ChoiceSeparator = (string)template["ChoiceSeparator"];
|
||||
attribute.ChoiceStyle = ProcessEnum<ChoiceStyleOptions>(template, "ChoiceStyle");
|
||||
attribute.Feedback = ProcessEnum<FeedbackOptions>(template, "Feedback");
|
||||
attribute.FieldCase = ProcessEnum<CaseNormalization>(template, "FieldCase");
|
||||
attribute.LastSeparator = (string)template["LastSeparator"];
|
||||
attribute.Separator = (string)template["Separator"];
|
||||
attribute.ValueCase = ProcessEnum<CaseNormalization>(template, "ValueCase");
|
||||
return attribute;
|
||||
}
|
||||
|
||||
protected DescribeAttribute ProcessDescription(JObject schema, string defaultDesc)
|
||||
{
|
||||
// Simple string or object
|
||||
// {Description=, Image=, Title=, SubTitle=}
|
||||
var desc = new DescribeAttribute();
|
||||
JToken jdesc;
|
||||
if (schema.TryGetValue("Describe", out jdesc))
|
||||
{
|
||||
if (jdesc.Type == JTokenType.String)
|
||||
{
|
||||
desc.Description = jdesc.Value<string>();
|
||||
}
|
||||
else
|
||||
{
|
||||
var jdescription = jdesc["Description"];
|
||||
if (jdescription != null)
|
||||
{
|
||||
desc.Description = jdescription.Value<string>();
|
||||
}
|
||||
else
|
||||
{
|
||||
desc.Description = defaultDesc;
|
||||
}
|
||||
|
||||
var jimage = jdesc["Image"];
|
||||
if (jimage != null)
|
||||
{
|
||||
desc.Image = jimage.Value<string>();
|
||||
}
|
||||
|
||||
var jtitle = jdesc["Title"];
|
||||
if (jtitle != null)
|
||||
{
|
||||
desc.Title = jtitle.Value<string>();
|
||||
}
|
||||
|
||||
var jsubTitle = jdesc["SubTitle"];
|
||||
if (jsubTitle != null)
|
||||
{
|
||||
desc.SubTitle = jsubTitle.Value<string>();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
desc.Description = defaultDesc;
|
||||
}
|
||||
return desc;
|
||||
}
|
||||
|
||||
protected T ProcessEnum<T>(JToken template, string name)
|
||||
{
|
||||
T result = default(T);
|
||||
var value = template[name];
|
||||
if (value != null)
|
||||
{
|
||||
result = (T)Enum.Parse(typeof(T), (string)value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
internal static bool IsType(JObject schema, string type)
|
||||
{
|
||||
bool isType = false;
|
||||
var jtype = schema["type"];
|
||||
if (jtype != null)
|
||||
{
|
||||
if (jtype is JArray)
|
||||
{
|
||||
isType = jtype.Values().Contains(type);
|
||||
}
|
||||
else
|
||||
{
|
||||
isType = (string)jtype == type;
|
||||
}
|
||||
}
|
||||
return isType;
|
||||
}
|
||||
|
||||
internal static bool IsPrimitiveType(JObject schema)
|
||||
{
|
||||
var isPrimitive = schema["enum"] != null && schema["enum"].Any();
|
||||
if (!isPrimitive)
|
||||
{
|
||||
isPrimitive =
|
||||
IsType(schema, "boolean")
|
||||
|| IsType(schema, "integer")
|
||||
|| IsType(schema, "number")
|
||||
|| IsType(schema, "string")
|
||||
|| (schema["DateTime"] != null && (bool)schema["DateTime"]);
|
||||
}
|
||||
return isPrimitive;
|
||||
}
|
||||
|
||||
internal static JObject ElementSchema(JObject schema)
|
||||
{
|
||||
JObject result = schema;
|
||||
if (IsType(schema, "array"))
|
||||
{
|
||||
var items = schema["items"];
|
||||
if (items is JArray)
|
||||
{
|
||||
result = (JObject)((JArray)items).First();
|
||||
}
|
||||
else
|
||||
{
|
||||
result = (JObject)items;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected FormBuilderJson _builder;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,541 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using Microsoft.Bot.Builder.V3Bridge.Dialogs;
|
||||
using Microsoft.Bot.Builder.V3Bridge.FormFlow.Advanced;
|
||||
using Microsoft.CodeAnalysis.CSharp.Scripting;
|
||||
using Microsoft.CodeAnalysis.Scripting;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.FormFlow.Json
|
||||
{
|
||||
// No need to document overrides of interface methods
|
||||
#pragma warning disable CS1591
|
||||
|
||||
#region Documentation
|
||||
/// <summary>Build a form by specifying messages, fields and confirmations through JSON Schema or programatically.</summary>
|
||||
/// <remarks>
|
||||
/// Define a form via [JSON Schema](http://json-schema.org/latest/json-schema-validation.html)
|
||||
/// with optional additional annotations that correspond to the attributes provided for C#.
|
||||
/// %FormFlow makes use of a number of standard [JSON Schema](http://json-schema.org/latest/json-schema-validation.html) keywords include:
|
||||
/// * `type` -- Defines the fields type.
|
||||
/// * `enum` -- Defines the possible field values.
|
||||
/// * `minimum` -- Defines the minimum allowed value as described in <see cref="NumericAttribute"/>.
|
||||
/// * `maximum` -- Defines the maximum allowed value as described in <see cref="NumericAttribute"/>.
|
||||
/// * `required` -- Defines what fields are required.
|
||||
/// * `pattern` -- For string fields will be used to validate the entered pattern as described in <see cref="PatternAttribute"/>.
|
||||
///
|
||||
/// Templates and prompts use the same vocabulary as <see cref="TemplateAttribute"/> and <see cref="PromptAttribute"/>.
|
||||
/// The property names are the same and the values are the same as those in the underlying C# enumeration.
|
||||
/// For example to define a template to override the <see cref="TemplateUsage.NotUnderstood"/> template
|
||||
/// and specify a TemplateBaseAttribute.ChoiceStyle, you would put this in your schema:
|
||||
/// ~~~
|
||||
/// "Templates":{ "NotUnderstood": { Patterns: ["I don't get it"], "ChoiceStyle":"Auto"}}
|
||||
/// ~~~
|
||||
///
|
||||
/// %Extensions defined at the root fo the schema
|
||||
/// * `OnCompletion: script` -- C# script with arguments (<see cref="IDialogContext"/> context, JObject state) for completing form.
|
||||
/// * `References: [assemblyReference, ...]` -- Define references to include in scripts. Paths should be absolute, or relative to the current directory. By default Microsoft.Bot.Builder.V3Bridge.dll is included.
|
||||
/// * `Imports: [import, ...]` -- Define imports to include in scripts with usings. By default these namespaces are included: Microsoft.Bot.Builder.V3Bridge, Microsoft.Bot.Builder.V3Bridge.Dialogs, Microsoft.Bot.Builder.V3Bridge.FormFlow, Microsoft.Bot.Builder.V3Bridge.FormFlow.Advanced, System.Collections.Generic, System.Linq
|
||||
///
|
||||
/// %Extensions defined at the root of a schema or as a peer of the "type" property.
|
||||
/// * `Templates:{TemplateUsage: { Patterns:[string, ...], <args> }, ...}` -- Define templates.
|
||||
/// * `Prompt: { Patterns:[string, ...] <args>}` -- Define a prompt.
|
||||
///
|
||||
/// %Extensions that are found in a property description as peers to the "type" property of a JSON Schema.
|
||||
/// * `DateTime:bool` -- Marks a field as being a DateTime field.
|
||||
/// * `Describe:string` -- Description of a field as described in <see cref="DescribeAttribute"/>.
|
||||
/// * `Terms:[string,...]` -- Regular expressions for matching a field value as described in <see cref="TermsAttribute"/>.
|
||||
/// * `MaxPhrase:int` -- This will run your terms through <see cref="Language.GenerateTerms(string, int)"/> to expand them.
|
||||
/// * `Values:{ string: {Describe:string, Terms:[string, ...], MaxPhrase}, ...}` -- The string must be found in the types "enum" and this allows you to override the automatically generated descriptions and terms. If MaxPhrase is specified the terms are passed through <see cref="Language.GenerateTerms(string, int)"/>.
|
||||
/// * `Active:script` -- C# script with arguments (JObject state)->bool to test to see if field/message/confirm is active.
|
||||
/// * `Validate:script` -- C# script with arguments (JObject state, object value)->ValidateResult for validating a field value.
|
||||
/// * `Define:script` -- C# script with arguments (JObject state, Field<JObject> field) for dynamically defining a field.
|
||||
/// * `Before:[confirm|message, ...]` -- Messages or confirmations before the containing field.
|
||||
/// * `After:[confirm|message, ...]` -- Messages or confirmations after the containing field.
|
||||
/// * `{Confirm:script|[string, ...], ...templateArgs}` -- With Before/After define a confirmation through either C# script with argument (JObject state) or through a set of patterns that will be randomly selected with optional template arguments.
|
||||
/// * `{Message:script|[string, ...] ...templateArgs}` -- With Before/After define a message through either C# script with argument (JObject state) or through a set of patterns that will be randomly selected with optional template arguments.
|
||||
/// * `Dependencies`:[string, ...]` -- Fields that this field, message or confirm depends on.
|
||||
///
|
||||
/// Scripts can be any C# code you would find in a method body. You can add references through "References" and using through "Imports". Special global variables include:
|
||||
/// * `choice` -- internal dispatch for script to execute.
|
||||
/// * `state` -- JObject form state bound for all scripts.
|
||||
/// * `ifield` -- <see cref="IField{JObject}"/> to allow reasoning over the current field for all scripts except %Message/Confirm prompt builders.
|
||||
/// * `value` -- object value to be validated for Validate.
|
||||
/// * `field` -- <see cref="Field{JObject}"/> to allow dynamically updating a field in Define.
|
||||
/// * `context` -- <see cref="IDialogContext"/> context to allow posting results in OnCompletion.
|
||||
///
|
||||
/// %Fields defined through this class have the same ability to extend or override the definitions
|
||||
/// programatically as any other field. They can also be localized in the same way.
|
||||
/// </remarks>
|
||||
#endregion
|
||||
public sealed class FormBuilderJson : FormBuilderBase<JObject>
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a JSON form builder.
|
||||
/// </summary>
|
||||
/// <param name="schema">JSON Schema that defines form.</param>
|
||||
public FormBuilderJson(JObject schema)
|
||||
{
|
||||
_schema = schema;
|
||||
ProcessOptions();
|
||||
ProcessOnCompletion();
|
||||
}
|
||||
|
||||
public override IForm<JObject> Build(Assembly resourceAssembly = null, string resourceName = null)
|
||||
{
|
||||
if (!_form.Fields.Any())
|
||||
{
|
||||
// No fieldss means add default field and confirmation
|
||||
AddRemainingFields();
|
||||
Confirm(new PromptAttribute(Configuration.Template(TemplateUsage.Confirmation)));
|
||||
}
|
||||
// Build all code into a single assembly and cache because assemblies have no GC.
|
||||
var builder = new StringBuilder("switch (choice) {");
|
||||
int choice = 1;
|
||||
var entries = new List<CallScript>();
|
||||
lock (_scripts)
|
||||
{
|
||||
foreach (var entry in _scripts)
|
||||
{
|
||||
if (entry.Value.Script == null)
|
||||
{
|
||||
entry.Value.Choice = choice++;
|
||||
builder.AppendLine();
|
||||
builder.Append($"case {entry.Value.Choice}: {{{entry.Key}}}; break;");
|
||||
entries.Add(entry.Value);
|
||||
}
|
||||
}
|
||||
if (entries.Any())
|
||||
{
|
||||
// Define does not need to return a result.
|
||||
builder.AppendLine();
|
||||
builder.AppendLine("}");
|
||||
builder.Append("return null;");
|
||||
var fun = Compile<ScriptGlobals, object>(builder.ToString());
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
entry.Script = fun;
|
||||
}
|
||||
}
|
||||
}
|
||||
return base.Build(resourceAssembly, resourceName);
|
||||
}
|
||||
|
||||
public override IFormBuilder<JObject> Field(string name, ActiveDelegate<JObject> active = null, ValidateAsyncDelegate<JObject> validate = null)
|
||||
{
|
||||
var field = new FieldJson(this, name);
|
||||
field.SetActive(active);
|
||||
field.SetValidate(validate);
|
||||
AddSteps(field.Before);
|
||||
Field(field);
|
||||
AddSteps(field.After);
|
||||
return this;
|
||||
}
|
||||
|
||||
public override IFormBuilder<JObject> Field(string name, string prompt, ActiveDelegate<JObject> active = null, ValidateAsyncDelegate<JObject> validate = null)
|
||||
{
|
||||
return Field(name, new PromptAttribute(prompt), active, validate);
|
||||
}
|
||||
|
||||
public override IFormBuilder<JObject> Field(string name, PromptAttribute prompt, ActiveDelegate<JObject> active = null, ValidateAsyncDelegate<JObject> validate = null)
|
||||
{
|
||||
var field = new FieldJson(this, name);
|
||||
field.SetPrompt(prompt);
|
||||
if (active != null)
|
||||
{
|
||||
field.SetActive(active);
|
||||
}
|
||||
if (validate != null)
|
||||
{
|
||||
field.SetValidate(validate);
|
||||
}
|
||||
return Field(field);
|
||||
}
|
||||
|
||||
public override IFormBuilder<JObject> AddRemainingFields(IEnumerable<string> exclude = null)
|
||||
{
|
||||
var exclusions = (exclude == null ? Array.Empty<string>() : exclude.ToArray());
|
||||
var fields = new List<string>();
|
||||
Fields(_schema, null, fields);
|
||||
foreach (var field in fields)
|
||||
{
|
||||
if (!exclusions.Contains(field) && !HasField(field))
|
||||
{
|
||||
Field(field);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
#region Class specific methods
|
||||
public JObject Schema
|
||||
{
|
||||
get { return _schema; }
|
||||
}
|
||||
|
||||
internal MessageDelegate<JObject> MessageScript(string script)
|
||||
{
|
||||
return script != null ? new MessageDelegate<JObject>(AddScript(null, script).MessageScript) : null;
|
||||
}
|
||||
|
||||
internal ActiveDelegate<JObject> ActiveScript(IField<JObject> field, string script)
|
||||
{
|
||||
return script != null ? new ActiveDelegate<JObject>(AddScript(field, script).ActiveScript) : null;
|
||||
}
|
||||
|
||||
internal DefineAsyncDelegate<JObject> DefineScript(IField<JObject> field, string script)
|
||||
{
|
||||
return script != null ? new DefineAsyncDelegate<JObject>(AddScript(field, script).DefineScriptAsync) : null;
|
||||
}
|
||||
|
||||
internal ValidateAsyncDelegate<JObject> ValidateScript(IField<JObject> field, string script)
|
||||
{
|
||||
return script != null ? new ValidateAsyncDelegate<JObject>(AddScript(field, script).ValidateScriptAsync) : null;
|
||||
}
|
||||
|
||||
internal NextDelegate<JObject> NextScript(IField<JObject> field, string script)
|
||||
{
|
||||
return script != null ? new NextDelegate<JObject>(AddScript(field, script).NextScript) : null;
|
||||
}
|
||||
|
||||
internal OnCompletionAsyncDelegate<JObject> OnCompletionScript(string script)
|
||||
{
|
||||
return script != null ? new OnCompletionAsyncDelegate<JObject>(AddScript(null, script).OnCompletionAsync) : null;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Implementation
|
||||
private void ProcessOptions()
|
||||
{
|
||||
JToken references;
|
||||
var assemblies = new List<string>() { "Microsoft.Bot.Builder.V3Bridge.dll" };
|
||||
if (_schema.TryGetValue("References", out references))
|
||||
{
|
||||
foreach (JToken reference in references.Children())
|
||||
{
|
||||
assemblies.Add((string)reference);
|
||||
}
|
||||
}
|
||||
JToken importsChildren;
|
||||
var imports = new List<string>();
|
||||
if (_schema.TryGetValue("Imports", out importsChildren))
|
||||
{
|
||||
foreach (JToken import in importsChildren.Children())
|
||||
{
|
||||
imports.Add((string)import);
|
||||
}
|
||||
}
|
||||
var dir = Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath);
|
||||
_options = CodeAnalysis.Scripting.ScriptOptions.Default
|
||||
.AddReferences((from assembly in assemblies select System.IO.Path.Combine(dir, assembly)).ToArray())
|
||||
.AddImports("Microsoft.Bot.Builder.V3Bridge", "Microsoft.Bot.Builder.V3Bridge.Dialogs",
|
||||
"Microsoft.Bot.Builder.V3Bridge.FormFlow", "Microsoft.Bot.Builder.V3Bridge.FormFlow.Advanced",
|
||||
"System.Collections.Generic", "System.Linq")
|
||||
.AddImports(imports.ToArray());
|
||||
}
|
||||
|
||||
private void ProcessOnCompletion()
|
||||
{
|
||||
if (_schema["OnCompletion"] != null)
|
||||
{
|
||||
OnCompletion(OnCompletionScript((string)_schema["OnCompletion"]));
|
||||
}
|
||||
}
|
||||
|
||||
private CallScript AddScript(IField<JObject> field, string script)
|
||||
{
|
||||
CallScript call;
|
||||
lock (_scripts)
|
||||
{
|
||||
if (!_scripts.TryGetValue(script, out call))
|
||||
{
|
||||
call = new CallScript { Field = field };
|
||||
_scripts[script] = call;
|
||||
}
|
||||
}
|
||||
return call;
|
||||
}
|
||||
|
||||
// NOTE: Compiling code creates an assembly which cannot be GC whereas EvaluateAsync does not.
|
||||
private ScriptRunner<R> Compile<G, R>(string code)
|
||||
{
|
||||
try
|
||||
{
|
||||
var script = CSharpScript.Create<R>(code, _options, typeof(G));
|
||||
return script.CreateDelegate();
|
||||
}
|
||||
catch (Microsoft.CodeAnalysis.Scripting.CompilationErrorException ex)
|
||||
{
|
||||
throw CompileException(ex, code);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<T> EvaluateAsync<T>(string code, object globals)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await CSharpScript.EvaluateAsync<T>(code, _options, globals);
|
||||
}
|
||||
catch (Microsoft.CodeAnalysis.Scripting.CompilationErrorException ex)
|
||||
{
|
||||
throw CompileException(ex, code);
|
||||
}
|
||||
}
|
||||
|
||||
private Exception CompileException(CompilationErrorException ex, string code)
|
||||
{
|
||||
Exception result = ex;
|
||||
var match = System.Text.RegularExpressions.Regex.Match(ex.Message, @"\(\s*(?<line>\d+)\s*,\s*(?<column>\d+)\s*\)\s*:\s*(?<message>.*)");
|
||||
if (match.Success)
|
||||
{
|
||||
var lineNumber = int.Parse(match.Groups["line"].Value) - 1;
|
||||
var column = int.Parse(match.Groups["column"].Value) - 1;
|
||||
var line = code.Split('\n')[lineNumber];
|
||||
var minCol = Math.Max(0, column - 20);
|
||||
var maxCol = Math.Min(line.Length, column + 20);
|
||||
var msg = line.Substring(minCol, column - minCol) + "^" + line.Substring(column, maxCol - column);
|
||||
result = new ArgumentException(match.Groups["message"].Value + ": " + msg);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void AddSteps(IEnumerable<FieldJson.MessageOrConfirmation> steps)
|
||||
{
|
||||
foreach (var step in steps)
|
||||
{
|
||||
var active = ActiveScript(null, step.ActiveScript);
|
||||
if (step.IsMessage)
|
||||
{
|
||||
if (step.MessageScript != null)
|
||||
{
|
||||
Message(MessageScript(step.MessageScript), active, step.Dependencies);
|
||||
}
|
||||
else
|
||||
{
|
||||
Message(step.Prompt, active, step.Dependencies);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (step.MessageScript != null)
|
||||
{
|
||||
Confirm(MessageScript(step.MessageScript), active, step.Dependencies);
|
||||
}
|
||||
else
|
||||
{
|
||||
Confirm(step.Prompt, active, step.Dependencies);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Fields(JObject schema, string prefix, IList<string> fields)
|
||||
{
|
||||
if (schema["properties"] != null)
|
||||
{
|
||||
foreach (JProperty property in schema["properties"])
|
||||
{
|
||||
var path = (prefix == null ? property.Name : $"{prefix}.{property.Name}");
|
||||
var childSchema = (JObject)property.Value;
|
||||
var eltSchema = FieldJson.ElementSchema(childSchema);
|
||||
if (FieldJson.IsPrimitiveType(eltSchema))
|
||||
{
|
||||
fields.Add(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
Fields(childSchema, path, fields);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void FieldPaths(Type type, string path, List<string> paths)
|
||||
{
|
||||
var newPath = (path == "" ? path : path + ".");
|
||||
foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Instance).Where(f => !f.IsDefined(typeof(IgnoreFieldAttribute))))
|
||||
{
|
||||
TypePaths(field.FieldType, newPath + field.Name, paths);
|
||||
}
|
||||
|
||||
foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
|
||||
{
|
||||
if (property.CanRead && property.CanWrite)
|
||||
{
|
||||
TypePaths(property.PropertyType, newPath + property.Name, paths);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TypePaths(Type type, string path, List<string> paths)
|
||||
{
|
||||
if (type.IsClass)
|
||||
{
|
||||
if (type == typeof(string))
|
||||
{
|
||||
paths.Add(path);
|
||||
}
|
||||
else if (type.IsIEnumerable())
|
||||
{
|
||||
var elt = type.GetGenericElementType();
|
||||
if (elt.IsEnum)
|
||||
{
|
||||
paths.Add(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: What to do about enumerations of things other than enums?
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
FieldPaths(type, path, paths);
|
||||
}
|
||||
}
|
||||
else if (type.IsEnum)
|
||||
{
|
||||
paths.Add(path);
|
||||
}
|
||||
else if (type == typeof(bool))
|
||||
{
|
||||
paths.Add(path);
|
||||
}
|
||||
else if (type.IsIntegral())
|
||||
{
|
||||
paths.Add(path);
|
||||
}
|
||||
else if (type.IsDouble())
|
||||
{
|
||||
paths.Add(path);
|
||||
}
|
||||
else if (type.IsNullable() && type.IsValueType)
|
||||
{
|
||||
paths.Add(path);
|
||||
}
|
||||
else if (type == typeof(DateTime))
|
||||
{
|
||||
paths.Add(path);
|
||||
}
|
||||
}
|
||||
|
||||
private delegate Task<object> CallAsyncDelegate(ScriptGlobals globals);
|
||||
private class CallScript
|
||||
{
|
||||
public int Choice;
|
||||
public ScriptRunner<object> Script;
|
||||
public IField<JObject> Field;
|
||||
|
||||
public async Task<PromptAttribute> MessageScript(JObject state)
|
||||
{
|
||||
return (PromptAttribute)await Script(new ScriptGlobals { choice = Choice, state = state, ifield = Field });
|
||||
}
|
||||
|
||||
public bool ActiveScript(JObject state)
|
||||
{
|
||||
return (bool)Script(new ScriptGlobals { choice = Choice, state = state, ifield = Field }).Result;
|
||||
}
|
||||
|
||||
public async Task<ValidateResult> ValidateScriptAsync(JObject state, object value)
|
||||
{
|
||||
return (ValidateResult)await Script(new ScriptGlobals { choice = Choice, state = state, value = value, ifield = Field });
|
||||
}
|
||||
|
||||
public async Task<bool> DefineScriptAsync(JObject state, Field<JObject> field)
|
||||
{
|
||||
return (bool)await Script(new ScriptGlobals { choice = Choice, state = state, field = field, ifield = Field });
|
||||
}
|
||||
|
||||
public NextStep NextScript(object value, JObject state)
|
||||
{
|
||||
return (NextStep)Script(new ScriptGlobals { choice = Choice, value = value, state = state, ifield = Field }).Result;
|
||||
}
|
||||
|
||||
public async Task OnCompletionAsync(IDialogContext context, JObject state)
|
||||
{
|
||||
await Script(new ScriptGlobals { choice = Choice, context = context, state = state, ifield = Field });
|
||||
}
|
||||
}
|
||||
|
||||
private readonly JObject _schema;
|
||||
private ScriptOptions _options;
|
||||
private static Dictionary<string, CallScript> _scripts = new Dictionary<string, CallScript>();
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.FormFlow.Advanced
|
||||
{
|
||||
/// <summary>
|
||||
/// Global values to pass into scripts defined using <see cref="Microsoft.Bot.Builder.V3Bridge.FormFlow.Json.FormBuilderJson"/>.
|
||||
/// </summary>
|
||||
public class ScriptGlobals
|
||||
{
|
||||
/// <summary>
|
||||
/// Which script to execute.
|
||||
/// </summary>
|
||||
public int choice;
|
||||
|
||||
/// <summary>
|
||||
/// Current form state.
|
||||
/// </summary>
|
||||
public JObject state;
|
||||
|
||||
/// <summary>
|
||||
/// Value to be validated.
|
||||
/// </summary>
|
||||
public object value;
|
||||
|
||||
/// <summary>
|
||||
/// Current field if any.
|
||||
/// </summary>
|
||||
public IField<JObject> ifield;
|
||||
|
||||
/// <summary>
|
||||
/// Field to be dynamically defined.
|
||||
/// </summary>
|
||||
public Field<JObject> field;
|
||||
|
||||
/// <summary>
|
||||
/// Dialog context for OnCompletionAsync handlers.
|
||||
/// </summary>
|
||||
public IDialogContext context;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0"?>
|
||||
<package xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
||||
<metadata xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<id>Microsoft.Bot.Builder.FormFlow.Json</id>
|
||||
<version>$version$</version>
|
||||
<authors>Microsoft</authors>
|
||||
<owners>microsoft, BotFramework, nugetbotbuilder </owners>
|
||||
<copyright>© Microsoft Corporation. All rights reserved.</copyright>
|
||||
<requireLicenseAcceptance>true</requireLicenseAcceptance>
|
||||
<licenseUrl>https://github.com/Microsoft/BotBuilder/blob/master/LICENSE</licenseUrl>
|
||||
<description>
|
||||
Microsoft Bot Builder extension for specifying a guided FormFlow dialog using an extended JSON Schema. Most of the annotations are declarative but you can
|
||||
also include C# code fragments that are dynamically compiled.
|
||||
</description>
|
||||
<projectUrl>https://github.com/Microsoft/BotBuilder</projectUrl>
|
||||
<iconUrl>http://docs.botframework.com/images/bot_icon.png</iconUrl>
|
||||
<summary>A Microsoft Bot Builder extension for declaratively specifying FormFlow dialogs using JSON Schema.</summary>
|
||||
<language>en-US</language>
|
||||
<dependencies>
|
||||
<dependency id="Microsoft.Bot.Builder" version="$builder$" />
|
||||
<dependency id="Newtonsoft.Json" version="8.0.3" />
|
||||
<dependency id="Microsoft.CodeAnalysis.CSharp.Scripting" version="1.2.2" />
|
||||
</dependencies>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="bin\release\Microsoft.Bot.Builder.FormFlow.Json.dll" target="lib\net46"/>
|
||||
<file src="bin\release\Microsoft.Bot.Builder.FormFlow.Json.xml" target="lib\net46" />
|
||||
<file src="bin\release\Microsoft.Bot.Builder.FormFlow.Json.pdb" target="lib\net46" />
|
||||
<file src="*.cs" target="src" />
|
||||
</files>
|
||||
</package>
|
|
@ -0,0 +1,142 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{8674F271-4437-4178-B4E2-15F91B322E5D}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Microsoft.Bot.Builder.V3Bridge.FormFlow.Json</RootNamespace>
|
||||
<AssemblyName>Microsoft.Bot.Builder.V3Bridge.FormFlow.Json</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<DocumentationFile>bin\Debug\Microsoft.Bot.Builder.V3Bridge.FormFlow.Json.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<DocumentationFile>bin\Release\Microsoft.Bot.Builder.V3Bridge.FormFlow.Json.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.CodeAnalysis, Version=1.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\packages\Microsoft.CodeAnalysis.Common.1.2.2\lib\net45\Microsoft.CodeAnalysis.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.CodeAnalysis.CSharp, Version=1.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\packages\Microsoft.CodeAnalysis.CSharp.1.2.2\lib\net45\Microsoft.CodeAnalysis.CSharp.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.CodeAnalysis.CSharp.Scripting, Version=1.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\packages\Microsoft.CodeAnalysis.CSharp.Scripting.1.2.2\lib\dotnet\Microsoft.CodeAnalysis.CSharp.Scripting.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.CodeAnalysis.Scripting, Version=1.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\packages\Microsoft.CodeAnalysis.Scripting.Common.1.2.2\lib\dotnet\Microsoft.CodeAnalysis.Scripting.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.IdentityModel.Logging, Version=5.2.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\packages\Microsoft.IdentityModel.Logging.5.2.1\lib\net451\Microsoft.IdentityModel.Logging.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.IdentityModel.Protocols, Version=5.2.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\packages\Microsoft.IdentityModel.Protocols.5.2.1\lib\net451\Microsoft.IdentityModel.Protocols.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect, Version=5.2.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\packages\Microsoft.IdentityModel.Protocols.OpenIdConnect.5.2.1\lib\net451\Microsoft.IdentityModel.Protocols.OpenIdConnect.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.IdentityModel.Tokens, Version=5.2.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\packages\Microsoft.IdentityModel.Tokens.5.2.1\lib\net451\Microsoft.IdentityModel.Tokens.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Rest.ClientRuntime, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\packages\Microsoft.Rest.ClientRuntime.2.3.10\lib\net452\Microsoft.Rest.ClientRuntime.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.AppContext, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\packages\System.AppContext.4.0.0\lib\net46\System.AppContext.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Collections.Immutable, Version=1.1.37.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\packages\System.Collections.Immutable.1.1.37\lib\dotnet\System.Collections.Immutable.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Diagnostics.StackTrace, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\packages\System.Diagnostics.StackTrace.4.0.0\lib\net46\System.Diagnostics.StackTrace.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.IdentityModel.Tokens.Jwt, Version=5.2.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\packages\System.IdentityModel.Tokens.Jwt.5.2.1\lib\net451\System.IdentityModel.Tokens.Jwt.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.IO.FileSystem, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\packages\System.IO.FileSystem.4.0.0\lib\net46\System.IO.FileSystem.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.IO.FileSystem.Primitives, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\packages\System.IO.FileSystem.Primitives.4.0.0\lib\net46\System.IO.FileSystem.Primitives.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Reflection.Metadata, Version=1.2.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\packages\System.Reflection.Metadata.1.2.0\lib\portable-net45+win8\System.Reflection.Metadata.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Runtime.Serialization" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="FieldJson.cs" />
|
||||
<Compile Include="FormBuilderJson.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Microsoft.Bot.Schema\Microsoft.Bot.Schema.csproj">
|
||||
<Project>{c1f54cdc-ad1d-45bb-8f7d-f49e411afaf1}</Project>
|
||||
<Name>Microsoft.Bot.Schema</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Microsoft.Bot.Builder.V3Bridge\Microsoft.Bot.Builder.V3Bridge.csproj">
|
||||
<Project>{cdfec7d6-847e-4c13-956b-0a960ae3eb60}</Project>
|
||||
<Name>Microsoft.Bot.Builder.V3Bridge</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="app.config">
|
||||
<SubType>Designer</SubType>
|
||||
</None>
|
||||
<None Include="createpackage.cmd" />
|
||||
<None Include="Microsoft.Bot.Builder.FormFlow.Json.nuspec" />
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Analyzer Include="..\..\..\packages\Microsoft.CodeAnalysis.Analyzers.1.1.0\analyzers\dotnet\cs\Microsoft.CodeAnalysis.Analyzers.dll" />
|
||||
<Analyzer Include="..\..\..\packages\Microsoft.CodeAnalysis.Analyzers.1.1.0\analyzers\dotnet\cs\Microsoft.CodeAnalysis.CSharp.Analyzers.dll" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
|
@ -0,0 +1,44 @@
|
|||
using System.Reflection;
|
||||
using System.Resources;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("Microsoft.Bot.Builder.V3Bridge.FormFlow.Json")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("Microsoft.Bot.Builder.V3Bridge.FormFlow.Json")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2016")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("8674f271-4437-4178-b4e2-15f91b322e5d")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("3.14.0.6")]
|
||||
[assembly: AssemblyFileVersion("3.14.0.6")]
|
||||
|
||||
[assembly: InternalsVisibleTo("Microsoft.Bot.Builder.V3Bridge.Tests")]
|
||||
//[assembly: InternalsVisibleTo("Microsoft.Bot.Sample.Tests")]
|
||||
[assembly: NeutralResourcesLanguage("en")]
|
||||
|
||||
//[assembly: AssemblyKeyFileAttribute(@"..\\..\\..\\buildtools\\35MSSharedLib1024.snk")]
|
||||
//[assembly: AssemblyDelaySignAttribute(true)]
|
|
@ -0,0 +1,43 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-10.0.0.0" newVersion="10.0.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.AppContext" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.1.2.0" newVersion="4.1.2.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.IO.FileSystem" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Diagnostics.StackTrace" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.1.0.0" newVersion="4.1.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.IO.FileSystem.Primitives" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.IdentityModel.Tokens" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-5.2.1.0" newVersion="5.2.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.IdentityModel.Tokens.Jwt" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-5.2.1.0" newVersion="5.2.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.IdentityModel.Protocols" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-5.2.1.0" newVersion="5.2.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.IdentityModel.Protocols.OpenIdConnect" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-5.2.1.0" newVersion="5.2.1.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" /></startup></configuration>
|
|
@ -0,0 +1,14 @@
|
|||
@echo off
|
||||
echo *** Building Microsoft.Bot.Builder.FormFlow.Json
|
||||
setlocal
|
||||
setlocal enabledelayedexpansion
|
||||
setlocal enableextensions
|
||||
set errorlevel=0
|
||||
mkdir ..\nuget
|
||||
erase /s ..\nuget\Microsoft.Bot.Builder.FormFlow.Json*nupkg
|
||||
msbuild /property:Configuration=release Microsoft.Bot.Builder.FormFlow.Json.csproj
|
||||
for /f %%v in ('powershell -noprofile "(Get-Command .\bin\release\Microsoft.Bot.Builder.FormFlow.Json.dll).FileVersionInfo.FileVersion"') do set version=%%v
|
||||
for /f %%v in ('powershell -noprofile "(Get-Command .\bin\release\Microsoft.Bot.Builder.dll).FileVersionInfo.FileVersion"') do set builder=%%v
|
||||
..\..\packages\NuGet.CommandLine.4.1.0\tools\NuGet.exe pack Microsoft.Bot.Builder.FormFlow.Json.nuspec -symbols -properties version=%version%;builder=%builder% -OutputDirectory ..\nuget
|
||||
echo *** Finished building Microsoft.Bot.Builder.FormFlow.Json
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.CodeAnalysis.Analyzers" version="1.1.0" targetFramework="net46" />
|
||||
<package id="Microsoft.CodeAnalysis.Common" version="1.2.2" targetFramework="net46" />
|
||||
<package id="Microsoft.CodeAnalysis.CSharp" version="1.2.2" targetFramework="net46" />
|
||||
<package id="Microsoft.CodeAnalysis.CSharp.Scripting" version="1.2.2" targetFramework="net46" />
|
||||
<package id="Microsoft.CodeAnalysis.Scripting" version="1.2.2" targetFramework="net46" />
|
||||
<package id="Microsoft.CodeAnalysis.Scripting.Common" version="1.2.2" targetFramework="net46" />
|
||||
<package id="Microsoft.IdentityModel.Logging" version="5.2.1" targetFramework="net461" />
|
||||
<package id="Microsoft.IdentityModel.Protocols" version="5.2.1" targetFramework="net461" />
|
||||
<package id="Microsoft.IdentityModel.Protocols.OpenIdConnect" version="5.2.1" targetFramework="net461" />
|
||||
<package id="Microsoft.IdentityModel.Tokens" version="5.2.1" targetFramework="net461" />
|
||||
<package id="Microsoft.Rest.ClientRuntime" version="2.3.10" targetFramework="net461" />
|
||||
<package id="Newtonsoft.Json" version="10.0.3" targetFramework="net461" />
|
||||
<package id="System.AppContext" version="4.0.0" targetFramework="net46" />
|
||||
<package id="System.Collections" version="4.0.10" targetFramework="net46" />
|
||||
<package id="System.Collections.Immutable" version="1.1.37" targetFramework="net46" />
|
||||
<package id="System.Diagnostics.Debug" version="4.0.10" targetFramework="net46" />
|
||||
<package id="System.Diagnostics.StackTrace" version="4.0.0" targetFramework="net46" />
|
||||
<package id="System.Diagnostics.Tools" version="4.0.0" targetFramework="net46" />
|
||||
<package id="System.Globalization" version="4.0.10" targetFramework="net46" />
|
||||
<package id="System.IdentityModel.Tokens.Jwt" version="5.2.1" targetFramework="net461" />
|
||||
<package id="System.IO" version="4.0.10" targetFramework="net46" />
|
||||
<package id="System.IO.FileSystem" version="4.0.0" targetFramework="net46" />
|
||||
<package id="System.IO.FileSystem.Primitives" version="4.0.0" targetFramework="net46" />
|
||||
<package id="System.Linq" version="4.0.0" targetFramework="net46" />
|
||||
<package id="System.Linq.Expressions" version="4.0.10" targetFramework="net46" />
|
||||
<package id="System.Reflection" version="4.0.10" targetFramework="net46" />
|
||||
<package id="System.Reflection.Extensions" version="4.0.0" targetFramework="net46" />
|
||||
<package id="System.Reflection.Metadata" version="1.2.0" targetFramework="net46" />
|
||||
<package id="System.Resources.ResourceManager" version="4.0.0" targetFramework="net46" />
|
||||
<package id="System.Runtime" version="4.0.20" targetFramework="net46" />
|
||||
<package id="System.Runtime.Extensions" version="4.0.10" targetFramework="net46" />
|
||||
<package id="System.Runtime.Handles" version="4.0.0" targetFramework="net46" />
|
||||
<package id="System.Runtime.InteropServices" version="4.0.20" targetFramework="net46" />
|
||||
<package id="System.Text.Encoding" version="4.0.0" targetFramework="net46" />
|
||||
<package id="System.Threading" version="4.0.10" targetFramework="net46" />
|
||||
<package id="System.Threading.Tasks" version="4.0.10" targetFramework="net46" />
|
||||
</packages>
|
|
@ -0,0 +1,69 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.History
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for managing activity history.
|
||||
/// </summary>
|
||||
public interface IActivityManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Delete a specific conversation.
|
||||
/// </summary>
|
||||
/// <param name="channelId">Channel where conversation took place.</param>
|
||||
/// <param name="conversationId">Id of conversation to delete.</param>
|
||||
/// <param name="cancel">Cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
Task DeleteConversationAsync(string channelId, string conversationId, CancellationToken cancel = default(CancellationToken));
|
||||
|
||||
/// <summary>
|
||||
/// Delete any conversation records older than <paramref name="oldest"/>.
|
||||
/// </summary>
|
||||
/// <param name="oldest">Earliest remaining date in log.</param>
|
||||
/// <param name="cancel">Cancellation token.</param>
|
||||
Task DeleteBeforeAsync(DateTime oldest, CancellationToken cancel = default(CancellationToken));
|
||||
|
||||
/// <summary>
|
||||
/// Delete all activities involving <paramref name="userId"/>.
|
||||
/// </summary>
|
||||
/// <param name="userId">User to delete.</param>
|
||||
/// <param name="cancel">Cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
Task DeleteUserActivitiesAsync(string userId, CancellationToken cancel = default(CancellationToken));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using Microsoft.Bot.Schema;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.History
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for getting activities from some source.
|
||||
/// </summary>
|
||||
public interface IActivitySource
|
||||
{
|
||||
/// <summary>
|
||||
/// Produce an enumeration over conversation.
|
||||
/// </summary>
|
||||
/// <param name="channelId">Channel where conversation happened.</param>
|
||||
/// <param name="conversationId">Conversation within the channel.</param>
|
||||
/// <param name="oldest">Earliest time to include.</param>
|
||||
/// <returns>Enumeration over the recorded activities.</returns>
|
||||
/// <remarks>Activities are ordered by channel, then conversation, then time ascending.</remarks>
|
||||
IEnumerable<IActivity> Activities(string channelId, string conversationId, DateTime oldest = default(DateTime));
|
||||
|
||||
/// <summary>
|
||||
/// Walk over recorded activities and call a function on them.
|
||||
/// </summary>
|
||||
/// <param name="function">Function to apply to each actitivty.</param>
|
||||
/// <param name="channelId">ChannelId to filter on or null for no filter.</param>
|
||||
/// <param name="conversationId">ConversationId to filter on or null for no filter.</param>
|
||||
/// <param name="oldest">Oldest timestamp to include.</param>
|
||||
/// <param name="cancel">Cancellation token.</param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>Activities are ordered by channel, then conversation, then time ascending.</remarks>
|
||||
Task WalkActivitiesAsync(Func<IActivity, Task> function, string channelId = null, string conversationId = null, DateTime oldest = default(DateTime), CancellationToken cancel = default(CancellationToken));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0"?>
|
||||
<package xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
||||
<metadata xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<id>Microsoft.Bot.Builder.History</id>
|
||||
<version>$version$</version>
|
||||
<authors>Microsoft</authors>
|
||||
<owners>microsoft, BotFramework, nugetbotbuilder </owners>
|
||||
<copyright>© Microsoft Corporation. All rights reserved.</copyright>
|
||||
<requireLicenseAcceptance>true</requireLicenseAcceptance>
|
||||
<licenseUrl>https://github.com/Microsoft/BotBuilder/blob/master/LICENSE</licenseUrl>
|
||||
<description>
|
||||
Provide interfaces and classes for working with conversation histories in the Microsoft Bot Builder.
|
||||
In particular this includes facilities to walk across stored histories and replay them.
|
||||
</description>
|
||||
<projectUrl>https://github.com/Microsoft/BotBuilder</projectUrl>
|
||||
<iconUrl>http://docs.botframework.com/images/bot_icon.png</iconUrl>
|
||||
<summary>Microsoft History extensions for Microsoft.Bot.Builder.</summary>
|
||||
<language>en-US</language>
|
||||
<dependencies>
|
||||
<dependency id="Microsoft.Bot.Builder" version="$builder$"/>
|
||||
</dependencies>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="bin\release\Microsoft.Bot.Builder.History.dll" target="lib\net46"/>
|
||||
<file src="bin\release\Microsoft.Bot.Builder.History.xml" target="lib\net46" />
|
||||
<file src="bin\release\Microsoft.Bot.Builder.History.pdb" target="lib\net46" />
|
||||
<file src="**\*.cs" exclude="**\obj\**\*.cs" target="src" />
|
||||
</files>
|
||||
</package>
|
|
@ -0,0 +1,97 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{D2834D71-D7A2-451B-9A61-1488AF049526}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Microsoft.Bot.Builder.V3Bridge.History</RootNamespace>
|
||||
<AssemblyName>Microsoft.Bot.Builder.V3Bridge.History</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<DocumentationFile>bin\Debug\Microsoft.Bot.Builder.V3Bridge.History.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<DocumentationFile>bin\Release\Microsoft.Bot.Builder.V3Bridge.History.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.IdentityModel.Logging, Version=5.2.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\packages\Microsoft.IdentityModel.Logging.5.2.1\lib\net451\Microsoft.IdentityModel.Logging.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.IdentityModel.Protocols, Version=5.2.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\packages\Microsoft.IdentityModel.Protocols.5.2.1\lib\net451\Microsoft.IdentityModel.Protocols.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect, Version=5.2.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\packages\Microsoft.IdentityModel.Protocols.OpenIdConnect.5.2.1\lib\net451\Microsoft.IdentityModel.Protocols.OpenIdConnect.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.IdentityModel.Tokens, Version=5.2.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\packages\Microsoft.IdentityModel.Tokens.5.2.1\lib\net451\Microsoft.IdentityModel.Tokens.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Rest.ClientRuntime, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\packages\Microsoft.Rest.ClientRuntime.2.3.10\lib\net452\Microsoft.Rest.ClientRuntime.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.IdentityModel.Tokens.Jwt, Version=5.2.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\packages\System.IdentityModel.Tokens.Jwt.5.2.1\lib\net451\System.IdentityModel.Tokens.Jwt.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Runtime.Serialization" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="IActivityManager.cs" />
|
||||
<Compile Include="IActivitySource.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="ReplayTranscript.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Microsoft.Bot.Schema\Microsoft.Bot.Schema.csproj">
|
||||
<Project>{c1f54cdc-ad1d-45bb-8f7d-f49e411afaf1}</Project>
|
||||
<Name>Microsoft.Bot.Schema</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Microsoft.Bot.Builder.V3Bridge\Microsoft.Bot.Builder.V3Bridge.csproj">
|
||||
<Project>{cdfec7d6-847e-4c13-956b-0a960ae3eb60}</Project>
|
||||
<Name>Microsoft.Bot.Builder.V3Bridge</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="app.config" />
|
||||
<None Include="createpackage.cmd" />
|
||||
<None Include="Microsoft.Bot.Builder.History.nuspec" />
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
|
@ -0,0 +1,38 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("Microsoft.Bot.Builder.V3Bridge.History")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("Microsoft.Bot.Builder.V3Bridge.History")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2016")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("d2834d71-d7a2-451b-9a61-1488af049526")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("3.14.0.6")]
|
||||
[assembly: AssemblyFileVersion("3.14.0.6")]
|
||||
|
||||
//[assembly: AssemblyKeyFileAttribute(@"..\\..\\..\\buildtools\\35MSSharedLib1024.snk")]
|
||||
//[assembly: AssemblyDelaySignAttribute(true)]
|
|
@ -0,0 +1,94 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using Microsoft.Bot.Schema;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Dialogs.Internals;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.History
|
||||
{
|
||||
/// <summary>
|
||||
/// Class to collect and then replay activities as a transcript.
|
||||
/// </summary>
|
||||
public sealed class ReplayTranscript
|
||||
{
|
||||
private IBotToUser _botToUser;
|
||||
private Func<IActivity, string> _header;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="botToUser">Where to replay transcript.</param>
|
||||
/// <param name="header">Function for defining the transcript header on each message.</param>
|
||||
public ReplayTranscript(IBotToUser botToUser, Func<IActivity, string> header = null)
|
||||
{
|
||||
_botToUser = botToUser;
|
||||
_header = header;
|
||||
if (_header == null)
|
||||
{
|
||||
_header = (activity) => $"({activity.From.Name} {activity.Timestamp:g})";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replay activity to IBotToUser.
|
||||
/// </summary>
|
||||
/// <param name="activity">Activity.</param>
|
||||
/// <returns>Task.</returns>
|
||||
public async Task Replay(IActivity activity)
|
||||
{
|
||||
if (activity is IMessageActivity)
|
||||
{
|
||||
var msg = _botToUser.MakeMessage();
|
||||
msg.Text = _header(activity);
|
||||
await _botToUser.PostAsync(msg);
|
||||
|
||||
var act = JsonConvert.DeserializeObject<Activity>(JsonConvert.SerializeObject(activity));
|
||||
if (act.ChannelId != msg.ChannelId)
|
||||
{
|
||||
act.ChannelData = null;
|
||||
}
|
||||
act.From = msg.From;
|
||||
act.Recipient = msg.Recipient;
|
||||
act.ReplyToId = msg.ReplyToId;
|
||||
act.ChannelId = msg.ChannelId;
|
||||
act.Conversation = msg.Conversation;
|
||||
await _botToUser.PostAsync(act);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-10.0.0.0" newVersion="10.0.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.IdentityModel.Tokens" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-5.2.1.0" newVersion="5.2.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.IdentityModel.Tokens.Jwt" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-5.2.1.0" newVersion="5.2.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.IdentityModel.Protocols" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-5.2.1.0" newVersion="5.2.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.IdentityModel.Protocols.OpenIdConnect" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-5.2.1.0" newVersion="5.2.1.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" /></startup></configuration>
|
|
@ -0,0 +1,14 @@
|
|||
@echo off
|
||||
echo *** Building Microsoft.Bot.Builder.History
|
||||
setlocal
|
||||
setlocal enabledelayedexpansion
|
||||
setlocal enableextensions
|
||||
set errorlevel=0
|
||||
mkdir ..\nuget
|
||||
erase /s ..\nuget\Microsoft.Bot.Builder.History*nupkg
|
||||
msbuild /property:Configuration=release Microsoft.Bot.Builder.History.csproj
|
||||
for /f %%v in ('powershell -noprofile "(Get-Command .\bin\release\Microsoft.Bot.Builder.dll).FileVersionInfo.FileVersion"') do set builder=%%v
|
||||
for /f %%v in ('powershell -noprofile "(Get-Command .\bin\release\Microsoft.Bot.Builder.History.dll).FileVersionInfo.FileVersion"') do set version=%%v
|
||||
..\..\packages\NuGet.CommandLine.4.1.0\tools\NuGet.exe pack Microsoft.Bot.Builder.History.nuspec -symbols -properties version=%version%;builder=%builder% -OutputDirectory ..\nuget
|
||||
echo *** Finished building Microsoft.Bot.Builder.History
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.IdentityModel.Logging" version="5.2.1" targetFramework="net461" />
|
||||
<package id="Microsoft.IdentityModel.Protocols" version="5.2.1" targetFramework="net461" />
|
||||
<package id="Microsoft.IdentityModel.Protocols.OpenIdConnect" version="5.2.1" targetFramework="net461" />
|
||||
<package id="Microsoft.IdentityModel.Tokens" version="5.2.1" targetFramework="net461" />
|
||||
<package id="Microsoft.Rest.ClientRuntime" version="2.3.10" targetFramework="net461" />
|
||||
<package id="Newtonsoft.Json" version="10.0.3" targetFramework="net461" />
|
||||
<package id="System.IdentityModel.Tokens.Jwt" version="5.2.1" targetFramework="net461" />
|
||||
</packages>
|
|
@ -0,0 +1,96 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Base
|
||||
{
|
||||
public interface IEventLoop
|
||||
{
|
||||
/// <summary>
|
||||
/// Poll the target for any work to be done.
|
||||
/// </summary>
|
||||
/// <param name="token">A cancellation token.</param>
|
||||
/// <returns>A task representing the poll operation.</returns>
|
||||
Task PollAsync(CancellationToken token);
|
||||
}
|
||||
|
||||
public interface IEventProducer<in Event>
|
||||
{
|
||||
void Post(Event @event, Action onPull = null);
|
||||
}
|
||||
|
||||
public interface IEventConsumer<Event>
|
||||
{
|
||||
bool TryPull(out Event @event);
|
||||
}
|
||||
|
||||
public sealed class EventQueue<Event> : IEventProducer<Event>, IEventConsumer<Event>
|
||||
{
|
||||
private struct Item
|
||||
{
|
||||
public Event Event { get; set; }
|
||||
public Action OnPull { get; set; }
|
||||
}
|
||||
|
||||
private readonly Queue<Item> queue = new Queue<Item>();
|
||||
void IEventProducer<Event>.Post(Event @event, Action onPull)
|
||||
{
|
||||
var item = new Item() { Event = @event, OnPull = onPull };
|
||||
this.queue.Enqueue(item);
|
||||
}
|
||||
|
||||
bool IEventConsumer<Event>.TryPull(out Event @event)
|
||||
{
|
||||
if (queue.Count > 0)
|
||||
{
|
||||
var item = this.queue.Dequeue();
|
||||
@event = item.Event;
|
||||
var onPull = item.OnPull;
|
||||
onPull?.Invoke();
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
@event = default(Event);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,152 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Dialogs.Internals
|
||||
{
|
||||
public static partial class Extensions
|
||||
{
|
||||
public static T MaxBy<T, R>(this IEnumerable<T> items, Func<T, R> selectRank, IComparer<R> comparer = null)
|
||||
{
|
||||
comparer = comparer ?? Comparer<R>.Default;
|
||||
|
||||
var bestItem = default(T);
|
||||
var bestRank = default(R);
|
||||
using (var item = items.GetEnumerator())
|
||||
{
|
||||
if (item.MoveNext())
|
||||
{
|
||||
bestItem = item.Current;
|
||||
bestRank = selectRank(item.Current);
|
||||
}
|
||||
|
||||
while (item.MoveNext())
|
||||
{
|
||||
var rank = selectRank(item.Current);
|
||||
var compare = comparer.Compare(rank, bestRank);
|
||||
if (compare > 0)
|
||||
{
|
||||
bestItem = item.Current;
|
||||
bestRank = rank;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bestItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Internals.Fibers
|
||||
{
|
||||
public static partial class Extensions
|
||||
{
|
||||
public static void Push<T>(this IList<T> stack, T item)
|
||||
{
|
||||
stack.Add(item);
|
||||
}
|
||||
|
||||
public static T Pop<T>(this List<T> stack)
|
||||
{
|
||||
var top = stack.Peek();
|
||||
stack.RemoveAt(stack.Count - 1);
|
||||
return top;
|
||||
}
|
||||
|
||||
public static T Peek<T>(this IReadOnlyList<T> stack)
|
||||
{
|
||||
if (stack.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("Stack is empty");
|
||||
}
|
||||
|
||||
return stack[stack.Count - 1];
|
||||
}
|
||||
|
||||
public static T GetValue<T>(this SerializationInfo info, string name)
|
||||
{
|
||||
return (T)info.GetValue(name, typeof(T));
|
||||
}
|
||||
|
||||
public static V GetOrAdd<K, V>(this IDictionary<K, V> valueByKey, K key, Func<K, V> make)
|
||||
{
|
||||
V value;
|
||||
if (!valueByKey.TryGetValue(key, out value))
|
||||
{
|
||||
value = make(key);
|
||||
valueByKey.Add(key, value);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public static bool Equals<T>(this IReadOnlyList<T> one, IReadOnlyList<T> two, IEqualityComparer<T> comparer)
|
||||
{
|
||||
if (object.Equals(one, two))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (one.Count != two.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int index = 0; index < one.Count; ++index)
|
||||
{
|
||||
if (!comparer.Equals(one[index], two[index]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static IReadOnlyList<R> ToList<T, R>(this IReadOnlyList<T> source, Func<T, R> selector)
|
||||
{
|
||||
var count = source.Count;
|
||||
var target = new R[count];
|
||||
for (int index = 0; index < count; ++index)
|
||||
{
|
||||
target[index] = selector(source[index]);
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Base
|
||||
{
|
||||
/// <summary>
|
||||
/// This class sets the ambient thread culture for a scope of code with a using-block.
|
||||
/// </summary>
|
||||
public struct LocalizedScope : IDisposable
|
||||
{
|
||||
private readonly CultureInfo previousCulture;
|
||||
private readonly CultureInfo previousUICulture;
|
||||
|
||||
public LocalizedScope(string locale)
|
||||
{
|
||||
this.previousCulture = Thread.CurrentThread.CurrentCulture;
|
||||
this.previousUICulture = Thread.CurrentThread.CurrentUICulture;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(locale))
|
||||
{
|
||||
CultureInfo found = null;
|
||||
try
|
||||
{
|
||||
found = CultureInfo.GetCultureInfo(locale);
|
||||
}
|
||||
catch (CultureNotFoundException)
|
||||
{
|
||||
}
|
||||
|
||||
if (found != null)
|
||||
{
|
||||
Thread.CurrentThread.CurrentCulture = found;
|
||||
Thread.CurrentThread.CurrentUICulture = found;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Thread.CurrentThread.CurrentCulture = previousCulture;
|
||||
Thread.CurrentThread.CurrentUICulture = previousUICulture;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Internals.Fibers
|
||||
{
|
||||
public static partial class Pair
|
||||
{
|
||||
public static Pair<T1, T2> Create<T1, T2>(T1 one, T2 two)
|
||||
where T1 : IEquatable<T1>, IComparable<T1>
|
||||
where T2 : IEquatable<T2>, IComparable<T2>
|
||||
{
|
||||
return new Pair<T1, T2>(one, two);
|
||||
}
|
||||
}
|
||||
|
||||
public struct Pair<T1, T2> : IEquatable<Pair<T1, T2>>, IComparable<Pair<T1, T2>>
|
||||
where T1 : IEquatable<T1>, IComparable<T1>
|
||||
where T2 : IEquatable<T2>, IComparable<T2>
|
||||
{
|
||||
public Pair(T1 one, T2 two)
|
||||
{
|
||||
this.One = one;
|
||||
this.Two = two;
|
||||
}
|
||||
|
||||
public T1 One { get; }
|
||||
public T2 Two { get; }
|
||||
|
||||
public int CompareTo(Pair<T1, T2> other)
|
||||
{
|
||||
var compare = this.One.CompareTo(other.One);
|
||||
if (compare != 0)
|
||||
{
|
||||
return compare;
|
||||
}
|
||||
|
||||
return this.Two.CompareTo(other.Two);
|
||||
}
|
||||
|
||||
public bool Equals(Pair<T1, T2> other)
|
||||
{
|
||||
return object.Equals(this.One, other.One)
|
||||
&& object.Equals(this.Two, other.Two);
|
||||
}
|
||||
|
||||
public override bool Equals(object other)
|
||||
{
|
||||
return other is Pair<T1, T2> && Equals((Pair<T1, T2>)other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return this.One.GetHashCode() ^ this.Two.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,201 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Internals.Fibers
|
||||
{
|
||||
public static partial class Range
|
||||
{
|
||||
public static Range<T> From<T>(T start, T after)
|
||||
where T : IEquatable<T>, IComparable<T>
|
||||
{
|
||||
return new Range<T>(start, after);
|
||||
}
|
||||
}
|
||||
|
||||
public struct Range<T> : IEquatable<Range<T>>, IComparable<Range<T>> where T : IEquatable<T>, IComparable<T>
|
||||
{
|
||||
public T Start { get; }
|
||||
public T After { get; }
|
||||
|
||||
public Range(T start, T after)
|
||||
{
|
||||
this.Start = start;
|
||||
this.After = after;
|
||||
}
|
||||
|
||||
public override bool Equals(object other)
|
||||
{
|
||||
return other is Range<T> && this.Equals((Range<T>)other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return this.Start.GetHashCode() ^ this.After.GetHashCode();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"[{this.Start}, {this.After})";
|
||||
}
|
||||
|
||||
public bool Equals(Range<T> other)
|
||||
{
|
||||
return this.Start.Equals(other.Start) && this.After.Equals(other.After);
|
||||
}
|
||||
|
||||
public int CompareTo(Range<T> other)
|
||||
{
|
||||
if (this.After.CompareTo(other.Start) < 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (other.After.CompareTo(this.Start) > 0)
|
||||
{
|
||||
return +1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static partial class Extensions
|
||||
{
|
||||
public static IEnumerable<int> Enumerate(this Range<int> range)
|
||||
{
|
||||
for (int index = range.Start; index < range.After; ++index)
|
||||
{
|
||||
yield return index;
|
||||
}
|
||||
}
|
||||
|
||||
public static T Min<T>(T one, T two) where T : IComparable<T>
|
||||
{
|
||||
var compare = one.CompareTo(two);
|
||||
return compare < 0 ? one : two;
|
||||
}
|
||||
|
||||
private static bool Advance<T>(IEnumerator<Range<T>> enumerator, ref T index, T after)
|
||||
where T : IEquatable<T>, IComparable<T>
|
||||
{
|
||||
index = after;
|
||||
var compare = index.CompareTo(enumerator.Current.After);
|
||||
if (compare < 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (compare == 0)
|
||||
{
|
||||
bool more = enumerator.MoveNext();
|
||||
if (more)
|
||||
{
|
||||
index = enumerator.Current.Start;
|
||||
}
|
||||
return more;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<Range<T>> SortedMerge<T>(this IEnumerable<Range<T>> oneItems, IEnumerable<Range<T>> twoItems)
|
||||
where T : IEquatable<T>, IComparable<T>
|
||||
{
|
||||
using (var one = oneItems.GetEnumerator())
|
||||
using (var two = twoItems.GetEnumerator())
|
||||
{
|
||||
T oneIndex = default(T);
|
||||
T twoIndex = default(T);
|
||||
|
||||
bool oneMore = one.MoveNext();
|
||||
bool twoMore = two.MoveNext();
|
||||
|
||||
if (oneMore)
|
||||
{
|
||||
oneIndex = one.Current.Start;
|
||||
}
|
||||
if (twoMore)
|
||||
{
|
||||
twoIndex = two.Current.Start;
|
||||
}
|
||||
|
||||
if (oneMore && twoMore)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var compare = oneIndex.CompareTo(twoIndex);
|
||||
if (compare < 0)
|
||||
{
|
||||
var after = Min(one.Current.After, twoIndex);
|
||||
oneMore = Advance(one, ref oneIndex, after);
|
||||
if (!oneMore)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (compare == 0)
|
||||
{
|
||||
var after = Min(one.Current.After, two.Current.After);
|
||||
yield return new Range<T>(oneIndex, after);
|
||||
oneMore = Advance(one, ref oneIndex, after);
|
||||
twoMore = Advance(two, ref twoIndex, after);
|
||||
if (!(oneMore && twoMore))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (compare > 0)
|
||||
{
|
||||
var after = Min(two.Current.After, oneIndex);
|
||||
twoMore = Advance(two, ref twoIndex, after);
|
||||
if (!twoMore)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,208 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using Microsoft.Bot.Builder.V3Bridge.Internals.Fibers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Scorables.Internals
|
||||
{
|
||||
/// <summary>
|
||||
/// Allow the resolution of values based on type and optionally tag.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The tag should be restrictive to services registered with that tag.
|
||||
/// </remarks>
|
||||
public interface IResolver
|
||||
{
|
||||
bool TryResolve(Type type, object tag, out object value);
|
||||
}
|
||||
|
||||
public delegate bool TryResolve(Type type, object tag, out object value);
|
||||
|
||||
public static partial class Extensions
|
||||
{
|
||||
public static bool CanResolve(this IResolver resolver, Type type, object tag)
|
||||
{
|
||||
object value;
|
||||
return resolver.TryResolve(type, tag, out value);
|
||||
}
|
||||
|
||||
public static object Resolve(this IResolver resolver, Type type, object tag)
|
||||
{
|
||||
object value;
|
||||
if (!resolver.TryResolve(type, null, out value))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public static bool TryResolve<T>(this IResolver resolver, object tag, out T value)
|
||||
{
|
||||
object inner;
|
||||
if (resolver.TryResolve(typeof(T), tag, out inner))
|
||||
{
|
||||
value = (T)inner;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
value = default(T);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class NullResolver : IResolver
|
||||
{
|
||||
public static readonly IResolver Instance = new NullResolver();
|
||||
|
||||
private NullResolver()
|
||||
{
|
||||
}
|
||||
|
||||
bool IResolver.TryResolve(Type type, object tag, out object value)
|
||||
{
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class NoneResolver : IResolver
|
||||
{
|
||||
public static readonly IResolver Instance = new NoneResolver();
|
||||
|
||||
private NoneResolver()
|
||||
{
|
||||
}
|
||||
|
||||
public static readonly object BoxedToken = CancellationToken.None;
|
||||
|
||||
bool IResolver.TryResolve(Type type, object tag, out object value)
|
||||
{
|
||||
if (typeof(CancellationToken).IsAssignableFrom(type))
|
||||
{
|
||||
value = BoxedToken;
|
||||
return true;
|
||||
}
|
||||
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class DelegatingResolver : IResolver
|
||||
{
|
||||
protected readonly IResolver inner;
|
||||
|
||||
protected DelegatingResolver(IResolver inner)
|
||||
{
|
||||
SetField.NotNull(out this.inner, nameof(inner), inner);
|
||||
}
|
||||
|
||||
public virtual bool TryResolve(Type type, object tag, out object value)
|
||||
{
|
||||
return inner.TryResolve(type, tag, out value);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class EnumResolver : DelegatingResolver
|
||||
{
|
||||
public EnumResolver(IResolver inner)
|
||||
: base(inner)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool TryResolve(Type type, object tag, out object value)
|
||||
{
|
||||
if (type.IsEnum)
|
||||
{
|
||||
var name = tag as string;
|
||||
if (name != null)
|
||||
{
|
||||
if (Enum.IsDefined(type, name))
|
||||
{
|
||||
value = Enum.Parse(type, name);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return base.TryResolve(type, tag, out value);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ArrayResolver : DelegatingResolver
|
||||
{
|
||||
private readonly IReadOnlyList<object> services;
|
||||
|
||||
public ArrayResolver(IResolver inner, IReadOnlyList<object> services)
|
||||
: base(inner)
|
||||
{
|
||||
SetField.NotNull(out this.services, nameof(services), services);
|
||||
}
|
||||
|
||||
public ArrayResolver(IResolver inner, params object[] services)
|
||||
: this(inner, (IReadOnlyList<object>)services)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool TryResolve(Type type, object tag, out object value)
|
||||
{
|
||||
if (tag == null)
|
||||
{
|
||||
for (int index = 0; index < this.services.Count; ++index)
|
||||
{
|
||||
var service = this.services[index];
|
||||
if (service != null)
|
||||
{
|
||||
var serviceType = service.GetType();
|
||||
if (type.IsAssignableFrom(serviceType))
|
||||
{
|
||||
value = service;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return base.TryResolve(type, tag, out value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Internals.Fibers
|
||||
{
|
||||
/// <summary>
|
||||
/// Provide an abstraction to serialize access to an item for a using-block scope of code.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The item type.</typeparam>
|
||||
public interface IScope<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Enter a scope of code keyed by item.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="token">The cancellation token.</param>
|
||||
/// <returns>A task whose completion produces an IDisposable for the scope.</returns>
|
||||
Task<IDisposable> WithScopeAsync(T item, CancellationToken token);
|
||||
}
|
||||
|
||||
public sealed class LocalMutualExclusion<T> : IScope<T>
|
||||
where T : class
|
||||
{
|
||||
private sealed class KeyedGate
|
||||
{
|
||||
// this is the per-item semaphore
|
||||
public readonly SemaphoreSlim Gate = new SemaphoreSlim(initialCount: 1, maxCount: 1);
|
||||
|
||||
// this property is protected by a monitor around gateByItem
|
||||
public int ReferenceCount = 0;
|
||||
}
|
||||
|
||||
private readonly Dictionary<T, KeyedGate> gateByItem;
|
||||
|
||||
public LocalMutualExclusion(IEqualityComparer<T> comparer)
|
||||
{
|
||||
this.gateByItem = new Dictionary<T, KeyedGate>(comparer);
|
||||
}
|
||||
|
||||
public bool TryGetReferenceCount(T item, out int referenceCount)
|
||||
{
|
||||
lock (this.gateByItem)
|
||||
{
|
||||
KeyedGate gate;
|
||||
if (this.gateByItem.TryGetValue(item, out gate))
|
||||
{
|
||||
referenceCount = gate.ReferenceCount;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
referenceCount = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
async Task<IDisposable> IScope<T>.WithScopeAsync(T item, CancellationToken token)
|
||||
{
|
||||
// manage reference count under global mutex
|
||||
KeyedGate gate;
|
||||
lock (this.gateByItem)
|
||||
{
|
||||
gate = this.gateByItem.GetOrAdd(item, _ => new KeyedGate());
|
||||
++gate.ReferenceCount;
|
||||
}
|
||||
|
||||
// wait to enter this item's semaphore outside of global mutex
|
||||
await gate.Gate.WaitAsync(token);
|
||||
|
||||
return new Releaser(this, item);
|
||||
}
|
||||
|
||||
private sealed class Releaser : IDisposable
|
||||
{
|
||||
private readonly LocalMutualExclusion<T> owner;
|
||||
private readonly T item;
|
||||
public Releaser(LocalMutualExclusion<T> owner, T item)
|
||||
{
|
||||
SetField.NotNull(out this.owner, nameof(owner), owner);
|
||||
SetField.NotNull(out this.item, nameof(item), item);
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
KeyedGate gate;
|
||||
lock (this.owner.gateByItem)
|
||||
{
|
||||
gate = this.owner.gateByItem[this.item];
|
||||
}
|
||||
|
||||
// exit this item's semaphore outside of global mutex, and let other threads run here
|
||||
gate.Gate.Release();
|
||||
|
||||
// obtain the global mutex to update the reference count
|
||||
lock (this.owner.gateByItem)
|
||||
{
|
||||
--gate.ReferenceCount;
|
||||
|
||||
// and possibly clean up the semaphore
|
||||
// if there are no other threads referencing this item's semaphore
|
||||
if (gate.ReferenceCount == 0)
|
||||
{
|
||||
if (!this.owner.gateByItem.Remove(this.item))
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Internals.Fibers
|
||||
{
|
||||
public static partial class SetField
|
||||
{
|
||||
public static void NotNull<T>(out T field, string name, T value) where T : class
|
||||
{
|
||||
CheckNull(name, value);
|
||||
field = value;
|
||||
}
|
||||
|
||||
public static void CheckNull<T>(string name, T value) where T : class
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(name);
|
||||
}
|
||||
}
|
||||
|
||||
public static void NotNullFrom<T>(out T field, string name, SerializationInfo info) where T : class
|
||||
{
|
||||
var value = (T)info.GetValue(name, typeof(T));
|
||||
SetField.NotNull(out field, name, value);
|
||||
}
|
||||
|
||||
public static void From<T>(out T field, string name, SerializationInfo info)
|
||||
{
|
||||
var value = (T)info.GetValue(name, typeof(T));
|
||||
field = value;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Internals.Fibers
|
||||
{
|
||||
public static partial class Tasks<T>
|
||||
{
|
||||
public static readonly Task<T> Null = Task.FromResult<T>(default(T));
|
||||
public static readonly Task<T> NotImplemented = Task.FromException<T>(new NotImplementedException());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Internals.Fibers
|
||||
{
|
||||
public interface ITraits<T>
|
||||
{
|
||||
T Minimum { get; }
|
||||
T Maximum { get; }
|
||||
}
|
||||
|
||||
public sealed class NormalizedTraits : ITraits<double>
|
||||
{
|
||||
public static readonly ITraits<double> Instance = new NormalizedTraits();
|
||||
private NormalizedTraits()
|
||||
{
|
||||
}
|
||||
double ITraits<double>.Maximum
|
||||
{
|
||||
get { return 1.0; }
|
||||
}
|
||||
|
||||
double ITraits<double>.Minimum
|
||||
{
|
||||
get { return 0.0; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Base
|
||||
{
|
||||
public static partial class Types
|
||||
{
|
||||
public static MethodInfo MethodOf(Expression<Action> action)
|
||||
{
|
||||
var call = (MethodCallExpression)action.Body;
|
||||
return call.Method;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,162 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using Microsoft.Bot.Builder.V3Bridge.Internals.Fibers;
|
||||
using Microsoft.Bot.Schema;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Dialogs
|
||||
{
|
||||
/// <summary>
|
||||
/// The key that minimally and completely identifies a bot's conversation with a user on a channel.
|
||||
/// </summary>
|
||||
public interface IAddress
|
||||
{
|
||||
string BotId { get; }
|
||||
string ChannelId { get; }
|
||||
string UserId { get; }
|
||||
string ConversationId { get; }
|
||||
string ServiceUrl { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The key that minimally and completely identifies a bot's conversation with a user on a channel.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public sealed class Address : IAddress, IEquatable<IAddress>
|
||||
{
|
||||
public static Address FromActivity(IActivity activity)
|
||||
{
|
||||
return new Address
|
||||
(
|
||||
// purposefully using named arguments because these all have the same type
|
||||
botId: activity.Recipient.Id,
|
||||
channelId: activity.ChannelId,
|
||||
userId: activity.From.Id,
|
||||
conversationId: activity.Conversation.Id,
|
||||
serviceUrl: activity.ServiceUrl
|
||||
);
|
||||
}
|
||||
|
||||
[JsonConstructor]
|
||||
public Address(string botId, string channelId, string userId, string conversationId, string serviceUrl)
|
||||
{
|
||||
SetField.CheckNull(nameof(botId), botId);
|
||||
SetField.CheckNull(nameof(channelId), channelId);
|
||||
SetField.CheckNull(nameof(userId), userId);
|
||||
SetField.CheckNull(nameof(conversationId), conversationId);
|
||||
SetField.CheckNull(nameof(serviceUrl), serviceUrl);
|
||||
|
||||
this.BotId = botId;
|
||||
this.ChannelId = channelId;
|
||||
this.UserId = userId;
|
||||
this.ConversationId = conversationId;
|
||||
this.ServiceUrl = serviceUrl;
|
||||
}
|
||||
public string BotId { get; }
|
||||
public string ChannelId { get; }
|
||||
public string UserId { get; }
|
||||
public string ConversationId { get; }
|
||||
public string ServiceUrl { get; }
|
||||
|
||||
public bool Equals(IAddress other)
|
||||
{
|
||||
return other != null
|
||||
&& object.Equals(this.BotId, other.BotId)
|
||||
&& object.Equals(this.ChannelId, other.ChannelId)
|
||||
&& object.Equals(this.UserId, other.UserId)
|
||||
&& object.Equals(this.ConversationId, other.ConversationId)
|
||||
&& object.Equals(this.ServiceUrl, other.ServiceUrl)
|
||||
;
|
||||
}
|
||||
|
||||
public override bool Equals(object other)
|
||||
{
|
||||
return this.Equals(other as IAddress);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var code
|
||||
= this.BotId.GetHashCode()
|
||||
^ this.ChannelId.GetHashCode()
|
||||
^ this.UserId.GetHashCode()
|
||||
^ this.ConversationId.GetHashCode()
|
||||
^ this.ServiceUrl.GetHashCode()
|
||||
;
|
||||
|
||||
return code;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare two Address instances for equality, excluding the user information.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This equality comparer excludes the user from the Address identity
|
||||
/// so that dialog execution can be serialized by conversation, thereby
|
||||
/// making it less likely to encounter 412 "precondition failed" when
|
||||
/// updating the bot state data bags with optimistic concurrency. Updates
|
||||
/// to the user's data bags may still conflict across multiple conversations.
|
||||
/// </remarks>
|
||||
public sealed class ConversationAddressComparer : IEqualityComparer<IAddress>
|
||||
{
|
||||
bool IEqualityComparer<IAddress>.Equals(IAddress one, IAddress two)
|
||||
{
|
||||
var equals =
|
||||
object.ReferenceEquals(one, two)
|
||||
|| (
|
||||
object.Equals(one.BotId, two.BotId)
|
||||
&& object.Equals(one.ChannelId, two.ChannelId)
|
||||
&& object.Equals(one.ConversationId, two.ConversationId)
|
||||
&& object.Equals(one.ServiceUrl, two.ServiceUrl)
|
||||
);
|
||||
|
||||
return equals;
|
||||
}
|
||||
|
||||
int IEqualityComparer<IAddress>.GetHashCode(IAddress address)
|
||||
{
|
||||
var code
|
||||
= address.BotId.GetHashCode()
|
||||
^ address.ChannelId.GetHashCode()
|
||||
^ address.ConversationId.GetHashCode()
|
||||
^ address.ServiceUrl.GetHashCode()
|
||||
;
|
||||
|
||||
return code;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
// <auto-generated>
|
||||
// Code generated by Microsoft (R) AutoRest Code Generator.
|
||||
// Changes may cause incorrect behavior and will be lost if the code is
|
||||
// regenerated.
|
||||
// </auto-generated>
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge
|
||||
{
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Linq;
|
||||
|
||||
public partial class BotData
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the BotData class.
|
||||
/// </summary>
|
||||
public BotData()
|
||||
{
|
||||
CustomInit();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the BotData class.
|
||||
/// </summary>
|
||||
public BotData(string eTag = default(string), object data = default(object))
|
||||
{
|
||||
Data = data;
|
||||
ETag = eTag;
|
||||
CustomInit();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An initialization method that performs custom operations like setting defaults
|
||||
/// </summary>
|
||||
partial void CustomInit();
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
[JsonProperty(PropertyName = "data")]
|
||||
public object Data { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
[JsonProperty(PropertyName = "eTag")]
|
||||
public string ETag { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get a property from a BotData recorded retrieved using the REST API
|
||||
/// </summary>
|
||||
/// <param name="property">property name to change</param>
|
||||
/// <returns>property requested or default for type</returns>
|
||||
public TypeT GetProperty<TypeT>(string property)
|
||||
{
|
||||
if (this.Data == null)
|
||||
this.Data = new JObject();
|
||||
|
||||
dynamic data = this.Data;
|
||||
if (data[property] == null)
|
||||
return default(TypeT);
|
||||
|
||||
// convert jToken (JArray or JObject) to the given typeT
|
||||
return (TypeT)(data[property].ToObject(typeof(TypeT)));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Set a property on a BotData record retrieved using the REST API
|
||||
/// </summary>
|
||||
/// <param name="property">property name to change</param>
|
||||
/// <param name="data">new data</param>
|
||||
public void SetProperty<TypeT>(string property, TypeT data)
|
||||
{
|
||||
if (this.Data == null)
|
||||
this.Data = new JObject();
|
||||
|
||||
// convert (object or array) to JToken (JObject/JArray)
|
||||
if (data == null)
|
||||
((JObject)this.Data)[property] = null;
|
||||
else
|
||||
((JObject)this.Data)[property] = JToken.FromObject(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a property from the BotData record
|
||||
/// </summary>
|
||||
/// <param name="property">property name to remove</param>
|
||||
public void RemoveProperty(string property)
|
||||
{
|
||||
if (this.Data == null)
|
||||
this.Data = new JObject();
|
||||
|
||||
((JObject)this.Data).Remove(property);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,677 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Base;
|
||||
using Microsoft.Bot.Builder.V3Bridge.ConnectorEx;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Internals.Fibers;
|
||||
using Microsoft.Bot.Builder.V3Bridge;
|
||||
using Microsoft.Bot.Schema;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Dialogs.Internals
|
||||
{
|
||||
public enum BotStoreType
|
||||
{
|
||||
BotConversationData,
|
||||
BotPrivateConversationData,
|
||||
BotUserData
|
||||
}
|
||||
|
||||
public interface IBotDataStore<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Return BotData with Data pointing to a JObject or an empty BotData() record with ETag:""
|
||||
/// </summary>
|
||||
/// <param name="key"> The key.</param>
|
||||
/// <param name="botStoreType"> The bot store type.</param>
|
||||
/// <param name="cancellationToken"> The cancellation token.</param>
|
||||
/// <returns>Bot record that is stored for this key, or "empty" bot record ready to be stored</returns>
|
||||
Task<T> LoadAsync(IAddress key, BotStoreType botStoreType, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Save a BotData using the ETag.
|
||||
/// Etag consistency checks
|
||||
/// If ETag is null or empty, this will set the value if nobody has set it yet
|
||||
/// If ETag is "*" then this will unconditionally set the value
|
||||
/// If ETag matches then this will update the value if it is unchanged.
|
||||
/// If Data is null this removes record, otherwise it stores
|
||||
/// </summary>
|
||||
/// <param name="key"> The key.</param>
|
||||
/// <param name="botStoreType">The bot store type.</param>
|
||||
/// <param name="data"> The data that should be saved.</param>
|
||||
/// <param name="cancellationToken"> The cancellation token.</param>
|
||||
/// <returns>throw HttpException(HttpStatusCode.PreconditionFailed) if update fails</returns>
|
||||
Task SaveAsync(IAddress key, BotStoreType botStoreType, T data, CancellationToken cancellationToken);
|
||||
Task<bool> FlushAsync(IAddress key, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Volitile in-memory implementation of <see cref="IBotDataStore{BotData}"/>
|
||||
/// </summary>
|
||||
public class InMemoryDataStore : IBotDataStore<BotData>
|
||||
{
|
||||
internal readonly ConcurrentDictionary<string, string> store = new ConcurrentDictionary<string, string>();
|
||||
private readonly Dictionary<BotStoreType, object> locks = new Dictionary<BotStoreType, object>()
|
||||
{
|
||||
{ BotStoreType.BotConversationData, new object() },
|
||||
{ BotStoreType.BotPrivateConversationData, new object() },
|
||||
{ BotStoreType.BotUserData, new object() }
|
||||
};
|
||||
|
||||
async Task<BotData> IBotDataStore<BotData>.LoadAsync(IAddress key, BotStoreType botStoreType, CancellationToken cancellationToken)
|
||||
{
|
||||
string serializedData;
|
||||
if (store.TryGetValue(GetKey(key, botStoreType), out serializedData))
|
||||
return Deserialize(serializedData);
|
||||
return new BotData(eTag: String.Empty);
|
||||
}
|
||||
|
||||
async Task IBotDataStore<BotData>.SaveAsync(IAddress key, BotStoreType botStoreType, BotData botData, CancellationToken cancellationToken)
|
||||
{
|
||||
lock (locks[botStoreType])
|
||||
{
|
||||
if (botData.Data != null)
|
||||
{
|
||||
store.AddOrUpdate(GetKey(key, botStoreType), (dictionaryKey) =>
|
||||
{
|
||||
botData.ETag = Guid.NewGuid().ToString("n");
|
||||
return Serialize(botData);
|
||||
}, (dictionaryKey, value) =>
|
||||
{
|
||||
ValidateETag(botData, value);
|
||||
botData.ETag = Guid.NewGuid().ToString("n");
|
||||
return Serialize(botData);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// remove record on null
|
||||
string value;
|
||||
if (store.TryGetValue(GetKey(key, botStoreType), out value))
|
||||
{
|
||||
ValidateETag(botData, value);
|
||||
store.TryRemove(GetKey(key, botStoreType), out value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateETag(BotData botData, string value)
|
||||
{
|
||||
if (botData.ETag != "*" && Deserialize(value).ETag != botData.ETag)
|
||||
{
|
||||
throw new HttpException((int)HttpStatusCode.PreconditionFailed, "Inconsistent SaveAsync based on ETag!");
|
||||
}
|
||||
}
|
||||
|
||||
Task<bool> IBotDataStore<BotData>.FlushAsync(IAddress key, CancellationToken cancellationToken)
|
||||
{
|
||||
// Everything is saved. Flush is no-op
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
private static string GetKey(IAddress key, BotStoreType botStoreType)
|
||||
{
|
||||
switch (botStoreType)
|
||||
{
|
||||
case BotStoreType.BotConversationData:
|
||||
return $"conversation:{key.BotId}:{key.ChannelId}:{key.ConversationId}";
|
||||
case BotStoreType.BotUserData:
|
||||
return $"user:{key.BotId}:{key.ChannelId}:{key.UserId}";
|
||||
case BotStoreType.BotPrivateConversationData:
|
||||
return $"privateConversation:{key.BotId}:{key.ChannelId}:{key.UserId}:{key.ConversationId}";
|
||||
default:
|
||||
throw new ArgumentException("Unsupported bot store type!");
|
||||
}
|
||||
}
|
||||
|
||||
private static string Serialize(BotData data)
|
||||
{
|
||||
using (var cmpStream = new MemoryStream())
|
||||
using (var stream = new GZipStream(cmpStream, CompressionMode.Compress))
|
||||
using (var streamWriter = new StreamWriter(stream))
|
||||
{
|
||||
var serializedJSon = JsonConvert.SerializeObject(data);
|
||||
streamWriter.Write(serializedJSon);
|
||||
streamWriter.Close();
|
||||
stream.Close();
|
||||
return Convert.ToBase64String(cmpStream.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
private static BotData Deserialize(string str)
|
||||
{
|
||||
byte[] bytes = Convert.FromBase64String(str);
|
||||
using (var stream = new MemoryStream(bytes))
|
||||
using (var gz = new GZipStream(stream, CompressionMode.Decompress))
|
||||
using (var streamReader = new StreamReader(gz))
|
||||
{
|
||||
return JsonConvert.DeserializeObject<BotData>(streamReader.ReadToEnd());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The data consistency policy for <see cref="CachingBotDataStore"/>
|
||||
/// </summary>
|
||||
public enum CachingBotDataStoreConsistencyPolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// Causes <see cref="CachingBotDataStore"/> to set <see cref="BotData.ETag"/> to "*" when it flushes the data to storage.
|
||||
/// As a result last write will overwrite the data.
|
||||
/// </summary>
|
||||
LastWriteWins,
|
||||
/// <summary>
|
||||
/// Causes <see cref="CachingBotDataStore"/> to write data with the same <see cref="BotData.ETag"/>
|
||||
/// returned by <see cref="CachingBotDataStore.inner"/>. As a result <see cref="IBotDataStore{T}.FlushAsync(IAddress, CancellationToken)"/>
|
||||
/// might fail because of ETag inconsistencies.
|
||||
/// </summary>
|
||||
ETagBasedConsistency
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Caches data for <see cref="BotDataBase{T}"/> and wraps the data in <see cref="BotData"/> to be stored in <see cref="CachingBotDataStore.inner"/>
|
||||
/// </summary>
|
||||
public class CachingBotDataStore : IBotDataStore<BotData>
|
||||
{
|
||||
private readonly IBotDataStore<BotData> inner;
|
||||
internal readonly Dictionary<IAddress, CacheEntry> cache = new Dictionary<IAddress, CacheEntry>();
|
||||
private readonly CachingBotDataStoreConsistencyPolicy dataConsistencyPolicy;
|
||||
|
||||
public CachingBotDataStore(IBotDataStore<BotData> inner, CachingBotDataStoreConsistencyPolicy dataConsistencyPolicy)
|
||||
{
|
||||
SetField.NotNull(out this.inner, nameof(inner), inner);
|
||||
this.dataConsistencyPolicy = dataConsistencyPolicy;
|
||||
}
|
||||
|
||||
internal class CacheEntry
|
||||
{
|
||||
public BotData BotConversationData { set; get; }
|
||||
public BotData BotPrivateConversationData { set; get; }
|
||||
public BotData BotUserData { set; get; }
|
||||
}
|
||||
|
||||
async Task<bool> IBotDataStore<BotData>.FlushAsync(IAddress key, CancellationToken cancellationToken)
|
||||
{
|
||||
CacheEntry entry;
|
||||
if (cache.TryGetValue(key, out entry))
|
||||
{
|
||||
// Removing the cached entry to make sure that we are not leaking
|
||||
// flushed entries when CachingBotDataStore is registered as a singleton object.
|
||||
// Also since this store is not updating ETags on LoadAsync(...), there
|
||||
// will be a conflict if we reuse the cached entries after flush.
|
||||
cache.Remove(key);
|
||||
await this.Save(key, entry, cancellationToken);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async Task<BotData> IBotDataStore<BotData>.LoadAsync(IAddress key, BotStoreType botStoreType, CancellationToken cancellationToken)
|
||||
{
|
||||
CacheEntry cacheEntry;
|
||||
BotData value = null;
|
||||
if (!cache.TryGetValue(key, out cacheEntry))
|
||||
{
|
||||
cacheEntry = new CacheEntry();
|
||||
cache.Add(key, cacheEntry);
|
||||
value = await LoadFromInnerAndCache(cacheEntry, botStoreType, key, cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (botStoreType)
|
||||
{
|
||||
case BotStoreType.BotConversationData:
|
||||
if (cacheEntry.BotConversationData != null)
|
||||
{
|
||||
value = cacheEntry.BotConversationData;
|
||||
}
|
||||
break;
|
||||
case BotStoreType.BotPrivateConversationData:
|
||||
if (cacheEntry.BotPrivateConversationData != null)
|
||||
{
|
||||
value = cacheEntry.BotPrivateConversationData;
|
||||
}
|
||||
break;
|
||||
case BotStoreType.BotUserData:
|
||||
if (cacheEntry.BotUserData != null)
|
||||
{
|
||||
value = cacheEntry.BotUserData;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
value = await LoadFromInnerAndCache(cacheEntry, botStoreType, key, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
async Task IBotDataStore<BotData>.SaveAsync(IAddress key, BotStoreType botStoreType, BotData value, CancellationToken cancellationToken)
|
||||
{
|
||||
CacheEntry entry;
|
||||
if (!cache.TryGetValue(key, out entry))
|
||||
{
|
||||
entry = new CacheEntry();
|
||||
cache.Add(key, entry);
|
||||
}
|
||||
|
||||
SetCachedValue(entry, botStoreType, value);
|
||||
}
|
||||
|
||||
private async Task<BotData> LoadFromInnerAndCache(CacheEntry cacheEntry, BotStoreType botStoreType, IAddress key, CancellationToken token)
|
||||
{
|
||||
var value = await inner.LoadAsync(key, botStoreType, token);
|
||||
|
||||
if (value != null)
|
||||
{
|
||||
SetCachedValue(cacheEntry, botStoreType, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
// inner store returned null, we create a new instance of BotData with ETag = "*"
|
||||
value = new BotData() { ETag = "*" };
|
||||
SetCachedValue(cacheEntry, botStoreType, value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private void SetCachedValue(CacheEntry entry, BotStoreType botStoreType, BotData value)
|
||||
{
|
||||
switch (botStoreType)
|
||||
{
|
||||
case BotStoreType.BotConversationData:
|
||||
entry.BotConversationData = value;
|
||||
break;
|
||||
case BotStoreType.BotPrivateConversationData:
|
||||
entry.BotPrivateConversationData = value;
|
||||
break;
|
||||
case BotStoreType.BotUserData:
|
||||
entry.BotUserData = value;
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Save(IAddress key, CacheEntry entry, CancellationToken cancellationToken)
|
||||
{
|
||||
switch (this.dataConsistencyPolicy)
|
||||
{
|
||||
case CachingBotDataStoreConsistencyPolicy.LastWriteWins:
|
||||
if (entry?.BotConversationData != null)
|
||||
{
|
||||
entry.BotConversationData.ETag = "*";
|
||||
}
|
||||
|
||||
if (entry?.BotUserData != null)
|
||||
{
|
||||
entry.BotUserData.ETag = "*";
|
||||
}
|
||||
|
||||
if (entry?.BotPrivateConversationData != null)
|
||||
{
|
||||
entry.BotPrivateConversationData.ETag = "*";
|
||||
}
|
||||
break;
|
||||
case CachingBotDataStoreConsistencyPolicy.ETagBasedConsistency:
|
||||
// no action needed, store relies on the ETags returned by inner store
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException($"{this.dataConsistencyPolicy} is not a valid consistency policy!");
|
||||
}
|
||||
|
||||
var tasks = new List<Task>(capacity: 3);
|
||||
|
||||
if (entry?.BotConversationData != null)
|
||||
{
|
||||
tasks.Add(inner.SaveAsync(key, BotStoreType.BotConversationData, entry.BotConversationData, cancellationToken));
|
||||
}
|
||||
|
||||
if (entry?.BotUserData != null)
|
||||
{
|
||||
tasks.Add(inner.SaveAsync(key, BotStoreType.BotUserData, entry.BotUserData, cancellationToken));
|
||||
}
|
||||
|
||||
if (entry?.BotPrivateConversationData != null)
|
||||
{
|
||||
tasks.Add(inner.SaveAsync(key, BotStoreType.BotPrivateConversationData, entry.BotPrivateConversationData, cancellationToken));
|
||||
}
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class DialogTaskManagerBotDataLoader : IBotData
|
||||
{
|
||||
private readonly IBotData inner;
|
||||
private readonly IDialogTaskManager dialogTaskManager;
|
||||
private readonly ILocaleFinder localeFinder;
|
||||
private readonly IActivity activity;
|
||||
|
||||
|
||||
public DialogTaskManagerBotDataLoader(IBotData inner, IDialogTaskManager dialogTaskManager, IActivity activity, ILocaleFinder localeFinder)
|
||||
{
|
||||
SetField.NotNull(out this.inner, nameof(inner), inner);
|
||||
SetField.NotNull(out this.dialogTaskManager, nameof(dialogTaskManager), dialogTaskManager);
|
||||
SetField.NotNull(out this.localeFinder, nameof(localeFinder), localeFinder);
|
||||
SetField.NotNull(out this.activity, nameof(activity), activity);
|
||||
}
|
||||
|
||||
public IBotDataBag UserData { get { return inner.UserData; } }
|
||||
public IBotDataBag ConversationData { get { return inner.ConversationData; } }
|
||||
public IBotDataBag PrivateConversationData { get { return inner.PrivateConversationData; } }
|
||||
public async Task LoadAsync(CancellationToken token)
|
||||
{
|
||||
await this.inner.LoadAsync(token);
|
||||
var locale = await this.localeFinder.FindLocale(this.activity, token);
|
||||
// The localeScope should be set before dialog stack is deserialized.
|
||||
// This enables dialogs, i.e. formflow dialog, to load the right resource for
|
||||
// the serialized instance.
|
||||
using (var localeScope = new LocalizedScope(locale))
|
||||
{
|
||||
await this.dialogTaskManager.LoadDialogTasks(token);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task FlushAsync(CancellationToken token)
|
||||
{
|
||||
await this.dialogTaskManager.FlushDialogTasks(token);
|
||||
await this.inner.FlushAsync(token);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class BotDataBase<T> : IBotData
|
||||
{
|
||||
protected readonly IBotDataStore<BotData> botDataStore;
|
||||
protected readonly IAddress botDataKey;
|
||||
private IBotDataBag conversationData;
|
||||
private IBotDataBag privateConversationData;
|
||||
private IBotDataBag userData;
|
||||
|
||||
public BotDataBase(IAddress botDataKey, IBotDataStore<BotData> botDataStore)
|
||||
{
|
||||
SetField.NotNull(out this.botDataStore, nameof(botDataStore), botDataStore);
|
||||
SetField.NotNull(out this.botDataKey, nameof(botDataKey), botDataKey);
|
||||
}
|
||||
|
||||
protected abstract T MakeData();
|
||||
protected abstract IBotDataBag WrapData(T data);
|
||||
|
||||
public async Task LoadAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var conversationTask = LoadData(BotStoreType.BotConversationData, cancellationToken);
|
||||
var privateConversationTask = LoadData(BotStoreType.BotPrivateConversationData, cancellationToken);
|
||||
var userTask = LoadData(BotStoreType.BotUserData, cancellationToken);
|
||||
|
||||
this.conversationData = await conversationTask;
|
||||
this.privateConversationData = await privateConversationTask;
|
||||
this.userData = await userTask;
|
||||
}
|
||||
|
||||
public async Task FlushAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await this.botDataStore.FlushAsync(botDataKey, cancellationToken);
|
||||
}
|
||||
|
||||
IBotDataBag IBotData.ConversationData
|
||||
{
|
||||
get
|
||||
{
|
||||
CheckNull(nameof(conversationData), conversationData);
|
||||
return this.conversationData;
|
||||
}
|
||||
}
|
||||
|
||||
IBotDataBag IBotData.PrivateConversationData
|
||||
{
|
||||
get
|
||||
{
|
||||
CheckNull(nameof(privateConversationData), privateConversationData);
|
||||
return this.privateConversationData;
|
||||
}
|
||||
}
|
||||
|
||||
IBotDataBag IBotData.UserData
|
||||
{
|
||||
get
|
||||
{
|
||||
CheckNull(nameof(userData), userData);
|
||||
return this.userData;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<IBotDataBag> LoadData(BotStoreType botStoreType, CancellationToken cancellationToken)
|
||||
{
|
||||
var botData = await this.botDataStore.LoadAsync(botDataKey, botStoreType, cancellationToken);
|
||||
if (botData?.Data == null)
|
||||
{
|
||||
botData.Data = this.MakeData();
|
||||
await this.botDataStore.SaveAsync(botDataKey, botStoreType, botData, cancellationToken);
|
||||
}
|
||||
return this.WrapData((T)botData.Data);
|
||||
}
|
||||
|
||||
private void CheckNull(string name, IBotDataBag value)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new InvalidOperationException($"{name} cannot be null! probably forgot to call LoadAsync() first!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class DictionaryBotData : BotDataBase<Dictionary<string, object>>
|
||||
{
|
||||
public DictionaryBotData(IAddress botDataKey, IBotDataStore<BotData> botDataStore)
|
||||
: base(botDataKey, botDataStore)
|
||||
{
|
||||
}
|
||||
|
||||
protected override Dictionary<string, object> MakeData()
|
||||
{
|
||||
return new Dictionary<string, object>();
|
||||
}
|
||||
|
||||
private sealed class Bag : IBotDataBag
|
||||
{
|
||||
private readonly Dictionary<string, object> bag;
|
||||
public Bag(Dictionary<string, object> bag)
|
||||
{
|
||||
SetField.NotNull(out this.bag, nameof(bag), bag);
|
||||
}
|
||||
|
||||
int IBotDataBag.Count { get { return this.bag.Count; } }
|
||||
|
||||
void IBotDataBag.SetValue<T>(string key, T value)
|
||||
{
|
||||
this.bag[key] = value;
|
||||
}
|
||||
|
||||
bool IBotDataBag.ContainsKey(string key)
|
||||
{
|
||||
return this.bag.ContainsKey(key);
|
||||
}
|
||||
|
||||
bool IBotDataBag.TryGetValue<T>(string key, out T value)
|
||||
{
|
||||
object boxed;
|
||||
bool found = this.bag.TryGetValue(key, out boxed);
|
||||
if (found)
|
||||
{
|
||||
if (boxed is T)
|
||||
{
|
||||
value = (T)boxed;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
value = default(T);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IBotDataBag.RemoveValue(string key)
|
||||
{
|
||||
return this.bag.Remove(key);
|
||||
}
|
||||
|
||||
void IBotDataBag.Clear()
|
||||
{
|
||||
this.bag.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
protected override IBotDataBag WrapData(Dictionary<string, object> data)
|
||||
{
|
||||
return new Bag(data);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class JObjectBotData : BotDataBase<JObject>
|
||||
{
|
||||
public JObjectBotData(IAddress botDataKey, IBotDataStore<BotData> botDataStore)
|
||||
: base(botDataKey, botDataStore)
|
||||
{
|
||||
}
|
||||
|
||||
protected override JObject MakeData()
|
||||
{
|
||||
return new JObject();
|
||||
}
|
||||
private sealed class Bag : IBotDataBag
|
||||
{
|
||||
private readonly JObject bag;
|
||||
public Bag(JObject bag)
|
||||
{
|
||||
SetField.NotNull(out this.bag, nameof(bag), bag);
|
||||
}
|
||||
|
||||
int IBotDataBag.Count { get { return this.bag.Count; } }
|
||||
|
||||
void IBotDataBag.SetValue<T>(string key, T value)
|
||||
{
|
||||
var token = JToken.FromObject(value);
|
||||
#if DEBUG
|
||||
var copy = token.ToObject<T>();
|
||||
#endif
|
||||
this.bag[key] = token;
|
||||
}
|
||||
|
||||
bool IBotDataBag.ContainsKey(string key)
|
||||
{
|
||||
return this.bag[key] != null;
|
||||
}
|
||||
|
||||
bool IBotDataBag.TryGetValue<T>(string key, out T value)
|
||||
{
|
||||
JToken token;
|
||||
bool found = this.bag.TryGetValue(key, out token);
|
||||
if (found)
|
||||
{
|
||||
value = token.ToObject<T>();
|
||||
return true;
|
||||
}
|
||||
|
||||
value = default(T);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IBotDataBag.RemoveValue(string key)
|
||||
{
|
||||
return this.bag.Remove(key);
|
||||
}
|
||||
|
||||
void IBotDataBag.Clear()
|
||||
{
|
||||
this.bag.RemoveAll();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected override IBotDataBag WrapData(JObject data)
|
||||
{
|
||||
return new Bag(data);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class BotDataBagStream : MemoryStream
|
||||
{
|
||||
private readonly IBotDataBag bag;
|
||||
private readonly string key;
|
||||
public BotDataBagStream(IBotDataBag bag, string key)
|
||||
{
|
||||
SetField.NotNull(out this.bag, nameof(bag), bag);
|
||||
SetField.NotNull(out this.key, nameof(key), key);
|
||||
|
||||
byte[] blob;
|
||||
if (this.bag.TryGetValue(key, out blob))
|
||||
{
|
||||
this.Write(blob, 0, blob.Length);
|
||||
this.Position = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
base.Flush();
|
||||
|
||||
var blob = this.ToArray();
|
||||
this.bag.SetValue(this.key, blob);
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
this.Flush();
|
||||
base.Close();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,386 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Builder.V3Bridge.ConnectorEx;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Internals.Fibers;
|
||||
using Microsoft.Bot.Connector;
|
||||
using Microsoft.Bot.Schema;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Dialogs.Internals
|
||||
{
|
||||
/// <summary>
|
||||
/// Methods to send a message from the bot to the user.
|
||||
/// </summary>
|
||||
public interface IBotToUser
|
||||
{
|
||||
/// <summary>
|
||||
/// Post a message to be sent to the user.
|
||||
/// </summary>
|
||||
/// <param name="message">The message for the user.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A task that represents the post operation.</returns>
|
||||
Task PostAsync(IMessageActivity message, CancellationToken cancellationToken = default(CancellationToken));
|
||||
|
||||
/// <summary>
|
||||
/// Make a message.
|
||||
/// </summary>
|
||||
/// <returns>The new message.</returns>
|
||||
IMessageActivity MakeMessage();
|
||||
}
|
||||
|
||||
public sealed class NullBotToUser : IBotToUser
|
||||
{
|
||||
private readonly IMessageActivity toBot;
|
||||
public NullBotToUser(IMessageActivity toBot)
|
||||
{
|
||||
SetField.NotNull(out this.toBot, nameof(toBot), toBot);
|
||||
}
|
||||
|
||||
IMessageActivity IBotToUser.MakeMessage()
|
||||
{
|
||||
var toBotActivity = (Activity)this.toBot;
|
||||
return toBotActivity.CreateReply();
|
||||
}
|
||||
|
||||
Task IBotToUser.PostAsync(IMessageActivity message, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class PassBotToUser : IBotToUser
|
||||
{
|
||||
private readonly IBotToUser inner;
|
||||
public PassBotToUser(IBotToUser inner)
|
||||
{
|
||||
SetField.NotNull(out this.inner, nameof(inner), inner);
|
||||
}
|
||||
|
||||
IMessageActivity IBotToUser.MakeMessage()
|
||||
{
|
||||
return this.inner.MakeMessage();
|
||||
}
|
||||
|
||||
async Task IBotToUser.PostAsync(IMessageActivity message, CancellationToken cancellationToken)
|
||||
{
|
||||
await this.inner.PostAsync(message, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AlwaysSendDirect_BotToUser : IBotToUser
|
||||
{
|
||||
private readonly IMessageActivity toBot;
|
||||
private readonly IConnectorClient client;
|
||||
public AlwaysSendDirect_BotToUser(IMessageActivity toBot, IConnectorClient client)
|
||||
{
|
||||
SetField.NotNull(out this.toBot, nameof(toBot), toBot);
|
||||
SetField.NotNull(out this.client, nameof(client), client);
|
||||
}
|
||||
|
||||
IMessageActivity IBotToUser.MakeMessage()
|
||||
{
|
||||
var toBotActivity = (Activity)this.toBot;
|
||||
return toBotActivity.CreateReply();
|
||||
}
|
||||
|
||||
async Task IBotToUser.PostAsync(IMessageActivity message, CancellationToken cancellationToken)
|
||||
{
|
||||
await this.client.Conversations.ReplyToActivityAsync((Activity)message, cancellationToken);
|
||||
}
|
||||
}
|
||||
public interface IMessageQueue
|
||||
{
|
||||
Task QueueMessageAsync(IBotToUser botToUser, IMessageActivity message, CancellationToken token);
|
||||
Task DrainQueueAsync(IBotToUser botToUser, CancellationToken token);
|
||||
}
|
||||
|
||||
public sealed class AutoInputHint_BotToUser : IBotToUser
|
||||
{
|
||||
private readonly IBotToUser inner;
|
||||
private readonly IMessageQueue queue;
|
||||
|
||||
public AutoInputHint_BotToUser(IBotToUser inner, IMessageQueue queue)
|
||||
{
|
||||
SetField.NotNull(out this.queue, nameof(queue), queue);
|
||||
SetField.NotNull(out this.inner, nameof(inner), inner);
|
||||
}
|
||||
|
||||
async Task IBotToUser.PostAsync(IMessageActivity message, CancellationToken cancellationToken)
|
||||
{
|
||||
await this.queue.QueueMessageAsync(inner, message, cancellationToken);
|
||||
}
|
||||
|
||||
IMessageActivity IBotToUser.MakeMessage()
|
||||
{
|
||||
return inner.MakeMessage();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class InputHintQueue : IMessageQueue
|
||||
{
|
||||
private readonly Queue<IMessageActivity> queue = new Queue<IMessageActivity>();
|
||||
private readonly IChannelCapability channelCapability;
|
||||
private readonly Func<IDialogStack> makeStack;
|
||||
|
||||
public InputHintQueue(IChannelCapability channelCapability, Func<IDialogStack> makeStack)
|
||||
{
|
||||
SetField.NotNull(out this.channelCapability, nameof(channelCapability), channelCapability);
|
||||
SetField.NotNull(out this.makeStack, nameof(makeStack), makeStack);
|
||||
}
|
||||
|
||||
async Task IMessageQueue.QueueMessageAsync(IBotToUser botToUser, IMessageActivity message, CancellationToken token)
|
||||
{
|
||||
// This assumes that if InputHint is set on message, it is the right value that channel expects
|
||||
// and will NOT queue the message
|
||||
if (this.channelCapability.ShouldSetInputHint(message))
|
||||
{
|
||||
// drain the queue
|
||||
while (this.queue.Count > 0)
|
||||
{
|
||||
var toUser = this.queue.Dequeue();
|
||||
toUser.InputHint = InputHints.IgnoringInput;
|
||||
await botToUser.PostAsync(toUser, token);
|
||||
}
|
||||
queue.Enqueue(message);
|
||||
}
|
||||
else
|
||||
{
|
||||
await botToUser.PostAsync(message, token);
|
||||
}
|
||||
}
|
||||
|
||||
async Task IMessageQueue.DrainQueueAsync(IBotToUser botToUser, CancellationToken token)
|
||||
{
|
||||
while (this.queue.Count > 0)
|
||||
{
|
||||
var toUser = this.queue.Dequeue();
|
||||
// last message in the queue will be treated specially for channels that need input hints
|
||||
if (this.queue.Count == 0)
|
||||
{
|
||||
var stack = this.makeStack();
|
||||
if (this.channelCapability.ShouldSetInputHint(toUser) && stack.Frames.Count > 0)
|
||||
{
|
||||
var topOfStack = stack.Frames[0].Target;
|
||||
// if there is a prompt dialog on top of stack, the InputHint will be set to Expecting
|
||||
if (topOfStack != null && topOfStack.GetType().DeclaringType == typeof(PromptDialog))
|
||||
{
|
||||
toUser.InputHint = InputHints.ExpectingInput;
|
||||
}
|
||||
else
|
||||
{
|
||||
toUser.InputHint = InputHints.AcceptingInput;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
if (this.channelCapability.ShouldSetInputHint(toUser))
|
||||
{
|
||||
toUser.InputHint = InputHints.IgnoringInput;
|
||||
}
|
||||
}
|
||||
|
||||
await botToUser.PostAsync(toUser, token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface IMessageActivityMapper
|
||||
{
|
||||
IMessageActivity Map(IMessageActivity message);
|
||||
}
|
||||
|
||||
#pragma warning disable CS0618
|
||||
public sealed class KeyboardCardMapper : IMessageActivityMapper
|
||||
{
|
||||
public IMessageActivity Map(IMessageActivity message)
|
||||
{
|
||||
if (message.Attachments.Any())
|
||||
{
|
||||
var keyboards = message.Attachments.Where(t => t.ContentType == KeyboardCard.ContentType).ToList();
|
||||
if (keyboards.Count > 1)
|
||||
{
|
||||
throw new ArgumentException("Each message can only have one keyboard card!");
|
||||
}
|
||||
|
||||
var keyboard = keyboards.FirstOrDefault();
|
||||
if (keyboard != null)
|
||||
{
|
||||
message.Attachments.Remove(keyboard);
|
||||
var keyboardCard = (KeyboardCard)keyboard.Content;
|
||||
if (message.ChannelId == "facebook" && keyboardCard.Buttons.Count <= 10)
|
||||
{
|
||||
message.ChannelData = keyboardCard.ToFacebookMessage();
|
||||
}
|
||||
else
|
||||
{
|
||||
message.Attachments.Add(keyboardCard.ToHeroCard().ToAttachment());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning restore CS0618
|
||||
|
||||
public sealed class SetLocalTimestampMapper : IMessageActivityMapper
|
||||
{
|
||||
public IMessageActivity Map(IMessageActivity message)
|
||||
{
|
||||
if (message.LocalTimestamp == null)
|
||||
{
|
||||
message.LocalTimestamp = DateTimeOffset.UtcNow;
|
||||
}
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class MapToChannelData_BotToUser : IBotToUser
|
||||
{
|
||||
private readonly IBotToUser inner;
|
||||
private readonly IEnumerable<IMessageActivityMapper> mappers;
|
||||
|
||||
public MapToChannelData_BotToUser(IBotToUser inner, IEnumerable<IMessageActivityMapper> mappers)
|
||||
{
|
||||
SetField.NotNull(out this.inner, nameof(inner), inner);
|
||||
SetField.NotNull(out this.mappers, nameof(mappers), mappers);
|
||||
}
|
||||
|
||||
public async Task PostAsync(IMessageActivity message, CancellationToken cancellationToken = new CancellationToken())
|
||||
{
|
||||
foreach (var mapper in mappers)
|
||||
{
|
||||
message = mapper.Map(message);
|
||||
}
|
||||
await this.inner.PostAsync(message, cancellationToken);
|
||||
}
|
||||
|
||||
public IMessageActivity MakeMessage()
|
||||
{
|
||||
return this.inner.MakeMessage();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class BotToUserQueue : IBotToUser
|
||||
{
|
||||
private readonly IMessageActivity toBot;
|
||||
private readonly Queue<IMessageActivity> queue;
|
||||
public BotToUserQueue(IMessageActivity toBot, Queue<IMessageActivity> queue)
|
||||
{
|
||||
SetField.NotNull(out this.toBot, nameof(toBot), toBot);
|
||||
SetField.NotNull(out this.queue, nameof(queue), queue);
|
||||
}
|
||||
|
||||
IMessageActivity IBotToUser.MakeMessage()
|
||||
{
|
||||
var toBotActivity = (Activity)this.toBot;
|
||||
return toBotActivity.CreateReply();
|
||||
}
|
||||
|
||||
async Task IBotToUser.PostAsync(IMessageActivity message, CancellationToken cancellationToken)
|
||||
{
|
||||
this.queue.Enqueue(message);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class BotToUserTextWriter : IBotToUser
|
||||
{
|
||||
private readonly IBotToUser inner;
|
||||
private readonly TextWriter writer;
|
||||
public BotToUserTextWriter(IBotToUser inner, TextWriter writer)
|
||||
{
|
||||
SetField.NotNull(out this.inner, nameof(inner), inner);
|
||||
SetField.NotNull(out this.writer, nameof(writer), writer);
|
||||
}
|
||||
|
||||
IMessageActivity IBotToUser.MakeMessage()
|
||||
{
|
||||
return this.inner.MakeMessage();
|
||||
}
|
||||
|
||||
async Task IBotToUser.PostAsync(IMessageActivity message, CancellationToken cancellationToken)
|
||||
{
|
||||
await this.inner.PostAsync(message, cancellationToken);
|
||||
await this.writer.WriteLineAsync($"{message.Text}{ButtonsToText(message.Attachments)}");
|
||||
}
|
||||
|
||||
private static string ButtonsToText(IList<Attachment> attachments)
|
||||
{
|
||||
var cardAttachments = attachments?.Where(attachment => attachment.ContentType.StartsWith("application/vnd.microsoft.card"));
|
||||
var builder = new StringBuilder();
|
||||
if (cardAttachments != null && cardAttachments.Any())
|
||||
{
|
||||
builder.AppendLine();
|
||||
foreach (var attachment in cardAttachments)
|
||||
{
|
||||
string type = attachment.ContentType.Split('.').Last();
|
||||
if (type == "hero" || type == "thumbnail")
|
||||
{
|
||||
var card = (HeroCard)attachment.Content;
|
||||
if (!string.IsNullOrEmpty(card.Title))
|
||||
{
|
||||
builder.AppendLine(card.Title);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(card.Subtitle))
|
||||
{
|
||||
builder.AppendLine(card.Subtitle);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(card.Text))
|
||||
{
|
||||
builder.AppendLine(card.Text);
|
||||
}
|
||||
if (card.Buttons != null)
|
||||
{
|
||||
foreach (var button in card.Buttons)
|
||||
{
|
||||
builder.AppendLine($"* {button.Title}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using Microsoft.Bot.Builder.V3Bridge.History;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Internals.Fibers;
|
||||
using Microsoft.Bot.Schema;
|
||||
using Newtonsoft.Json;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.History
|
||||
{
|
||||
/// <summary>
|
||||
/// Log message activities between bots and users.
|
||||
/// </summary>
|
||||
public interface IActivityLogger
|
||||
{
|
||||
Task LogAsync(IActivity activity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Activity logger that traces to the console.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// To use this, you need to register the class like this:
|
||||
/// <code>
|
||||
/// var builder = new ContainerBuilder();
|
||||
/// builder.RegisterModule(Dialog_Manager.MakeRoot());
|
||||
/// builder.RegisterType<TraceActivityLogger>()
|
||||
/// .AsImplementedInterfaces()
|
||||
/// .InstancePerLifetimeScope();
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
public sealed class TraceActivityLogger : IActivityLogger
|
||||
{
|
||||
/// <summary>
|
||||
/// Log activity to trace stream.
|
||||
/// </summary>
|
||||
/// <param name="activity">Activity to log.</param>
|
||||
/// <returns></returns>
|
||||
async Task IActivityLogger.LogAsync(IActivity activity)
|
||||
{
|
||||
Trace.TraceInformation(JsonConvert.SerializeObject(activity));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Activity logger that doesn't log.
|
||||
/// </summary>
|
||||
public sealed class NullActivityLogger : IActivityLogger
|
||||
{
|
||||
/// <summary>
|
||||
/// Swallow activity.
|
||||
/// </summary>
|
||||
/// <param name="activity">Activity to be logged.</param>
|
||||
/// <returns></returns>
|
||||
async Task IActivityLogger.LogAsync(IActivity activity)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Dialogs.Internals
|
||||
{
|
||||
public sealed class LogPostToBot : IPostToBot
|
||||
{
|
||||
private readonly IPostToBot inner;
|
||||
private readonly IActivityLogger logger;
|
||||
public LogPostToBot(IPostToBot inner, IActivityLogger logger)
|
||||
{
|
||||
SetField.NotNull(out this.inner, nameof(inner), inner);
|
||||
SetField.NotNull(out this.logger, nameof(logger), logger);
|
||||
}
|
||||
|
||||
async Task IPostToBot.PostAsync(IActivity activity, CancellationToken token)
|
||||
{
|
||||
await this.logger.LogAsync(activity);
|
||||
await inner.PostAsync(activity, token);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class LogBotToUser : IBotToUser
|
||||
{
|
||||
private readonly IBotToUser inner;
|
||||
private readonly IActivityLogger logger;
|
||||
public LogBotToUser(IBotToUser inner, IActivityLogger logger)
|
||||
{
|
||||
SetField.NotNull(out this.inner, nameof(inner), inner);
|
||||
SetField.NotNull(out this.logger, nameof(logger), logger);
|
||||
}
|
||||
|
||||
IMessageActivity IBotToUser.MakeMessage()
|
||||
{
|
||||
return this.inner.MakeMessage();
|
||||
}
|
||||
|
||||
async Task IBotToUser.PostAsync(IMessageActivity message, CancellationToken cancellationToken)
|
||||
{
|
||||
await this.logger.LogAsync(message);
|
||||
await this.inner.PostAsync(message, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Dialogs.Internals
|
||||
{
|
||||
/// <summary>
|
||||
/// Private bot data.
|
||||
/// </summary>
|
||||
public interface IBotData
|
||||
{
|
||||
/// <summary>
|
||||
/// Private bot data associated with a user (across all channels and conversations).
|
||||
/// </summary>
|
||||
IBotDataBag UserData { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Private bot data associated with a conversation.
|
||||
/// </summary>
|
||||
IBotDataBag ConversationData { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Private bot data associated with a user in a conversation.
|
||||
/// </summary>
|
||||
IBotDataBag PrivateConversationData { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Loads the bot data from <see cref="IBotDataStore{T}"/>
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
Task LoadAsync(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Flushes the bot data to <see cref="IBotDataStore{T}"/>
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
Task FlushAsync(CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Dialogs
|
||||
{
|
||||
/// <summary>
|
||||
/// A property bag of bot data.
|
||||
/// </summary>
|
||||
public interface IBotDataBag
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the number of key/value pairs contained in the <see cref="IBotDataBag"/>.
|
||||
/// </summary>
|
||||
int Count { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Checks if data bag contains a value with specified key
|
||||
/// </summary>
|
||||
/// <param name="key">The key.</param>
|
||||
bool ContainsKey(string key);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value associated with the specified key.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the value to set.</typeparam>
|
||||
/// <param name="key">The key of the value to get.</param>
|
||||
/// <param name="value">
|
||||
/// When this method returns, contains the value associated with the specified key, if the key is found;
|
||||
/// otherwise, the default value for the type of the value parameter. This parameter is passed uninitialized.
|
||||
/// </param>
|
||||
/// <returns>true if the <see cref="IBotDataBag"/> contains an element with the specified key; otherwise, false.</returns>
|
||||
bool TryGetValue<T>(string key, out T value);
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified key and value to the bot data bag.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the value to get.</typeparam>
|
||||
/// <param name="key">The key of the element to add.</param>
|
||||
/// <param name="value">The value of the element to add. The value can be null for reference types.</param>
|
||||
void SetValue<T>(string key, T value);
|
||||
|
||||
/// <summary>
|
||||
/// Removes the specified key from the bot data bag.
|
||||
/// </summary>
|
||||
/// <param name="key">They key of the element to remove</param>
|
||||
/// <returns>True if removal of the key is successful; otherwise, false</returns>
|
||||
bool RemoveValue(string key);
|
||||
|
||||
/// <summary>
|
||||
/// Removes all of the values from data bag.
|
||||
/// </summary>
|
||||
void Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper methods.
|
||||
/// </summary>
|
||||
public static partial class Extensions
|
||||
{
|
||||
[System.Obsolete(@"Use GetValue<T> instead", false)]
|
||||
public static T Get<T>(this IBotDataBag bag, string key)
|
||||
{
|
||||
T value;
|
||||
if (!bag.TryGetValue(key, out value))
|
||||
{
|
||||
throw new KeyNotFoundException(key);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value associated with the specified key.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the value to get.</typeparam>
|
||||
/// <param name="bag">The bot data bag.</param>
|
||||
/// <param name="key">The key of the value to get or set.</param>
|
||||
/// <returns>The value associated with the specified key. If the specified key is not found, a get operation throws a KeyNotFoundException.</returns>
|
||||
/// <exception cref="KeyNotFoundException"><paramref name="key"/></exception>
|
||||
public static T GetValue<T>(this IBotDataBag bag, string key)
|
||||
{
|
||||
T value;
|
||||
if (!bag.TryGetValue(key, out value))
|
||||
{
|
||||
throw new KeyNotFoundException(key);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value associated with the specified key or a default value if not found.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the value to get.</typeparam>
|
||||
/// <param name="bag">The bot data bag.</param>
|
||||
/// <param name="key">The key of the value to get or set.</param>
|
||||
/// <param name="defaultValue">The value to return if the key is not present</param>
|
||||
/// <returns>The value associated with the specified key. If the specified key is not found, <paramref name="defaultValue"/>
|
||||
/// is returned </returns>
|
||||
public static T GetValueOrDefault<T>(this IBotDataBag bag, string key, T defaultValue = default(T))
|
||||
{
|
||||
T value;
|
||||
if (!bag.TryGetValue(key, out value))
|
||||
{
|
||||
value = defaultValue;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using Microsoft.Bot.Builder.V3Bridge.Internals.Fibers;
|
||||
using Microsoft.Bot.Schema;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Dialogs
|
||||
{
|
||||
public sealed class ChannelIds
|
||||
{
|
||||
public const string Facebook = "facebook";
|
||||
public const string Skype = "skype";
|
||||
public const string Msteams = "msteams";
|
||||
public const string Telegram = "telegram";
|
||||
public const string Kik = "kik";
|
||||
public const string Email = "email";
|
||||
public const string Slack = "slack";
|
||||
public const string Groupme = "groupme";
|
||||
public const string Sms = "sms";
|
||||
public const string Emulator = "emulator";
|
||||
public const string Directline = "directline";
|
||||
public const string Webchat = "webchat";
|
||||
public const string Console = "console";
|
||||
public const string Cortana = "cortana";
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Capability for a specific channel
|
||||
/// </summary>
|
||||
public interface IChannelCapability
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates if channel supports keyboard.
|
||||
/// </summary>
|
||||
/// <param name="buttonCount"> number of buttons.</param>
|
||||
/// <returns>True if the channel support number of buttons; false otherwise.</returns>
|
||||
bool SupportsKeyboards(int buttonCount);
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if channel is TTS enabled.
|
||||
/// </summary>
|
||||
/// <returns>True if channel support TTS and the bot can set <see cref="Connector.Activity.Speak"/>; false otherwise.</returns>
|
||||
bool SupportsSpeak();
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if channel relies on <see cref="Connector.Activity.InputHint"/>.
|
||||
/// </summary>
|
||||
/// <returns>True if channel expect bot setting <see cref="Connector.Activity.InputHint"/>; false otherwise </returns>
|
||||
bool NeedsInputHint();
|
||||
}
|
||||
|
||||
public sealed class ChannelCapability : IChannelCapability
|
||||
{
|
||||
private readonly IAddress address;
|
||||
|
||||
public ChannelCapability(IAddress address)
|
||||
{
|
||||
SetField.NotNull(out this.address, nameof(address), address);
|
||||
}
|
||||
|
||||
public bool NeedsInputHint()
|
||||
{
|
||||
return this.address.ChannelId == ChannelIds.Cortana;
|
||||
}
|
||||
|
||||
public bool SupportsKeyboards(int buttonCount)
|
||||
{
|
||||
switch (this.address.ChannelId)
|
||||
{
|
||||
case ChannelIds.Facebook:
|
||||
return buttonCount <= 10;
|
||||
case ChannelIds.Kik:
|
||||
return buttonCount <= 20;
|
||||
case ChannelIds.Slack:
|
||||
case ChannelIds.Telegram:
|
||||
case ChannelIds.Cortana:
|
||||
return buttonCount <= 100;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool SupportsSpeak()
|
||||
{
|
||||
return this.address.ChannelId == ChannelIds.Cortana || this.address.ChannelId == ChannelIds.Webchat;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ChannelCapabilityEx
|
||||
{
|
||||
public static bool ShouldSetInputHint(this IChannelCapability channelCapability, IMessageActivity activity)
|
||||
{
|
||||
return channelCapability.NeedsInputHint()
|
||||
&& activity.Type == ActivityTypes.Message
|
||||
&& string.IsNullOrEmpty(activity.InputHint);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using Microsoft.Bot.Builder.V3Bridge;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Internals.Fibers;
|
||||
using Microsoft.Bot.Connector;
|
||||
using Microsoft.Bot.Connector.Authentication;
|
||||
using Microsoft.Bot.Schema;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Dialogs.Internals
|
||||
{
|
||||
/// <summary>
|
||||
/// Factory for IConnectorClient.
|
||||
/// </summary>
|
||||
public interface IConnectorClientFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Make the IConnectorClient implementation.
|
||||
/// </summary>
|
||||
/// <returns>The IConnectorClient implementation.</returns>
|
||||
IConnectorClient MakeConnectorClient();
|
||||
|
||||
}
|
||||
|
||||
public sealed class ConnectorClientFactory : IConnectorClientFactory
|
||||
{
|
||||
private readonly Uri serviceUri;
|
||||
private readonly IAddress address;
|
||||
private readonly MicrosoftAppCredentials credentials;
|
||||
public ConnectorClientFactory(IAddress address, MicrosoftAppCredentials credentials)
|
||||
{
|
||||
SetField.NotNull(out this.address, nameof(address), address);
|
||||
SetField.NotNull(out this.credentials, nameof(credentials), credentials);
|
||||
|
||||
this.serviceUri = new Uri(address.ServiceUrl);
|
||||
}
|
||||
|
||||
public static bool IsEmulator(IAddress address)
|
||||
{
|
||||
return address.ChannelId.Equals(ChannelIds.Emulator, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
IConnectorClient IConnectorClientFactory.MakeConnectorClient()
|
||||
{
|
||||
return new ConnectorClient(this.serviceUri, this.credentials);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,171 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using Microsoft.Bot.Builder.V3Bridge.Dialogs.Internals;
|
||||
using Microsoft.Bot.Schema;
|
||||
using Newtonsoft.Json;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.ConnectorEx
|
||||
{
|
||||
/// <summary>
|
||||
/// Card representing a keyboard
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This will be mapped to <see cref="HeroCard"/> for all channels
|
||||
/// except Facebook. For Facebook, <see cref="KeyboardCardMapper"/> maps it
|
||||
/// to <see cref="FacebookQuickReply"/>
|
||||
/// </remarks>
|
||||
[System.Obsolete("Please use SuggestedActions instead.")]
|
||||
public partial class KeyboardCard
|
||||
{
|
||||
/// <summary>
|
||||
/// Content type of keyboard card for <see cref="Attachment.ContentType"/>.
|
||||
/// </summary>
|
||||
public const string ContentType = "application/vnd.microsoft.card.keyboard";
|
||||
|
||||
/// <summary>
|
||||
/// Constructs an instance of the keyboard card.
|
||||
/// </summary>
|
||||
/// <param name="text"> The keyboard text.</param>
|
||||
/// <param name="buttons"> The buttons in keyboard.</param>
|
||||
public KeyboardCard(string text, IList<CardAction> buttons)
|
||||
{
|
||||
Text = text;
|
||||
Buttons = buttons;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The keyboard text.
|
||||
/// </summary>
|
||||
[JsonProperty(PropertyName = "text")]
|
||||
public string Text { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The buttons in the keyboard.
|
||||
/// </summary>
|
||||
[JsonProperty(PropertyName = "buttons")]
|
||||
public IList<CardAction> Buttons { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Facebook quick reply. See https://developers.facebook.com/docs/messenger-platform/send-api-reference/quick-replies.
|
||||
/// </summary>
|
||||
public sealed class FacebookQuickReply
|
||||
{
|
||||
public sealed class ContentTypes
|
||||
{
|
||||
public const string Text = "text";
|
||||
public const string Location = "location";
|
||||
}
|
||||
|
||||
public FacebookQuickReply(string contentType, string title, string payload, string image = default(string))
|
||||
{
|
||||
ContentType = contentType;
|
||||
Title = title;
|
||||
Payload = payload;
|
||||
Image = image;
|
||||
}
|
||||
|
||||
[JsonProperty(PropertyName = "content_type")]
|
||||
public string ContentType { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "title")]
|
||||
public string Title { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "payload")]
|
||||
public string Payload { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "image_url")]
|
||||
public string Image { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Facebook message format for quick reply.
|
||||
/// </summary>
|
||||
public sealed class FacebookMessage
|
||||
{
|
||||
public FacebookMessage(string text, IList<FacebookQuickReply> quickReplies = default(IList<FacebookQuickReply>))
|
||||
{
|
||||
Text = text;
|
||||
QuickReplies = quickReplies;
|
||||
}
|
||||
|
||||
[JsonProperty(PropertyName = "text")]
|
||||
public string Text { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "quick_replies")]
|
||||
public IList<FacebookQuickReply> QuickReplies { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for <see cref="KeyboardCard"/>
|
||||
/// </summary>
|
||||
public static partial class KeyboardCardEx
|
||||
{
|
||||
#pragma warning disable CS0618
|
||||
public static Attachment ToAttachment(this KeyboardCard keyboard)
|
||||
{
|
||||
return new Attachment
|
||||
{
|
||||
ContentType = KeyboardCard.ContentType,
|
||||
Content = keyboard
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps a <see cref="KeyboardCard"/> to a <see cref="HeroCard"/>
|
||||
/// </summary>
|
||||
/// <param name="keyboard"> The keyboard card.</param>
|
||||
public static HeroCard ToHeroCard(this KeyboardCard keyboard)
|
||||
{
|
||||
return new HeroCard(text: keyboard.Text, buttons: keyboard.Buttons);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps a <see cref="KeyboardCard"/> to a <see cref="FacebookMessage"/>
|
||||
/// </summary>
|
||||
/// <param name="keyboard"> The keyboard card.</param>
|
||||
public static FacebookMessage ToFacebookMessage(this KeyboardCard keyboard)
|
||||
{
|
||||
return new FacebookMessage(text: keyboard.Text, quickReplies: keyboard.Buttons.Select(b => b.ToFacebookQuickReply()).ToList());
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
|
||||
internal static FacebookQuickReply ToFacebookQuickReply(this CardAction button)
|
||||
{
|
||||
return new FacebookQuickReply(contentType: FacebookQuickReply.ContentTypes.Text, title: button.Title, payload: (string)button.Value, image: button.Image);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Base;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Dialogs.Internals;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Internals.Fibers;
|
||||
using Microsoft.Bot.Connector.Authentication;
|
||||
using Microsoft.Bot.Schema;
|
||||
using Microsoft.Bot.Schema;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.ConnectorEx
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// The interface for finding and setting locale for <see cref="LocalizedScope"/> in <see cref="SetAmbientThreadCulture"/>.
|
||||
/// </summary>
|
||||
public interface ILocaleFinder
|
||||
{
|
||||
/// <summary>
|
||||
/// Given an activity it finds the locale.
|
||||
/// </summary>
|
||||
/// <param name="activity"> The activity.</param>
|
||||
/// <param name="token">The cancellation token.</param>
|
||||
/// <returns></returns>
|
||||
Task<string> FindLocale(IActivity activity, CancellationToken token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The locale finder implementation based on <see cref="ResumptionContext"/>.
|
||||
/// </summary>
|
||||
public sealed class LocaleFinder : ILocaleFinder
|
||||
{
|
||||
private readonly ResumptionContext resumptionContext;
|
||||
private readonly ConversationReference conversationReference;
|
||||
private string locale;
|
||||
|
||||
public LocaleFinder(ConversationReference conversationReference, ResumptionContext resumptionContext)
|
||||
{
|
||||
SetField.NotNull(out this.conversationReference, nameof(conversationReference), conversationReference);
|
||||
SetField.NotNull(out this.resumptionContext, nameof(resumptionContext), resumptionContext);
|
||||
}
|
||||
|
||||
public async Task<string> FindLocale(IActivity activity, CancellationToken token)
|
||||
{
|
||||
if (string.IsNullOrEmpty(this.locale))
|
||||
{
|
||||
var resumptionData = await this.resumptionContext.LoadDataAsync(token);
|
||||
|
||||
if (resumptionData != null && resumptionData.IsTrustedServiceUrl)
|
||||
{
|
||||
MicrosoftAppCredentials.TrustServiceUrl(this.conversationReference.ServiceUrl);
|
||||
}
|
||||
|
||||
this.locale = (activity as IMessageActivity)?.Locale;
|
||||
|
||||
// if locale is null or whitespace in the incoming request,
|
||||
// try to set it from the ResumptionContext
|
||||
if (string.IsNullOrWhiteSpace(this.locale))
|
||||
{
|
||||
this.locale = resumptionData?.Locale;
|
||||
}
|
||||
|
||||
// persist resumptionData with updated information
|
||||
var data = new ResumptionData
|
||||
{
|
||||
Locale = this.locale,
|
||||
IsTrustedServiceUrl = MicrosoftAppCredentials.IsTrustedServiceUrl(this.conversationReference.ServiceUrl)
|
||||
};
|
||||
await this.resumptionContext.SaveDataAsync(data, token);
|
||||
}
|
||||
return this.locale;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,173 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Net.Mime;
|
||||
using System.Resources;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Base;
|
||||
using Microsoft.Bot.Builder.V3Bridge.ConnectorEx;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Internals.Fibers;
|
||||
using Microsoft.Bot.Schema;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Dialogs.Internals
|
||||
{
|
||||
/// <summary>
|
||||
/// Methods to send a message from the user to the bot.
|
||||
/// </summary>
|
||||
public interface IPostToBot
|
||||
{
|
||||
/// <summary>
|
||||
/// Post an item (e.g. message or other external event) to the bot.
|
||||
/// </summary>
|
||||
/// <param name="activity">The item for the bot.</param>
|
||||
/// <param name="token">The cancellation token.</param>
|
||||
/// <returns>A task that represents the post operation.</returns>
|
||||
Task PostAsync(IActivity activity, CancellationToken token);
|
||||
}
|
||||
|
||||
public sealed class NullPostToBot : IPostToBot
|
||||
{
|
||||
Task IPostToBot.PostAsync(IActivity activity, CancellationToken token)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class PassPostToBot : IPostToBot
|
||||
{
|
||||
private readonly IPostToBot inner;
|
||||
|
||||
public PassPostToBot(IPostToBot inner)
|
||||
{
|
||||
SetField.NotNull(out this.inner, nameof(inner), inner);
|
||||
}
|
||||
|
||||
async Task IPostToBot.PostAsync(IActivity activity, CancellationToken token)
|
||||
{
|
||||
await this.inner.PostAsync(activity, token);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This IPostToBot service sets the ambient thread culture based on the <see cref="IMessageActivity.Locale"/>.
|
||||
/// </summary>
|
||||
public sealed class SetAmbientThreadCulture : IPostToBot
|
||||
{
|
||||
private readonly IPostToBot inner;
|
||||
private readonly ILocaleFinder localeFinder;
|
||||
|
||||
public SetAmbientThreadCulture(IPostToBot inner, ILocaleFinder localeFinder)
|
||||
{
|
||||
SetField.NotNull(out this.inner, nameof(inner), inner);
|
||||
SetField.NotNull(out this.localeFinder, nameof(localeFinder), localeFinder);
|
||||
}
|
||||
|
||||
async Task IPostToBot.PostAsync(IActivity activity, CancellationToken token)
|
||||
{
|
||||
var locale = await this.localeFinder.FindLocale(activity, token);
|
||||
using (var localeScope = new LocalizedScope(locale))
|
||||
{
|
||||
await this.inner.PostAsync(activity, token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This IPostToBot service serializes the execution of a particular conversation's code to avoid
|
||||
/// concurrency issues.
|
||||
/// </summary>
|
||||
public sealed class SerializeByConversation : IPostToBot
|
||||
{
|
||||
private readonly IPostToBot inner;
|
||||
private readonly IAddress address;
|
||||
private readonly IScope<IAddress> scopeForCookie;
|
||||
|
||||
public SerializeByConversation(IPostToBot inner, IAddress address, IScope<IAddress> scopeForCookie)
|
||||
{
|
||||
SetField.NotNull(out this.inner, nameof(inner), inner);
|
||||
SetField.NotNull(out this.address, nameof(address), address);
|
||||
SetField.NotNull(out this.scopeForCookie, nameof(scopeForCookie), scopeForCookie);
|
||||
}
|
||||
|
||||
async Task IPostToBot.PostAsync(IActivity activity, CancellationToken token)
|
||||
{
|
||||
using (await this.scopeForCookie.WithScopeAsync(this.address, token))
|
||||
{
|
||||
await this.inner.PostAsync(activity, token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This IPostToBot service converts any unhandled exceptions to a message sent to the user.
|
||||
/// </summary>
|
||||
public sealed class PostUnhandledExceptionToUser : IPostToBot
|
||||
{
|
||||
private readonly IPostToBot inner;
|
||||
private readonly IBotToUser botToUser;
|
||||
private readonly ResourceManager resources;
|
||||
private readonly TraceListener trace;
|
||||
|
||||
public PostUnhandledExceptionToUser(IPostToBot inner, IBotToUser botToUser, ResourceManager resources, TraceListener trace)
|
||||
{
|
||||
SetField.NotNull(out this.inner, nameof(inner), inner);
|
||||
SetField.NotNull(out this.botToUser, nameof(botToUser), botToUser);
|
||||
SetField.NotNull(out this.resources, nameof(resources), resources);
|
||||
SetField.NotNull(out this.trace, nameof(trace), trace);
|
||||
}
|
||||
|
||||
async Task IPostToBot.PostAsync(IActivity activity, CancellationToken token)
|
||||
{
|
||||
try
|
||||
{
|
||||
await this.inner.PostAsync(activity, token);
|
||||
}
|
||||
catch
|
||||
{
|
||||
try
|
||||
{
|
||||
await this.botToUser.PostAsync(this.resources.GetString("UnhandledExceptionToUser"));
|
||||
}
|
||||
catch (Exception inner)
|
||||
{
|
||||
this.trace.WriteLine(inner);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,160 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Bot.Schema;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Scorables.Internals
|
||||
{
|
||||
/// <summary>
|
||||
/// A resolver to recover C# type information from Activity schema types.
|
||||
/// </summary>
|
||||
public sealed class ActivityResolver : DelegatingResolver
|
||||
{
|
||||
public ActivityResolver(IResolver inner)
|
||||
: base(inner)
|
||||
{
|
||||
}
|
||||
|
||||
public static readonly IReadOnlyDictionary<string, Type> TypeByName = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ ActivityTypes.ContactRelationUpdate, typeof(IContactRelationUpdateActivity) },
|
||||
{ ActivityTypes.ConversationUpdate, typeof(IConversationUpdateActivity) },
|
||||
{ ActivityTypes.DeleteUserData, typeof(IActivity) },
|
||||
{ ActivityTypes.Message, typeof(IMessageActivity) },
|
||||
{ ActivityTypes.Ping, typeof(IActivity) },
|
||||
{ ActivityTypes.Event, typeof(IEventActivity) },
|
||||
{ ActivityTypes.Invoke, typeof(IInvokeActivity) },
|
||||
{ ActivityTypes.Typing, typeof(ITypingActivity) },
|
||||
};
|
||||
|
||||
public override bool TryResolve(Type type, object tag, out object value)
|
||||
{
|
||||
if (tag == null)
|
||||
{
|
||||
// if type is Activity, we're not delegating to the inner IResolver.
|
||||
if (typeof(IActivity).IsAssignableFrom(type))
|
||||
{
|
||||
// if we have a registered IActivity
|
||||
IActivity activity;
|
||||
if (this.inner.TryResolve<IActivity>(tag, out activity))
|
||||
{
|
||||
if (activity.Type != null)
|
||||
{
|
||||
// then make sure the IActivity.Type allows the desired type
|
||||
Type allowedType;
|
||||
if (TypeByName.TryGetValue(activity.Type, out allowedType))
|
||||
{
|
||||
if (type.IsAssignableFrom(allowedType))
|
||||
{
|
||||
// and make sure the actual CLR type also allows the desired type
|
||||
// (this is true most of the time since Activity implements all of the interfaces)
|
||||
Type clrType = activity.GetType();
|
||||
if (allowedType.IsAssignableFrom(clrType))
|
||||
{
|
||||
value = activity;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise we were asking for IActivity and it wasn't assignable from the IActivity.Type
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// delegate to the inner for all remaining type resolutions
|
||||
return base.TryResolve(type, tag, out value);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class PropertyResolver<T> : DelegatingResolver
|
||||
{
|
||||
public PropertyResolver(IResolver inner)
|
||||
: base(inner)
|
||||
{
|
||||
}
|
||||
|
||||
protected abstract object PropertyFrom(T item);
|
||||
|
||||
public override bool TryResolve(Type type, object tag, out object value)
|
||||
{
|
||||
T item;
|
||||
if (this.inner.TryResolve(tag, out item))
|
||||
{
|
||||
var property = PropertyFrom(item);
|
||||
if (property != null)
|
||||
{
|
||||
var propertyType = property.GetType();
|
||||
if (type.IsAssignableFrom(propertyType))
|
||||
{
|
||||
value = property;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return base.TryResolve(type, tag, out value);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class EventActivityValueResolver : PropertyResolver<IEventActivity>
|
||||
{
|
||||
public EventActivityValueResolver(IResolver inner)
|
||||
: base(inner)
|
||||
{
|
||||
}
|
||||
|
||||
protected override object PropertyFrom(IEventActivity item)
|
||||
{
|
||||
return item.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class InvokeActivityValueResolver : PropertyResolver<IInvokeActivity>
|
||||
{
|
||||
public InvokeActivityValueResolver(IResolver inner)
|
||||
: base(inner)
|
||||
{
|
||||
}
|
||||
|
||||
protected override object PropertyFrom(IInvokeActivity item)
|
||||
{
|
||||
return item.Value;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,174 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Runtime.Serialization.Formatters.Binary;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.Bot.Builder.V3Bridge.Dialogs;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Internals.Fibers;
|
||||
using Microsoft.Bot.Schema;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.ConnectorEx
|
||||
{
|
||||
/// <summary>
|
||||
/// The data persisted for ConversationReference that will be consumed for Conversation.ResumeAsync.
|
||||
/// </summary>
|
||||
public sealed class ResumptionData
|
||||
{
|
||||
/// <summary>
|
||||
/// The locale.
|
||||
/// </summary>
|
||||
public string Locale { set; get; }
|
||||
|
||||
/// <summary>
|
||||
/// The flag indicating if the ServiceUrl is trusted.
|
||||
/// </summary>
|
||||
public bool IsTrustedServiceUrl { set; get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The resumption context that is responsible for loading/persisting the <see cref="ResumptionData"/>.
|
||||
/// </summary>
|
||||
public sealed class ResumptionContext
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// The key for <see cref="ResumptionData"/> in <see cref="botDataBag"/>.
|
||||
/// </summary>
|
||||
public const string RESUMPTION_CONTEXT_KEY = "ResumptionContext";
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IBotDataBag"/> used to store the data.
|
||||
/// </summary>
|
||||
private readonly Lazy<IBotDataBag> botDataBag;
|
||||
|
||||
public ResumptionContext(Func<IBotDataBag> makeBotDataBag)
|
||||
{
|
||||
SetField.CheckNull(nameof(makeBotDataBag), makeBotDataBag);
|
||||
this.botDataBag = new Lazy<IBotDataBag>(() => makeBotDataBag());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load <see cref="ResumptionData"/> from <see cref="botDataBag"/>.
|
||||
/// </summary>
|
||||
/// <param name="token"> The cancellation token.</param>
|
||||
public async Task<ResumptionData> LoadDataAsync(CancellationToken token)
|
||||
{
|
||||
ResumptionData data;
|
||||
botDataBag.Value.TryGetValue(ResumptionContext.RESUMPTION_CONTEXT_KEY, out data);
|
||||
return data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save the <paramref name="data"/> in <see cref="botDataBag"/>.
|
||||
/// </summary>
|
||||
/// <param name="data"> The <see cref="ResumptionData"/>.</param>
|
||||
/// <param name="token"> The cancellation token.</param>
|
||||
public async Task SaveDataAsync(ResumptionData data, CancellationToken token)
|
||||
{
|
||||
var clonedData = new ResumptionData
|
||||
{
|
||||
Locale = data.Locale,
|
||||
IsTrustedServiceUrl = data.IsTrustedServiceUrl
|
||||
};
|
||||
|
||||
botDataBag.Value.SetValue(ResumptionContext.RESUMPTION_CONTEXT_KEY, clonedData);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helpers for <see cref="ConversationReference"/>
|
||||
/// </summary>
|
||||
public sealed class ConversationReferenceHelpers
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes the GZip serialized <see cref="ConversationReference"/> using <see cref="Extensions.GZipSerialize(ConversationReference)"/>.
|
||||
/// </summary>
|
||||
/// <param name="str"> The Base64 encoded string.</param>
|
||||
/// <returns> An instance of <see cref="ConversationReference"/></returns>
|
||||
public static ConversationReference GZipDeserialize(string str)
|
||||
{
|
||||
byte[] bytes = Convert.FromBase64String(str);
|
||||
|
||||
using (var stream = new MemoryStream(bytes))
|
||||
using (var gz = new GZipStream(stream, CompressionMode.Decompress))
|
||||
{
|
||||
return (ConversationReference)(new BinaryFormatter().Deserialize(gz));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static partial class Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a <see cref="ConversationReference"/> from <see cref="IAddress"/>.
|
||||
/// </summary>
|
||||
/// <param name="address"> The address.</param>
|
||||
/// <returns> The <see cref="ConversationReference"/>.</returns>
|
||||
public static ConversationReference ToConversationReference(this IAddress address)
|
||||
{
|
||||
return new ConversationReference
|
||||
{
|
||||
Bot = new ChannelAccount { Id = address.BotId },
|
||||
ChannelId = address.ChannelId,
|
||||
User = new ChannelAccount { Id = address.UserId },
|
||||
Conversation = new ConversationAccount { Id = address.ConversationId },
|
||||
ServiceUrl = address.ServiceUrl
|
||||
};
|
||||
}
|
||||
|
||||
#pragma warning disable CS0618 //disable obsolete warning for this helper.
|
||||
/// <summary>
|
||||
/// Creates a <see cref="ConversationReference"/> from <see cref="ResumptionCookie"/>.
|
||||
/// </summary>
|
||||
/// <param name="resumptionCookie"> The resumption cookie.</param>
|
||||
/// <returns> The <see cref="ConversationReference"/>.</returns>
|
||||
public static ConversationReference ToConversationReference(this ResumptionCookie resumptionCookie)
|
||||
{
|
||||
return new ConversationReference
|
||||
{
|
||||
Bot = new ChannelAccount { Id = resumptionCookie.Address.BotId, Name = resumptionCookie.UserName },
|
||||
ChannelId = resumptionCookie.Address.ChannelId,
|
||||
User = new ChannelAccount { Id = resumptionCookie.Address.UserId, Name = resumptionCookie.UserName },
|
||||
Conversation = new ConversationAccount { Id = resumptionCookie.Address.ConversationId, IsGroup = resumptionCookie.IsGroup },
|
||||
ServiceUrl = resumptionCookie.Address.ServiceUrl
|
||||
};
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="ConversationReference"/> from <see cref="IActivity"/>.
|
||||
/// </summary>
|
||||
/// <param name="activity"> The <see cref="IActivity"/> posted to bot.</param>
|
||||
/// <returns> The <see cref="ConversationReference"/>.</returns>
|
||||
public static ConversationReference ToConversationReference(this IActivity activity)
|
||||
{
|
||||
return new ConversationReference
|
||||
{
|
||||
Bot = new ChannelAccount { Id = activity.Recipient.Id, Name = activity.Recipient.Name },
|
||||
ChannelId = activity.ChannelId,
|
||||
User = new ChannelAccount { Id = activity.From.Id, Name = activity.From.Name },
|
||||
Conversation = new ConversationAccount { Id = activity.Conversation.Id, IsGroup = activity.Conversation.IsGroup, Name = activity.Conversation.Name },
|
||||
ServiceUrl = activity.ServiceUrl
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binary serializes <see cref="ConversationReference"/> using <see cref="GZipStream"/>.
|
||||
/// </summary>
|
||||
/// <param name="conversationReference"> The resumption cookie.</param>
|
||||
/// <returns> A Base64 encoded string.</returns>
|
||||
public static string GZipSerialize(this ConversationReference conversationReference)
|
||||
{
|
||||
using (var cmpStream = new MemoryStream())
|
||||
using (var stream = new GZipStream(cmpStream, CompressionMode.Compress))
|
||||
{
|
||||
new BinaryFormatter().Serialize(stream, conversationReference);
|
||||
stream.Close();
|
||||
return Convert.ToBase64String(cmpStream.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,196 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Runtime.Serialization.Formatters.Binary;
|
||||
using Microsoft.Bot.Connector.Authentication;
|
||||
using Microsoft.Bot.Schema;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Dialogs
|
||||
{
|
||||
/// <summary>
|
||||
/// The resumption cookie that can be used to resume a conversation with a user.
|
||||
/// </summary>
|
||||
[Obsolete("Use ConversationReference.")]
|
||||
[Serializable]
|
||||
public sealed class ResumptionCookie : IEquatable<ResumptionCookie>
|
||||
{
|
||||
/// <summary>
|
||||
/// The key that minimally and completely identifies a bot's conversation with a user on a channel.
|
||||
/// </summary>
|
||||
public IAddress Address { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The user name.
|
||||
/// </summary>
|
||||
public string UserName { set; get; }
|
||||
|
||||
/// <summary>
|
||||
/// True if the <see cref="IAddress.ServiceUrl"/> is trusted; False otherwise.
|
||||
/// </summary>
|
||||
/// <remarks> Conversation.ResumeAsync adds
|
||||
/// the host of the <see cref="IAddress.ServiceUrl"/> to <see cref="MicrosoftAppCredentials.TrustedHostNames"/> if this flag is True.
|
||||
/// </remarks>
|
||||
public bool IsTrustedServiceUrl { private set; get; }
|
||||
|
||||
/// <summary>
|
||||
/// The IsGroup flag for conversation.
|
||||
/// </summary>
|
||||
public bool IsGroup { set; get; }
|
||||
|
||||
/// <summary>
|
||||
/// The locale of message.
|
||||
/// </summary>
|
||||
public string Locale { set; get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of the resumption cookie.
|
||||
/// </summary>
|
||||
/// <param name="address">The address.</param>
|
||||
/// <param name="userName">The user name.</param>
|
||||
/// <param name="isGroup">The IsGroup flag for conversation.</param>
|
||||
/// <param name="locale">The locale of the message.</param>
|
||||
[JsonConstructor]
|
||||
public ResumptionCookie(Address address, string userName, bool isGroup, string locale)
|
||||
{
|
||||
this.Address = address;
|
||||
this.UserName = userName;
|
||||
this.IsGroup = isGroup;
|
||||
this.Locale = locale;
|
||||
this.IsTrustedServiceUrl = MicrosoftAppCredentials.IsTrustedServiceUrl(address.ServiceUrl);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of resumption cookie form a <see cref="IMessageActivity"/>
|
||||
/// </summary>
|
||||
/// <param name="msg">The message.</param>
|
||||
public ResumptionCookie(IMessageActivity msg)
|
||||
: this
|
||||
(
|
||||
address: Dialogs.Address.FromActivity(msg),
|
||||
userName: msg.From?.Name,
|
||||
isGroup: msg.Conversation?.IsGroup ?? false,
|
||||
locale: msg.Locale
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
public bool Equals(ResumptionCookie other)
|
||||
{
|
||||
return other != null
|
||||
&& object.Equals(this.Address, other.Address)
|
||||
&& object.Equals(this.UserName, other.UserName)
|
||||
&& this.IsTrustedServiceUrl == other.IsTrustedServiceUrl
|
||||
&& this.IsGroup == other.IsGroup
|
||||
&& object.Equals(this.Locale, other.Locale);
|
||||
}
|
||||
|
||||
public override bool Equals(object other)
|
||||
{
|
||||
return this.Equals(other as ResumptionCookie);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return this.Address.GetHashCode();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a message from the resumption cookie.
|
||||
/// </summary>
|
||||
/// <returns> The message that can be sent to bot based on the resumption cookie</returns>
|
||||
public Activity GetMessage()
|
||||
{
|
||||
return new Activity
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
Recipient = new ChannelAccount
|
||||
{
|
||||
Id = this.Address.BotId
|
||||
},
|
||||
ChannelId = this.Address.ChannelId,
|
||||
ServiceUrl = this.Address.ServiceUrl,
|
||||
Conversation = new ConversationAccount
|
||||
{
|
||||
Id = this.Address.ConversationId,
|
||||
IsGroup = this.IsGroup
|
||||
},
|
||||
From = new ChannelAccount
|
||||
{
|
||||
Id = this.Address.UserId,
|
||||
Name = this.UserName
|
||||
},
|
||||
Locale = this.Locale
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes the GZip serialized <see cref="ResumptionCookie"/> using <see cref="Extensions.GZipSerialize(ResumptionCookie)"/>.
|
||||
/// </summary>
|
||||
/// <param name="str"> The Base64 encoded string.</param>
|
||||
/// <returns> An instance of <see cref="ResumptionCookie"/></returns>
|
||||
public static ResumptionCookie GZipDeserialize(string str)
|
||||
{
|
||||
byte[] bytes = Convert.FromBase64String(str);
|
||||
|
||||
using (var stream = new MemoryStream(bytes))
|
||||
using (var gz = new GZipStream(stream, CompressionMode.Decompress))
|
||||
{
|
||||
return (ResumptionCookie)(new BinaryFormatter().Deserialize(gz));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public partial class Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Binary serializes <see cref="ResumptionCookie"/> using <see cref="GZipStream"/>.
|
||||
/// </summary>
|
||||
/// <param name="resumptionCookie"> The resumption cookie.</param>
|
||||
/// <returns> A Base64 encoded string.</returns>
|
||||
[Obsolete("Use ConversationReference.")]
|
||||
public static string GZipSerialize(this ResumptionCookie resumptionCookie)
|
||||
{
|
||||
using (var cmpStream = new MemoryStream())
|
||||
using (var stream = new GZipStream(cmpStream, CompressionMode.Compress))
|
||||
{
|
||||
new BinaryFormatter().Serialize(stream, resumptionCookie);
|
||||
stream.Close();
|
||||
return Convert.ToBase64String(cmpStream.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Bson;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Dialogs
|
||||
{
|
||||
/// <summary>
|
||||
/// Allow object instances to serialized to URLs. Base64 can not be stored in URLs due to special characters.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// We use Bson and Gzip to make it small enough to fit within the maximum character limit of URLs.
|
||||
/// http://stackoverflow.com/a/32999062 suggests HttpServerUtility's UrlTokenEncode and UrlTokenDecode
|
||||
/// is not standards-compliant, but they seem to do the job.
|
||||
/// </remarks>
|
||||
public static class UrlToken
|
||||
{
|
||||
/// <summary>
|
||||
/// Encode an item to be stored in a url.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The item type.</typeparam>
|
||||
/// <param name="item">The item instance.</param>
|
||||
/// <returns>The encoded token.</returns>
|
||||
public static string Encode<T>(T item)
|
||||
{
|
||||
using (var memory = new MemoryStream())
|
||||
{
|
||||
using (var gzip = new GZipStream(memory, CompressionMode.Compress))
|
||||
using (var writer = new BsonWriter(gzip))
|
||||
{
|
||||
var serializer = JsonSerializer.CreateDefault();
|
||||
serializer.Serialize(writer, item);
|
||||
}
|
||||
var token = HttpServerUtility.UrlTokenEncode(memory.ToArray());
|
||||
return token;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decode an item from a url token.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The item type.</typeparam>
|
||||
/// <param name="token">The item token.</param>
|
||||
/// <returns>The item instance.</returns>
|
||||
public static T Decode<T>(string token)
|
||||
{
|
||||
var buffer = HttpServerUtility.UrlTokenDecode(token);
|
||||
using (var memory = new MemoryStream(buffer))
|
||||
using (var gzip = new GZipStream(memory, CompressionMode.Decompress))
|
||||
using (var reader = new BsonReader(gzip))
|
||||
{
|
||||
var serializer = JsonSerializer.CreateDefault();
|
||||
var item = serializer.Deserialize<T>(reader);
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,195 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using Microsoft.Bot.Builder.V3Bridge.Internals.Fibers;
|
||||
using Microsoft.Bot.Schema;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Dialogs
|
||||
{
|
||||
#region Documentation
|
||||
/// <summary> Dialog that dispatches based on a regex matching input. </summary>
|
||||
#endregion
|
||||
[Serializable]
|
||||
public class CommandDialog<T> : IDialog<T>
|
||||
{
|
||||
#region Documentation
|
||||
/// <summary> A single command. </summary>
|
||||
#endregion
|
||||
[Serializable]
|
||||
public class Command
|
||||
{
|
||||
#region Documentation
|
||||
/// <summary> Gets or sets the command ID used for persisting currently running command handler. </summary>
|
||||
/// <value> Command ID. </value>
|
||||
#endregion
|
||||
public string CommandId { set; get; }
|
||||
|
||||
#region Documentation
|
||||
/// <summary> Gets or sets the regular expression for matching command. </summary>
|
||||
/// <value> The regular expression. </value>
|
||||
#endregion
|
||||
public Regex Expression { set; get; }
|
||||
|
||||
#region Documentation
|
||||
/// <summary> Gets or sets the command handler. </summary>
|
||||
/// <value> The command handler. </value>
|
||||
#endregion
|
||||
public ResumeAfter<IMessageActivity> CommandHandler { set; get; }
|
||||
}
|
||||
|
||||
private Command defaultCommand;
|
||||
private readonly List<Command> commands = new List<Command>();
|
||||
private readonly Dictionary<string, Delegate> resultHandlers = new Dictionary<string, Delegate>();
|
||||
|
||||
async Task IDialog<T>.StartAsync(IDialogContext context)
|
||||
{
|
||||
context.Wait(MessageReceived);
|
||||
}
|
||||
|
||||
public virtual async Task MessageReceived(IDialogContext context, IAwaitable<IMessageActivity> message)
|
||||
{
|
||||
var text = (await message).Text;
|
||||
Command matched = null;
|
||||
for (int idx = 0; idx < commands.Count; idx++)
|
||||
{
|
||||
var handler = commands[idx];
|
||||
if (handler.Expression.Match(text).Success)
|
||||
{
|
||||
matched = handler;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (matched == null && this.defaultCommand != null)
|
||||
{
|
||||
matched = this.defaultCommand;
|
||||
}
|
||||
|
||||
if (matched != null)
|
||||
{
|
||||
context.PrivateConversationData.SetValue("ActiveCommandId", matched.CommandId);
|
||||
await matched.CommandHandler(context, message);
|
||||
}
|
||||
else
|
||||
{
|
||||
string error = $"CommandDialog doesn't have a registered command handler for this message: {text}";
|
||||
throw new InvalidOperationException(error);
|
||||
}
|
||||
}
|
||||
|
||||
#region Documentation
|
||||
/// <summary>
|
||||
/// The result handler of the command dialog passed to the child dialogs.
|
||||
/// </summary>
|
||||
/// <typeparam name="U"> The type of the result returned by the child dialog. </typeparam>
|
||||
/// <param name="context"> Dialog context. </param>
|
||||
/// <param name="result"> The result retured by the child dialog. </param>
|
||||
#endregion
|
||||
public virtual async Task ResultHandler<U>(IDialogContext context, IAwaitable<U> result)
|
||||
{
|
||||
Delegate handler;
|
||||
string commandId;
|
||||
if (context.PrivateConversationData.TryGetValue("ActiveCommandId", out commandId) && resultHandlers.TryGetValue(commandId, out handler))
|
||||
{
|
||||
await ((ResumeAfter<U>)handler).Invoke(context, result);
|
||||
context.Wait(MessageReceived);
|
||||
}
|
||||
else
|
||||
{
|
||||
string error = $"CommandDialog doesn't have a registered result handler for this type: {typeof(U)}";
|
||||
throw new InvalidOperationException(error);
|
||||
}
|
||||
}
|
||||
|
||||
#region Documentation
|
||||
/// <summary> Define a handler that is fired on a regular expression match of a message. </summary>
|
||||
/// <typeparam name="U"> Type of input to result handler. </typeparam>
|
||||
/// <param name="expression"> Regular expression to match. </param>
|
||||
/// <param name="handler"> Handler to call on match. </param>
|
||||
/// <param name="resultHandler"> Optional result handler to be called if handler is creating a chaild dialog. </param>
|
||||
/// <returns> A commandDialog. </returns>
|
||||
#endregion
|
||||
public CommandDialog<T> On<U>(Regex expression, ResumeAfter<IMessageActivity> handler, ResumeAfter<U> resultHandler = null)
|
||||
{
|
||||
var command = new Command
|
||||
{
|
||||
CommandId = ComputeHash(expression.ToString()),
|
||||
Expression = expression,
|
||||
CommandHandler = handler,
|
||||
};
|
||||
commands.Add(command);
|
||||
RegisterResultHandler(command, resultHandler);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
#region Documentation
|
||||
/// <summary> Define the default action if no match. </summary>
|
||||
/// <typeparam name="U"> Type of input to result handler. </typeparam>
|
||||
/// <param name="handler"> Handler to call if no match. </param>
|
||||
/// <param name="resultHandler"> Optional result handler to be called if handler is creating a chaild dialog. </param>
|
||||
/// <returns> A CommandDialog. </returns>
|
||||
#endregion
|
||||
public CommandDialog<T> OnDefault<U>(ResumeAfter<IMessageActivity> handler, ResumeAfter<U> resultHandler = null)
|
||||
{
|
||||
var command = new Command { CommandId = "defaultResultHandler", CommandHandler = handler };
|
||||
this.defaultCommand = command;
|
||||
RegisterResultHandler(command, resultHandler);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private void RegisterResultHandler<U>(Command command, ResumeAfter<U> resultHandler)
|
||||
{
|
||||
if (resultHandler != null)
|
||||
{
|
||||
resultHandlers.Add(command.CommandId, resultHandler);
|
||||
}
|
||||
}
|
||||
|
||||
private string ComputeHash(string str)
|
||||
{
|
||||
var algorithm = SHA1.Create();
|
||||
return Convert.ToBase64String(algorithm.ComputeHash(Encoding.UTF8.GetBytes(str)));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using Microsoft.Bot.Builder.V3Bridge.Dialogs.Internals;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Internals.Fibers;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Scorables.Internals;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Resource;
|
||||
using Microsoft.Bot.Schema;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Resource;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Dialogs
|
||||
{
|
||||
public sealed class DeleteProfileScorable : ScorableBase<IActivity, string, double>
|
||||
{
|
||||
private readonly IDialogStack stack;
|
||||
private readonly IBotData botData;
|
||||
private readonly IBotToUser botToUser;
|
||||
private readonly Regex regex;
|
||||
|
||||
public DeleteProfileScorable(IDialogStack stack, IBotData botData, IBotToUser botToUser, Regex regex)
|
||||
{
|
||||
SetField.NotNull(out this.stack, nameof(stack), stack);
|
||||
SetField.NotNull(out this.botData, nameof(botData), botData);
|
||||
SetField.NotNull(out this.botToUser, nameof(botToUser), botToUser);
|
||||
SetField.NotNull(out this.regex, nameof(regex), regex);
|
||||
}
|
||||
|
||||
protected override async Task<string> PrepareAsync(IActivity activity, CancellationToken token)
|
||||
{
|
||||
var message = activity as IMessageActivity;
|
||||
if (message != null && message.Text != null)
|
||||
{
|
||||
var text = message.Text;
|
||||
var match = regex.Match(text);
|
||||
if (match.Success)
|
||||
{
|
||||
return match.Groups[0].Value;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override bool HasScore(IActivity item, string state)
|
||||
{
|
||||
return state != null;
|
||||
}
|
||||
|
||||
protected override double GetScore(IActivity item, string state)
|
||||
{
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
protected override async Task PostAsync(IActivity item, string state, CancellationToken token)
|
||||
{
|
||||
this.stack.Reset();
|
||||
botData.UserData.Clear();
|
||||
botData.PrivateConversationData.Clear();
|
||||
await botData.FlushAsync(token);
|
||||
await botToUser.PostAsync(Resources.UserProfileDeleted);
|
||||
}
|
||||
protected override Task DoneAsync(IActivity item, string state, CancellationToken token)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,152 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.Bot.Builder.V3Bridge.Internals.Fibers;
|
||||
using Microsoft.Bot.Schema;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Dialogs.Internals
|
||||
{
|
||||
public sealed class DialogContext : IDialogContext
|
||||
{
|
||||
private readonly IBotToUser botToUser;
|
||||
private readonly IBotData botData;
|
||||
private readonly IDialogStack stack;
|
||||
private readonly CancellationToken token;
|
||||
private readonly IActivity activity;
|
||||
|
||||
public DialogContext(IBotToUser botToUser, IBotData botData, IDialogStack stack, IActivity activity, CancellationToken token)
|
||||
{
|
||||
SetField.NotNull(out this.botToUser, nameof(botToUser), botToUser);
|
||||
SetField.NotNull(out this.botData, nameof(botData), botData);
|
||||
SetField.NotNull(out this.stack, nameof(stack), stack);
|
||||
SetField.NotNull(out this.activity, nameof(activity), activity);
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
IBotDataBag IBotData.ConversationData
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.botData.ConversationData;
|
||||
}
|
||||
}
|
||||
|
||||
IBotDataBag IBotData.PrivateConversationData
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.botData.PrivateConversationData;
|
||||
}
|
||||
}
|
||||
|
||||
IBotDataBag IBotData.UserData
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.botData.UserData;
|
||||
}
|
||||
}
|
||||
|
||||
async Task IBotToUser.PostAsync(IMessageActivity message, CancellationToken cancellationToken)
|
||||
{
|
||||
await this.botToUser.PostAsync(message, cancellationToken);
|
||||
}
|
||||
|
||||
IMessageActivity IBotToUser.MakeMessage()
|
||||
{
|
||||
return this.botToUser.MakeMessage();
|
||||
}
|
||||
|
||||
IReadOnlyList<Delegate> IDialogStack.Frames
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.stack.Frames;
|
||||
}
|
||||
}
|
||||
|
||||
void IDialogStack.Call<R>(IDialog<R> child, ResumeAfter<R> resume)
|
||||
{
|
||||
this.stack.Call<R>(child, resume);
|
||||
}
|
||||
|
||||
void IDialogStack.Post<E>(E @event, ResumeAfter<E> resume)
|
||||
{
|
||||
this.stack.Post<E>(@event, resume);
|
||||
}
|
||||
|
||||
async Task IDialogStack.Forward<R, T>(IDialog<R> child, ResumeAfter<R> resume, T item, CancellationToken token)
|
||||
{
|
||||
await this.stack.Forward<R, T>(child, resume, item, token);
|
||||
}
|
||||
|
||||
void IDialogStack.Done<R>(R value)
|
||||
{
|
||||
this.stack.Done<R>(value);
|
||||
}
|
||||
|
||||
void IDialogStack.Fail(Exception error)
|
||||
{
|
||||
this.stack.Fail(error);
|
||||
}
|
||||
|
||||
void IDialogStack.Wait<R>(ResumeAfter<R> resume)
|
||||
{
|
||||
this.stack.Wait(resume);
|
||||
}
|
||||
|
||||
void IDialogStack.Reset()
|
||||
{
|
||||
this.stack.Reset();
|
||||
}
|
||||
|
||||
async Task IBotData.LoadAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await this.botData.LoadAsync(cancellationToken);
|
||||
}
|
||||
|
||||
async Task IBotData.FlushAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await this.botData.FlushAsync(cancellationToken);
|
||||
}
|
||||
|
||||
CancellationToken IBotContext.CancellationToken => this.token;
|
||||
|
||||
IActivity IBotContext.Activity => this.activity;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using Microsoft.Bot.Builder.V3Bridge.Internals.Fibers;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Scorables.Internals;
|
||||
using Microsoft.Bot.Schema;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Scorables;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Dialogs.Internals
|
||||
{
|
||||
/// <summary>
|
||||
/// Scorable for Dialog module routing.
|
||||
/// </summary>
|
||||
public sealed class DialogRouter : DelegatingScorable<IActivity, double>
|
||||
{
|
||||
public static IEnumerable<IScorable<IActivity, double>> EnumerateRelevant(
|
||||
IDialogStack stack,
|
||||
IEnumerable<IScorable<IActivity, double>> fromActivity,
|
||||
IEnumerable<IScorable<IResolver, double>> fromResolver,
|
||||
Func<IActivity, IResolver> makeResolver)
|
||||
{
|
||||
// first, let's go through stack frames
|
||||
var targets = stack.Frames.Select(f => f.Target);
|
||||
foreach (var target in targets)
|
||||
{
|
||||
var activityScorable = target as IScorable<IActivity, double>;
|
||||
if (activityScorable != null)
|
||||
{
|
||||
yield return activityScorable;
|
||||
}
|
||||
|
||||
var resolverScorable = target as IScorable<IResolver, double>;
|
||||
if (resolverScorable != null)
|
||||
{
|
||||
yield return resolverScorable.SelectItem(makeResolver);
|
||||
}
|
||||
}
|
||||
|
||||
// then global scorables "on the side"
|
||||
foreach (var activityScorable in fromActivity)
|
||||
{
|
||||
yield return activityScorable;
|
||||
}
|
||||
|
||||
foreach (var resolverScorable in fromResolver)
|
||||
{
|
||||
yield return resolverScorable.SelectItem(makeResolver);
|
||||
}
|
||||
}
|
||||
|
||||
public static IScorable<IActivity, double> MakeDelegate(
|
||||
IDialogStack stack,
|
||||
IEnumerable<IScorable<IActivity, double>> fromActivity,
|
||||
IEnumerable<IScorable<IResolver, double>> fromResolver,
|
||||
Func<IActivity, IResolver> makeResolver,
|
||||
ITraits<double> traits,
|
||||
IComparer<double> comparer)
|
||||
{
|
||||
// since the stack of scorables changes over time, this should be lazy
|
||||
var relevant = EnumerateRelevant(stack, fromActivity, fromResolver, makeResolver);
|
||||
var significant = relevant.Select(s => s.WhereScore((_, score) => comparer.Compare(score, traits.Minimum) >= 0));
|
||||
var scorable = new TraitsScorable<IActivity, double>(traits, comparer, significant);
|
||||
return scorable;
|
||||
}
|
||||
|
||||
public DialogRouter(
|
||||
IDialogStack stack,
|
||||
IEnumerable<IScorable<IActivity, double>> fromActivity,
|
||||
IEnumerable<IScorable<IResolver, double>> fromResolver,
|
||||
Func<IActivity, IResolver> makeResolver,
|
||||
ITraits<double> traits,
|
||||
IComparer<double> comparer)
|
||||
: base(MakeDelegate(stack, fromActivity, fromResolver, makeResolver, traits, comparer))
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using Microsoft.Bot.Builder.V3Bridge.Base;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Dialogs.Internals;
|
||||
using Microsoft.Bot.Schema;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Internals.Fibers;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Dialogs.Internals
|
||||
{
|
||||
/// <summary>
|
||||
/// The dialog system represents the top-level interface for the dialog tasks and their event loop.
|
||||
/// </summary>
|
||||
public interface IDialogSystem : IDialogTasks, IEventLoop, IEventProducer<IActivity>
|
||||
{
|
||||
}
|
||||
|
||||
public sealed class DialogSystem : IDialogSystem
|
||||
{
|
||||
private readonly IDialogTasks tasks;
|
||||
private readonly IEventLoop loop;
|
||||
private readonly IEventProducer<IActivity> queue;
|
||||
public DialogSystem(IDialogTasks tasks, IEventLoop loop, IEventProducer<IActivity> queue)
|
||||
{
|
||||
SetField.NotNull(out this.tasks, nameof(tasks), tasks);
|
||||
SetField.NotNull(out this.loop, nameof(loop), loop);
|
||||
SetField.NotNull(out this.queue, nameof(queue), queue);
|
||||
}
|
||||
|
||||
IReadOnlyList<IDialogTask> IDialogTasks.DialogTasks => this.tasks.DialogTasks;
|
||||
|
||||
IDialogTask IDialogTasks.CreateDialogTask()
|
||||
{
|
||||
return this.tasks.CreateDialogTask();
|
||||
}
|
||||
|
||||
async Task IEventLoop.PollAsync(CancellationToken token)
|
||||
{
|
||||
await this.loop.PollAsync(token);
|
||||
}
|
||||
|
||||
void IEventProducer<IActivity>.Post(IActivity activity, Action onPull)
|
||||
{
|
||||
this.queue.Post(activity, onPull);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,489 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.Bot.Builder.V3Bridge.Base;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Internals.Fibers;
|
||||
using Microsoft.Bot.Schema;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Dialogs.Internals
|
||||
{
|
||||
/// <summary>
|
||||
/// A dialog task is a
|
||||
/// 1. single <see cref="IDialogStack"/> stack of <see cref="IDialog"/> frames, waiting on the next <see cref="IActivity"/>
|
||||
/// 2. the <see cref="IEventProducer{Activity}"/> queue of activity events necessary to satisfy those waits
|
||||
/// 2. the <see cref="IEventLoop"/> loop to execute that dialog code once the waits are satisfied
|
||||
/// </summary>
|
||||
public sealed class DialogTask : IDialogTask
|
||||
{
|
||||
private readonly Func<CancellationToken, IDialogContext> makeContext;
|
||||
private readonly IStore<IFiberLoop<DialogTask>> store;
|
||||
private readonly IEventProducer<IActivity> queue;
|
||||
private readonly IFiberLoop<DialogTask> fiber;
|
||||
private readonly Frames frames;
|
||||
public DialogTask(Func<CancellationToken, IDialogContext> makeContext, IStore<IFiberLoop<DialogTask>> store, IEventProducer<IActivity> queue)
|
||||
{
|
||||
SetField.NotNull(out this.makeContext, nameof(makeContext), makeContext);
|
||||
SetField.NotNull(out this.store, nameof(store), store);
|
||||
SetField.NotNull(out this.queue, nameof(queue), queue);
|
||||
this.store.TryLoad(out this.fiber);
|
||||
this.frames = new Frames(this);
|
||||
}
|
||||
|
||||
private IWait<DialogTask> nextWait;
|
||||
private IWait<DialogTask> NextWait()
|
||||
{
|
||||
if (this.fiber.Frames.Count > 0)
|
||||
{
|
||||
var nextFrame = this.fiber.Frames.Peek();
|
||||
|
||||
switch (nextFrame.Wait.Need)
|
||||
{
|
||||
case Need.Wait:
|
||||
// since the leaf frame is waiting, save this wait as the mark for that frame
|
||||
nextFrame.Mark = nextFrame.Wait.CloneTyped();
|
||||
break;
|
||||
case Need.Call:
|
||||
// because the user did not specify a new wait for the leaf frame during the call,
|
||||
// reuse the previous mark for this frame
|
||||
this.nextWait = nextFrame.Wait = nextFrame.Mark.CloneTyped();
|
||||
break;
|
||||
case Need.None:
|
||||
case Need.Poll:
|
||||
break;
|
||||
case Need.Done:
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
return this.nextWait;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adjust the calling convention from Dialog's to Fiber's delegates.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// https://en.wikipedia.org/wiki/Thunk
|
||||
/// </remarks>
|
||||
public interface IThunk
|
||||
{
|
||||
Delegate Method { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adjust the calling convention from Dialog's to Fiber's delegates
|
||||
/// for IDialog.StartAsync.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
private sealed class ThunkStart : IThunk
|
||||
{
|
||||
private readonly StartAsync start;
|
||||
public ThunkStart(StartAsync start)
|
||||
{
|
||||
SetField.NotNull(out this.start, nameof(start), start);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.start.Target}.{this.start.Method.Name}";
|
||||
}
|
||||
|
||||
Delegate IThunk.Method => this.start;
|
||||
|
||||
public async Task<IWait<DialogTask>> Rest(IFiber<DialogTask> fiber, DialogTask task, IItem<object> item, CancellationToken token)
|
||||
{
|
||||
var result = await item;
|
||||
if (result != null)
|
||||
{
|
||||
throw new ArgumentException(nameof(item));
|
||||
}
|
||||
|
||||
await this.start(task.makeContext(token));
|
||||
return task.NextWait();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adjust the calling convention from Dialog's to Fiber's delegates
|
||||
/// for IDialog's <see cref="ResumeAfter{T}"/>.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
private sealed class ThunkResume<T> : IThunk
|
||||
{
|
||||
private readonly ResumeAfter<T> resume;
|
||||
public ThunkResume(ResumeAfter<T> resume)
|
||||
{
|
||||
SetField.NotNull(out this.resume, nameof(resume), resume);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.resume.Target}.{this.resume.Method.Name}";
|
||||
}
|
||||
|
||||
Delegate IThunk.Method => this.resume;
|
||||
|
||||
public async Task<IWait<DialogTask>> Rest(IFiber<DialogTask> fiber, DialogTask task, IItem<T> item, CancellationToken token)
|
||||
{
|
||||
await this.resume(task.makeContext(token), item);
|
||||
return task.NextWait();
|
||||
}
|
||||
}
|
||||
|
||||
internal Rest<DialogTask, object> ToRest(StartAsync start)
|
||||
{
|
||||
var thunk = new ThunkStart(start);
|
||||
return thunk.Rest;
|
||||
}
|
||||
|
||||
internal Rest<DialogTask, T> ToRest<T>(ResumeAfter<T> resume)
|
||||
{
|
||||
var thunk = new ThunkResume<T>(resume);
|
||||
return thunk.Rest;
|
||||
}
|
||||
|
||||
private sealed class Frames : IReadOnlyList<Delegate>
|
||||
{
|
||||
private readonly DialogTask task;
|
||||
public Frames(DialogTask task)
|
||||
{
|
||||
SetField.NotNull(out this.task, nameof(task), task);
|
||||
}
|
||||
|
||||
int IReadOnlyCollection<Delegate>.Count
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.task.fiber.Frames.Count;
|
||||
}
|
||||
}
|
||||
|
||||
public Delegate Map(int ordinal)
|
||||
{
|
||||
var frames = this.task.fiber.Frames;
|
||||
int index = frames.Count - ordinal - 1;
|
||||
var frame = frames[index];
|
||||
var wait = frame.Wait;
|
||||
var rest = wait.Rest;
|
||||
var thunk = (IThunk)rest.Target;
|
||||
return thunk.Method;
|
||||
}
|
||||
|
||||
Delegate IReadOnlyList<Delegate>.this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.Map(index);
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
IEnumerable<Delegate> enumerable = this;
|
||||
return enumerable.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator<Delegate> IEnumerable<Delegate>.GetEnumerator()
|
||||
{
|
||||
var frames = this.task.fiber.Frames;
|
||||
for (int index = 0; index < frames.Count; ++index)
|
||||
{
|
||||
yield return this.Map(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IReadOnlyList<Delegate> IDialogStack.Frames
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.frames;
|
||||
}
|
||||
}
|
||||
|
||||
void IDialogStack.Call<R>(IDialog<R> child, ResumeAfter<R> resume)
|
||||
{
|
||||
var callRest = ToRest(child.StartAsync);
|
||||
if (resume != null)
|
||||
{
|
||||
var doneRest = ToRest(resume);
|
||||
this.nextWait = this.fiber.Call<DialogTask, object, R>(callRest, null, doneRest);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.nextWait = this.fiber.Call<DialogTask, object>(callRest, null);
|
||||
}
|
||||
}
|
||||
|
||||
async Task IDialogStack.Forward<R, T>(IDialog<R> child, ResumeAfter<R> resume, T item, CancellationToken token)
|
||||
{
|
||||
// put the child on the stack
|
||||
IDialogStack stack = this;
|
||||
stack.Call(child, resume);
|
||||
// run the loop
|
||||
IEventLoop loop = this;
|
||||
await loop.PollAsync(token);
|
||||
// forward the item
|
||||
this.fiber.Post(item);
|
||||
// run the loop again
|
||||
await loop.PollAsync(token);
|
||||
}
|
||||
|
||||
void IDialogStack.Done<R>(R value)
|
||||
{
|
||||
this.nextWait = this.fiber.Done(value);
|
||||
}
|
||||
|
||||
void IDialogStack.Fail(Exception error)
|
||||
{
|
||||
this.nextWait = this.fiber.Fail(error);
|
||||
}
|
||||
|
||||
void IDialogStack.Wait<R>(ResumeAfter<R> resume)
|
||||
{
|
||||
this.nextWait = this.fiber.Wait<DialogTask, R>(ToRest(resume));
|
||||
}
|
||||
|
||||
void IDialogStack.Post<E>(E @event, ResumeAfter<E> resume)
|
||||
{
|
||||
// schedule the wait for event delivery
|
||||
this.nextWait = this.fiber.Wait<DialogTask, E>(ToRest(resume));
|
||||
|
||||
// save the wait for this event, in case the scorable action event handlers manipulate the stack
|
||||
var wait = this.nextWait;
|
||||
Action onPull = () =>
|
||||
{
|
||||
// and satisfy that particular saved wait when the event has been pulled from the queue
|
||||
wait.Post(@event);
|
||||
};
|
||||
|
||||
// post the activity to the queue
|
||||
var activity = new Activity(ActivityTypes.Event) { Value = @event };
|
||||
this.queue.Post(activity, onPull);
|
||||
}
|
||||
|
||||
void IDialogStack.Reset()
|
||||
{
|
||||
this.store.Reset();
|
||||
this.store.Flush();
|
||||
this.fiber.Reset();
|
||||
}
|
||||
|
||||
async Task IEventLoop.PollAsync(CancellationToken token)
|
||||
{
|
||||
try
|
||||
{
|
||||
await this.fiber.PollAsync(this, token);
|
||||
|
||||
// this line will throw an error if the code does not schedule the next callback
|
||||
// to wait for the next message sent from the user to the bot.
|
||||
this.fiber.Wait.ValidateNeed(Need.Wait);
|
||||
}
|
||||
catch
|
||||
{
|
||||
this.store.Reset();
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.store.Save(this.fiber);
|
||||
this.store.Flush();
|
||||
}
|
||||
}
|
||||
|
||||
void IEventProducer<IActivity>.Post(IActivity item, Action onPull)
|
||||
{
|
||||
this.fiber.Post(item);
|
||||
onPull?.Invoke();
|
||||
}
|
||||
|
||||
internal IStore<IFiberLoop<DialogTask>> Store
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.store;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A reactive dialog task (in contrast to a proactive dialog task) is a dialog task that
|
||||
/// starts some root dialog when it receives the first <see cref="IActivity"/> activity.
|
||||
/// </summary>
|
||||
public sealed class ReactiveDialogTask : IEventLoop, IEventProducer<IActivity>
|
||||
{
|
||||
private readonly IDialogTask dialogTask;
|
||||
private readonly Func<IDialog<object>> makeRoot;
|
||||
|
||||
public ReactiveDialogTask(IDialogTask dialogTask, Func<IDialog<object>> makeRoot)
|
||||
{
|
||||
SetField.NotNull(out this.dialogTask, nameof(dialogTask), dialogTask);
|
||||
SetField.NotNull(out this.makeRoot, nameof(makeRoot), makeRoot);
|
||||
}
|
||||
|
||||
async Task IEventLoop.PollAsync(CancellationToken token)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (this.dialogTask.Frames.Count == 0)
|
||||
{
|
||||
var root = this.makeRoot();
|
||||
var loop = root.Loop();
|
||||
this.dialogTask.Call(loop, null);
|
||||
}
|
||||
|
||||
await this.dialogTask.PollAsync(token);
|
||||
}
|
||||
catch
|
||||
{
|
||||
this.dialogTask.Reset();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void IEventProducer<IActivity>.Post(IActivity item, Action onPull)
|
||||
{
|
||||
this.dialogTask.Post(item, onPull);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This dialog task translates from the more orthogonal (opaque) fiber exceptions
|
||||
/// to the more readable dialog programming model exceptions.
|
||||
/// </summary>
|
||||
public sealed class ExceptionTranslationDialogTask : IPostToBot
|
||||
{
|
||||
private readonly IPostToBot inner;
|
||||
|
||||
public ExceptionTranslationDialogTask(IPostToBot inner)
|
||||
{
|
||||
SetField.NotNull(out this.inner, nameof(inner), inner);
|
||||
}
|
||||
|
||||
async Task IPostToBot.PostAsync(IActivity activity, CancellationToken token)
|
||||
{
|
||||
try
|
||||
{
|
||||
await this.inner.PostAsync(activity, token);
|
||||
}
|
||||
catch (InvalidNeedException error) when (error.Need == Need.Wait && error.Have == Need.None)
|
||||
{
|
||||
throw new NoResumeHandlerException(error);
|
||||
}
|
||||
catch (InvalidNeedException error) when (error.Need == Need.Wait && error.Have == Need.Done)
|
||||
{
|
||||
throw new NoResumeHandlerException(error);
|
||||
}
|
||||
catch (InvalidNeedException error) when (error.Need == Need.Call && error.Have == Need.Wait)
|
||||
{
|
||||
throw new MultipleResumeHandlerException(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class EventLoopDialogTask : IPostToBot
|
||||
{
|
||||
private readonly Lazy<IEventLoop> inner;
|
||||
private readonly IEventProducer<IActivity> queue;
|
||||
public EventLoopDialogTask(Func<IEventLoop> makeInner, IEventProducer<IActivity> queue, IBotData botData)
|
||||
{
|
||||
SetField.NotNull(out this.queue, nameof(queue), queue);
|
||||
SetField.CheckNull(nameof(makeInner), makeInner);
|
||||
this.inner = new Lazy<IEventLoop>(() => makeInner());
|
||||
}
|
||||
|
||||
async Task IPostToBot.PostAsync(IActivity activity, CancellationToken token)
|
||||
{
|
||||
this.queue.Post(activity);
|
||||
var loop = this.inner.Value;
|
||||
await loop.PollAsync(token);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public sealed class QueueDrainingDialogTask : IPostToBot
|
||||
{
|
||||
private readonly IPostToBot inner;
|
||||
private readonly IBotToUser botToUser;
|
||||
private readonly IMessageQueue messageQueue;
|
||||
|
||||
public QueueDrainingDialogTask(IPostToBot inner, IBotToUser botToUser, IMessageQueue messageQueue)
|
||||
{
|
||||
SetField.NotNull(out this.inner, nameof(inner), inner);
|
||||
SetField.NotNull(out this.botToUser, nameof(botToUser), botToUser);
|
||||
SetField.NotNull(out this.messageQueue, nameof(messageQueue), messageQueue);
|
||||
}
|
||||
|
||||
async Task IPostToBot.PostAsync(IActivity activity, CancellationToken token)
|
||||
{
|
||||
await this.inner.PostAsync(activity, token);
|
||||
await this.messageQueue.DrainQueueAsync(this.botToUser, token);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This dialog task loads the dialog stack from <see cref="IBotData"/> before handling the incoming
|
||||
/// activity and saves the dialog stack to <see cref="IBotData"/> afterwards.
|
||||
/// </summary>
|
||||
public sealed class PersistentDialogTask : IPostToBot
|
||||
{
|
||||
private readonly IPostToBot inner;
|
||||
private readonly IBotData botData;
|
||||
|
||||
public PersistentDialogTask(IPostToBot inner, IBotData botData)
|
||||
{
|
||||
SetField.NotNull(out this.inner, nameof(inner), inner);
|
||||
SetField.NotNull(out this.botData, nameof(botData), botData);
|
||||
}
|
||||
|
||||
async Task IPostToBot.PostAsync(IActivity activity, CancellationToken token)
|
||||
{
|
||||
await botData.LoadAsync(token);
|
||||
try
|
||||
{
|
||||
await this.inner.PostAsync(activity, token);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await botData.FlushAsync(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.Bot.Builder.V3Bridge.Internals.Fibers;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Base;
|
||||
using Microsoft.Bot.Schema;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Dialogs.Internals
|
||||
{
|
||||
public interface IDialogTasks
|
||||
{
|
||||
/// <summary>
|
||||
/// The list of <see cref="IDialogTask"/>
|
||||
/// </summary>
|
||||
IReadOnlyList<IDialogTask> DialogTasks { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="IDialogTask"/> and add it to <see cref="DialogTasks"/>
|
||||
/// </summary>
|
||||
IDialogTask CreateDialogTask();
|
||||
}
|
||||
|
||||
public interface IDialogTaskManager : IDialogTasks
|
||||
{
|
||||
// TODO: move these to separate interface, remove IDialogTaskManager (interface segregation principle)
|
||||
// TODO: possibly share with IBotData LoadAsync and FlushAsync,
|
||||
|
||||
/// <summary>
|
||||
/// Loads the <see cref="IDialogTasks.DialogTasks"/> from <see cref="IBotDataBag"/>.
|
||||
/// </summary>
|
||||
/// <param name="token"> The cancellation token.</param>
|
||||
Task LoadDialogTasks(CancellationToken token);
|
||||
|
||||
/// <summary>
|
||||
/// Flushes the <see cref="IDialogTask"/> in <see cref="IDialogTasks.DialogTasks"/>
|
||||
/// </summary>
|
||||
/// <param name="token"> The cancellation token.</param>
|
||||
Task FlushDialogTasks(CancellationToken token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class is responsible for managing the set of dialog tasks.
|
||||
/// </summary>
|
||||
public sealed class DialogTaskManager : IDialogTaskManager
|
||||
{
|
||||
private readonly string blobKeyPrefix;
|
||||
private readonly IBotData botData;
|
||||
private readonly IStackStoreFactory<DialogTask> stackStoreFactory;
|
||||
private readonly Func<IDialogStack, CancellationToken, IDialogContext> contextFactory;
|
||||
private readonly IEventProducer<IActivity> queue;
|
||||
|
||||
private List<DialogTask> dialogTasks;
|
||||
|
||||
public DialogTaskManager(string blobKeyPrefix, IBotData botData,
|
||||
IStackStoreFactory<DialogTask> stackStoreFactory,
|
||||
Func<IDialogStack, CancellationToken, IDialogContext> contextFactory,
|
||||
IEventProducer<IActivity> queue)
|
||||
{
|
||||
SetField.NotNull(out this.blobKeyPrefix, nameof(blobKeyPrefix), blobKeyPrefix);
|
||||
SetField.NotNull(out this.botData, nameof(botData), botData);
|
||||
SetField.NotNull(out this.contextFactory, nameof(contextFactory), contextFactory);
|
||||
SetField.NotNull(out this.stackStoreFactory, nameof(stackStoreFactory), stackStoreFactory);
|
||||
SetField.NotNull(out this.queue, nameof(queue), queue);
|
||||
}
|
||||
|
||||
async Task IDialogTaskManager.LoadDialogTasks(CancellationToken token)
|
||||
{
|
||||
if (this.dialogTasks == null)
|
||||
{
|
||||
// load all dialog tasks. By default it loads/creates the default dialog task
|
||||
// which will be used by ReactiveDialogTask
|
||||
this.dialogTasks = new List<DialogTask>();
|
||||
do
|
||||
{
|
||||
IDialogTaskManager dialogTaskManager = this;
|
||||
dialogTaskManager.CreateDialogTask();
|
||||
} while (
|
||||
this.botData.PrivateConversationData.ContainsKey(this.GetCurrentTaskBlobKey(this.dialogTasks.Count)));
|
||||
}
|
||||
}
|
||||
|
||||
async Task IDialogTaskManager.FlushDialogTasks(CancellationToken token)
|
||||
{
|
||||
foreach (var dialogTask in this.dialogTasks)
|
||||
{
|
||||
dialogTask.Store.Flush();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
IReadOnlyList<IDialogTask> IDialogTasks.DialogTasks
|
||||
{
|
||||
get { return this.dialogTasks; }
|
||||
}
|
||||
|
||||
IDialogTask IDialogTasks.CreateDialogTask()
|
||||
{
|
||||
IDialogStack stack = default(IDialogStack);
|
||||
Func<CancellationToken, IDialogContext> makeContext = token => contextFactory(stack, token);
|
||||
var task = new DialogTask(makeContext, stackStoreFactory.StoreFrom(this.GetCurrentTaskBlobKey(this.dialogTasks.Count), botData.PrivateConversationData), this.queue);
|
||||
stack = task;
|
||||
dialogTasks.Add(task);
|
||||
return task;
|
||||
}
|
||||
|
||||
private string GetCurrentTaskBlobKey(int idx)
|
||||
{
|
||||
return idx == 0 ? this.blobKeyPrefix : this.blobKeyPrefix + idx;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.Bot.Schema;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Scorables;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Dialogs
|
||||
{
|
||||
/// <summary>
|
||||
/// A dialog specialized to dispatch an IScorable.
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">The result type.</typeparam>
|
||||
[Serializable]
|
||||
public class DispatchDialog<TResult> : Dispatcher, IDialog<TResult>
|
||||
{
|
||||
public virtual async Task StartAsync(IDialogContext context)
|
||||
{
|
||||
context.Wait(ActivityReceivedAsync);
|
||||
}
|
||||
|
||||
[NonSerialized]
|
||||
private IReadOnlyList<object> services;
|
||||
protected override IReadOnlyList<object> MakeServices()
|
||||
{
|
||||
return this.services;
|
||||
}
|
||||
|
||||
protected virtual async Task ActivityReceivedAsync(IDialogContext context, IAwaitable<IActivity> item)
|
||||
{
|
||||
var activity = await item;
|
||||
this.services = new object[] { this, context, activity };
|
||||
try
|
||||
{
|
||||
IDispatcher dispatcher = this;
|
||||
await dispatcher.TryPostAsync(context.CancellationToken);
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.services = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A dialog specialized to dispatch an IScorable.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This non-generic dialog is intended for use as a top-level dialog that will not
|
||||
/// return to any calling parent dialog (and therefore the result type is object).
|
||||
/// </remarks>
|
||||
[Serializable]
|
||||
public class DispatchDialog : DispatchDialog<object>
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Dialogs.Internals;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Scorables;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Scorables.Internals;
|
||||
using Microsoft.Bot.Schema;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Dialogs
|
||||
{
|
||||
/// <summary>
|
||||
/// This event represents the end of the conversation. It is initiated by <see cref="Extensions.EndConversation(IDialogContext, string)"/>
|
||||
/// and propagates as an event in the stack scorable process to allow interception.
|
||||
/// </summary>
|
||||
public sealed class EndConversationEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// Code selected from <see cref="EndOfConversationCodes"/>
|
||||
/// </summary>
|
||||
public string Code { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Construct the <see cref="EndConversationEvent"/>.
|
||||
/// </summary>
|
||||
/// <param name="code">Code selected from <see cref="EndOfConversationCodes"/>.</param>
|
||||
public EndConversationEvent(string code)
|
||||
{
|
||||
this.Code = code;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct the scorable used to handle the <see cref="EndConversationEvent"/>.
|
||||
/// </summary>
|
||||
/// <returns>The constructed scorable.</returns>
|
||||
public static IScorable<IResolver, double> MakeScorable()
|
||||
{
|
||||
var scorable =
|
||||
Actions.Bind(async (EndConversationEvent e, IDialogStack stack, IBotToUser botToUser, IBotData data, CancellationToken token) =>
|
||||
{
|
||||
stack.Reset();
|
||||
|
||||
data.ConversationData.Clear();
|
||||
data.PrivateConversationData.Clear();
|
||||
|
||||
var end = botToUser.MakeMessage();
|
||||
end.Type = ActivityTypes.EndOfConversation;
|
||||
end.AsEndOfConversationActivity().Code = e.Code;
|
||||
|
||||
await botToUser.PostAsync(end, token);
|
||||
})
|
||||
.Normalize();
|
||||
|
||||
return scorable;
|
||||
}
|
||||
}
|
||||
|
||||
public static partial class Extensions
|
||||
{
|
||||
private static readonly ResumeAfter<IEventActivity> AfterReset = (context, result) => Task.CompletedTask;
|
||||
|
||||
/// <summary>
|
||||
/// Initiate an <see cref="EndConversationEvent"/> to reset the conversation's state and stack and send an
|
||||
/// <see cref="ActivityTypes.EndOfConversation"/> to the Connector.
|
||||
/// </summary>
|
||||
public static void EndConversation(this IDialogContext context, string code)
|
||||
{
|
||||
var activity = new Activity(ActivityTypes.Event) { Value = new EndConversationEvent(code) };
|
||||
context.Post(activity, AfterReset);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Dialogs
|
||||
{
|
||||
/// <summary>
|
||||
/// The root of the exception hierarchy related to <see cref="Internals.IDialogStack"/> .
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public abstract class DialogStackException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the DialogStackException class with a specified error message.
|
||||
/// </summary>
|
||||
/// <param name="message">The error message that explains the reason for the exception.</param>
|
||||
/// <param name="inner">The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified.</param>
|
||||
public DialogStackException(string message, Exception inner)
|
||||
: base(message, inner)
|
||||
{
|
||||
}
|
||||
protected DialogStackException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The exception representing no resume handler specified for the dialog stack.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public sealed class NoResumeHandlerException : DialogStackException
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the NoResumeHandlerException class.
|
||||
/// </summary>
|
||||
/// <param name="inner">The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified.</param>
|
||||
public NoResumeHandlerException(Exception inner)
|
||||
: base("IDialog method execution finished with no resume handler specified through IDialogStack.", inner)
|
||||
{
|
||||
}
|
||||
private NoResumeHandlerException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The exception representing multiple resume handlers specified for the dialog stack.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public sealed class MultipleResumeHandlerException : DialogStackException
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the MultipleResumeHandlerException class.
|
||||
/// </summary>
|
||||
/// <param name="inner">The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified.</param>
|
||||
public MultipleResumeHandlerException(Exception inner)
|
||||
: base("IDialog method execution finished with multiple resume handlers specified through IDialogStack.", inner)
|
||||
{
|
||||
}
|
||||
private MultipleResumeHandlerException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The root of the exception hierarchy related to prompts.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public abstract class PromptException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the PromptException class with a specified error message.
|
||||
/// </summary>
|
||||
/// <param name="message">The error message that explains the reason for the exception.</param>
|
||||
public PromptException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
protected PromptException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The exception representing too many attempts by the user to answer the question asked by the prompt.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public sealed class TooManyAttemptsException : PromptException
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the TooManyAttemptsException class with a specified error message.
|
||||
/// </summary>
|
||||
/// <param name="message">The error message that explains the reason for the exception.</param>
|
||||
public TooManyAttemptsException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
private TooManyAttemptsException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Dialogs
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="IDialog{TResult}"/> is a suspendable conversational process that produces a result of type <typeparamref name="TResult"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Dialogs can call child dialogs or send messages to a user.
|
||||
/// Dialogs are suspended when waiting for a message from the user to the bot.
|
||||
/// Dialogs are resumed when the bot receives a message from the user.
|
||||
/// </remarks>
|
||||
/// <typeparam name="TResult">The result type.</typeparam>
|
||||
public interface IDialog<out TResult>
|
||||
{
|
||||
/// <summary>
|
||||
/// The start of the code that represents the conversational dialog.
|
||||
/// </summary>
|
||||
/// <param name="context">The dialog context.</param>
|
||||
/// <returns>A task that represents the dialog start.</returns>
|
||||
Task StartAsync(IDialogContext context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="IDialog"/> is a suspendable conversational process that produces an ignored result.
|
||||
/// </summary>
|
||||
public interface IDialog : IDialog<object>
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,216 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Dialogs.Internals;
|
||||
using Microsoft.Bot.Schema;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Dialogs
|
||||
{
|
||||
/// <summary>
|
||||
/// Encapsulates a method that represents the code to execute after a result is available.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The result is often a message from the user.
|
||||
/// </remarks>
|
||||
/// <typeparam name="T">The type of the result.</typeparam>
|
||||
/// <param name="context">The dialog context.</param>
|
||||
/// <param name="result">The result.</param>
|
||||
/// <returns>A task that represents the code that will resume after the result is available.</returns>
|
||||
public delegate Task ResumeAfter<in T>(IDialogContext context, IAwaitable<T> result);
|
||||
|
||||
/// <summary>
|
||||
/// Encapsulate a method that represents the code to start a dialog.
|
||||
/// </summary>
|
||||
/// <param name="context">The dialog context.</param>
|
||||
/// <returns>A task that represents the start code for a dialog.</returns>
|
||||
public delegate Task StartAsync(IDialogContext context);
|
||||
|
||||
/// <summary>
|
||||
/// The context for the bot.
|
||||
/// </summary>
|
||||
public interface IBotContext : IBotData, IBotToUser
|
||||
{
|
||||
CancellationToken CancellationToken { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The activity posted to bot.
|
||||
/// </summary>
|
||||
/// <remarks> This is the incoming activity in reactive cases.
|
||||
/// for proactive case, i.e. Conversation.ResumeAsync code path,
|
||||
/// it will be the <see cref="IMessageActivity"/> returned by <see cref="ConversationReference.GetPostToBotMessage"/>.
|
||||
/// </remarks>
|
||||
IActivity Activity { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The context for the execution of a dialog's conversational process.
|
||||
/// </summary>
|
||||
public interface IDialogContext : IDialogStack, IBotContext
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Optional message properties that can be sent <see cref="Extensions.SayAsync(IBotToUser, string, string, MessageOptions, string, CancellationToken)"/>
|
||||
/// </summary>
|
||||
public class MessageOptions
|
||||
{
|
||||
public MessageOptions()
|
||||
{
|
||||
this.TextFormat = TextFormatTypes.Markdown;
|
||||
this.AttachmentLayout = AttachmentLayoutTypes.List;
|
||||
this.Attachments = new List<Attachment>();
|
||||
this.Entities = new List<Entity>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the bot is accepting, expecting, or ignoring input
|
||||
/// </summary>
|
||||
public string InputHint { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Format of text fields [plain|markdown] Default:markdown
|
||||
/// </summary>
|
||||
public string TextFormat { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Hint for how to deal with multiple attachments: [list|carousel] Default:list
|
||||
/// </summary>
|
||||
public string AttachmentLayout { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Attachments
|
||||
/// </summary>
|
||||
public IList<Attachment> Attachments { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Collection of Entity objects, each of which contains metadata about this activity. Each Entity object is typed.
|
||||
/// </summary>
|
||||
public IList<Entity> Entities { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper methods.
|
||||
/// </summary>
|
||||
public static partial class Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Post a message to be sent to the user, using previous messages to establish a conversation context.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If the locale parameter is not set, locale of the incoming message will be used for reply.
|
||||
/// </remarks>
|
||||
/// <param name="botToUser">Communication channel to use.</param>
|
||||
/// <param name="text">The message text.</param>
|
||||
/// <param name="locale">The locale of the text.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A task that represents the post operation.</returns>
|
||||
public static async Task PostAsync(this IBotToUser botToUser, string text, string locale = null, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
var message = botToUser.MakeMessage();
|
||||
message.Text = text;
|
||||
|
||||
if (!string.IsNullOrEmpty(locale))
|
||||
{
|
||||
message.Locale = locale;
|
||||
}
|
||||
|
||||
await botToUser.PostAsync(message, cancellationToken);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Post a message and optional SSML to be sent to the user, using previous messages to establish a conversation context.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If the locale parameter is not set, locale of the incoming message will be used for reply.
|
||||
/// </remarks>
|
||||
/// <param name="botToUser">Communication channel to use.</param>
|
||||
/// <param name="text">The message text.</param>
|
||||
/// <param name="speak">The SSML markup for text to speech.</param>
|
||||
/// <param name="options">The options for the message.</param>
|
||||
/// <param name="locale">The locale of the text.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A task that represents the post operation.</returns>
|
||||
public static async Task SayAsync(this IBotToUser botToUser, string text, string speak = null, MessageOptions options = null, string locale = null, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
var message = botToUser.MakeMessage();
|
||||
|
||||
message.Text = text;
|
||||
message.Speak = speak;
|
||||
|
||||
if (!string.IsNullOrEmpty(locale))
|
||||
{
|
||||
message.Locale = locale;
|
||||
}
|
||||
|
||||
if (options != null)
|
||||
{
|
||||
message.InputHint = options.InputHint;
|
||||
message.TextFormat = options.TextFormat;
|
||||
message.AttachmentLayout = options.AttachmentLayout;
|
||||
message.Attachments = options.Attachments;
|
||||
message.Entities = options.Entities;
|
||||
}
|
||||
|
||||
await botToUser.PostAsync(message, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Suspend the current dialog until the user has sent a message to the bot.
|
||||
/// </summary>
|
||||
/// <param name="stack">The dialog stack.</param>
|
||||
/// <param name="resume">The method to resume when the message has been received.</param>
|
||||
public static void Wait(this IDialogStack stack, ResumeAfter<IMessageActivity> resume)
|
||||
{
|
||||
stack.Wait<IMessageActivity>(resume);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call a child dialog, add it to the top of the stack and post the message to the child dialog.
|
||||
/// </summary>
|
||||
/// <typeparam name="R">The type of result expected from the child dialog.</typeparam>
|
||||
/// <param name="stack">The dialog stack.</param>
|
||||
/// <param name="child">The child dialog.</param>
|
||||
/// <param name="resume">The method to resume when the child dialog has completed.</param>
|
||||
/// <param name="message">The message that will be posted to child dialog.</param>
|
||||
/// <param name="token">A cancellation token.</param>
|
||||
/// <returns>A task representing the Forward operation.</returns>
|
||||
public static async Task Forward<R>(this IDialogStack stack, IDialog<R> child, ResumeAfter<R> resume, IMessageActivity message, CancellationToken token = default(CancellationToken))
|
||||
{
|
||||
await stack.Forward<R, IMessageActivity>(child, resume, message, token);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using Microsoft.Bot.Builder.V3Bridge.Base;
|
||||
using Microsoft.Bot.Schema;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Dialogs.Internals
|
||||
{
|
||||
/// <summary>
|
||||
/// The stack of dialogs in the conversational process.
|
||||
/// </summary>
|
||||
public interface IDialogStack
|
||||
{
|
||||
/// <summary>
|
||||
/// The dialog frames active on the stack.
|
||||
/// </summary>
|
||||
IReadOnlyList<Delegate> Frames { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Suspend the current dialog until an external event has been sent to the bot.
|
||||
/// </summary>
|
||||
/// <param name="resume">The method to resume when the event has been received.</param>
|
||||
void Wait<R>(ResumeAfter<R> resume);
|
||||
|
||||
/// <summary>
|
||||
/// Call a child dialog and add it to the top of the stack.
|
||||
/// </summary>
|
||||
/// <typeparam name="R">The type of result expected from the child dialog.</typeparam>
|
||||
/// <param name="child">The child dialog.</param>
|
||||
/// <param name="resume">The method to resume when the child dialog has completed.</param>
|
||||
void Call<R>(IDialog<R> child, ResumeAfter<R> resume);
|
||||
|
||||
/// <summary>
|
||||
/// Post an internal event to the queue.
|
||||
/// </summary>
|
||||
/// <param name="event">The event to post to the queue.</param>
|
||||
/// <param name="resume">The method to resume when the event has been delivered.</param>
|
||||
void Post<E>(E @event, ResumeAfter<E> resume);
|
||||
|
||||
/// <summary>
|
||||
/// Call a child dialog, add it to the top of the stack and post the item to the child dialog.
|
||||
/// </summary>
|
||||
/// <typeparam name="R">The type of result expected from the child dialog.</typeparam>
|
||||
/// <typeparam name="T">The type of the item posted to child dialog.</typeparam>
|
||||
/// <param name="child">The child dialog.</param>
|
||||
/// <param name="resume">The method to resume when the child dialog has completed.</param>
|
||||
/// <param name="item">The item that will be posted to child dialog.</param>
|
||||
/// <param name="token">A cancellation token.</param>
|
||||
/// <returns>A task representing the Forward operation.</returns>
|
||||
Task Forward<R, T>(IDialog<R> child, ResumeAfter<R> resume, T item, CancellationToken token);
|
||||
|
||||
/// <summary>
|
||||
/// Complete the current dialog and return a result to the parent dialog.
|
||||
/// </summary>
|
||||
/// <typeparam name="R">The type of the result dialog.</typeparam>
|
||||
/// <param name="value">The value of the result.</param>
|
||||
void Done<R>(R value);
|
||||
|
||||
/// <summary>
|
||||
/// Fail the current dialog and return an exception to the parent dialog.
|
||||
/// </summary>
|
||||
/// <param name="error">The error.</param>
|
||||
void Fail(Exception error);
|
||||
|
||||
/// <summary>
|
||||
/// Resets the stack.
|
||||
/// </summary>
|
||||
void Reset();
|
||||
}
|
||||
|
||||
public interface IDialogTask : IDialogStack, IEventLoop, IEventProducer<IActivity>
|
||||
{
|
||||
}
|
||||
|
||||
public static partial class Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Interrupt the waiting dialog with a new dialog
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of result expected from the dialog.</typeparam>
|
||||
/// <typeparam name="R">The type of the item posted to dialog.</typeparam>
|
||||
/// <param name="task">The dialog task.</param>
|
||||
/// <param name="dialog">The new interrupting dialog.</param>
|
||||
/// <param name="item">The item to forward to the new interrupting dialog.</param>
|
||||
/// <param name="token">The cancellation token.</param>
|
||||
/// <returns>A task that represents the interruption operation.</returns>
|
||||
public static async Task InterruptAsync<T, R>(this IDialogTask task, IDialog<T> dialog, R item, CancellationToken token)
|
||||
{
|
||||
await task.Forward(dialog.Void<T, R>(), null, item, token);
|
||||
await task.PollAsync(token);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,422 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Dialogs.Internals;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Internals.Fibers;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Luis;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Luis.Models;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Scorables.Internals;
|
||||
using Microsoft.Bot.Schema;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Dialogs
|
||||
{
|
||||
/// <summary>
|
||||
/// Associate a LUIS intent with a dialog method.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
|
||||
[Serializable]
|
||||
public class LuisIntentAttribute : AttributeString
|
||||
{
|
||||
/// <summary>
|
||||
/// The LUIS intent name.
|
||||
/// </summary>
|
||||
public readonly string IntentName;
|
||||
|
||||
/// <summary>
|
||||
/// Construct the association between the LUIS intent and a dialog method.
|
||||
/// </summary>
|
||||
/// <param name="intentName">The LUIS intent name.</param>
|
||||
public LuisIntentAttribute(string intentName)
|
||||
{
|
||||
SetField.NotNull(out this.IntentName, nameof(intentName), intentName);
|
||||
}
|
||||
|
||||
protected override string Text
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.IntentName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The handler for a LUIS intent.
|
||||
/// </summary>
|
||||
/// <param name="context">The dialog context.</param>
|
||||
/// <param name="luisResult">The LUIS result.</param>
|
||||
/// <returns>A task representing the completion of the intent processing.</returns>
|
||||
public delegate Task IntentHandler(IDialogContext context, LuisResult luisResult);
|
||||
|
||||
/// <summary>
|
||||
/// The handler for a LUIS intent.
|
||||
/// </summary>
|
||||
/// <param name="context">The dialog context.</param>
|
||||
/// <param name="message">The dialog message.</param>
|
||||
/// <param name="luisResult">The LUIS result.</param>
|
||||
/// <returns>A task representing the completion of the intent processing.</returns>
|
||||
public delegate Task IntentActivityHandler(IDialogContext context, IAwaitable<IMessageActivity> message, LuisResult luisResult);
|
||||
|
||||
/// <summary>
|
||||
/// An exception for invalid intent handlers.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public sealed class InvalidIntentHandlerException : InvalidOperationException
|
||||
{
|
||||
public readonly MethodInfo Method;
|
||||
|
||||
public InvalidIntentHandlerException(string message, MethodInfo method)
|
||||
: base(message)
|
||||
{
|
||||
SetField.NotNull(out this.Method, nameof(method), method);
|
||||
}
|
||||
|
||||
private InvalidIntentHandlerException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Matches a LuisResult object with the best scored IntentRecommendation of the LuisResult
|
||||
/// and corresponding Luis service.
|
||||
/// </summary>
|
||||
public class LuisServiceResult
|
||||
{
|
||||
public LuisServiceResult(LuisResult result, IntentRecommendation intent, ILuisService service)
|
||||
{
|
||||
this.Result = result;
|
||||
this.BestIntent = intent;
|
||||
this.LuisService = service;
|
||||
}
|
||||
|
||||
public LuisResult Result { get; }
|
||||
|
||||
public IntentRecommendation BestIntent { get; }
|
||||
|
||||
public ILuisService LuisService { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A dialog specialized to handle intents and entities from LUIS.
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">The result type.</typeparam>
|
||||
[Serializable]
|
||||
public class LuisDialog<TResult> : IDialog<TResult>
|
||||
{
|
||||
protected readonly IReadOnlyList<ILuisService> services;
|
||||
|
||||
/// <summary> Mapping from intent string to the appropriate handler. </summary>
|
||||
[NonSerialized]
|
||||
protected Dictionary<string, IntentActivityHandler> handlerByIntent;
|
||||
|
||||
public ILuisService[] MakeServicesFromAttributes()
|
||||
{
|
||||
var type = this.GetType();
|
||||
var luisModels = type.GetCustomAttributes<LuisModelAttribute>(inherit: true);
|
||||
return luisModels.Select(m => new LuisService(m)).Cast<ILuisService>().ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct the LUIS dialog.
|
||||
/// </summary>
|
||||
/// <param name="services">The LUIS service.</param>
|
||||
public LuisDialog(params ILuisService[] services)
|
||||
{
|
||||
if (services.Length == 0)
|
||||
{
|
||||
services = MakeServicesFromAttributes();
|
||||
}
|
||||
|
||||
SetField.NotNull(out this.services, nameof(services), services);
|
||||
}
|
||||
|
||||
public virtual async Task StartAsync(IDialogContext context)
|
||||
{
|
||||
context.Wait(MessageReceived);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the best scored <see cref="IntentRecommendation" /> from a <see cref="LuisResult" />.
|
||||
/// </summary>
|
||||
/// <param name="result">A result of a LUIS service call.</param>
|
||||
/// <returns>The best scored <see cref="IntentRecommendation" />, or null if <paramref name="result" /> doesn't contain any intents.</returns>
|
||||
protected virtual IntentRecommendation BestIntentFrom(LuisResult result)
|
||||
{
|
||||
return result.TopScoringIntent ?? result.Intents?.MaxBy(i => i.Score ?? 0d);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the best scored <see cref="LuisServiceResult" /> across multiple <see cref="LuisServiceResult" /> returned by
|
||||
/// different <see cref="ILuisService"/>.
|
||||
/// </summary>
|
||||
/// <param name="results">Results of multiple LUIS services calls.</param>
|
||||
/// <returns>A <see cref="LuisServiceResult" /> with the best scored <see cref="IntentRecommendation" /> and related <see cref="LuisResult" />,
|
||||
/// or null if no one of <paramref name="results" /> contains any intents.</returns>
|
||||
protected virtual LuisServiceResult BestResultFrom(IEnumerable<LuisServiceResult> results)
|
||||
{
|
||||
return results.MaxBy(i => i.BestIntent.Score ?? 0d);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modify LUIS request before it is sent.
|
||||
/// </summary>
|
||||
/// <param name="request">Request so far.</param>
|
||||
/// <returns>Modified request.</returns>
|
||||
protected virtual LuisRequest ModifyLuisRequest(LuisRequest request)
|
||||
{
|
||||
return request;
|
||||
}
|
||||
|
||||
protected virtual async Task MessageReceived(IDialogContext context, IAwaitable<IMessageActivity> item)
|
||||
{
|
||||
var message = await item;
|
||||
var messageText = await GetLuisQueryTextAsync(context, message);
|
||||
|
||||
if (messageText != null)
|
||||
{
|
||||
// Modify request by the service to add attributes and then by the dialog to reflect the particular query
|
||||
var tasks = this.services.Select(s => s.QueryAsync(ModifyLuisRequest(s.ModifyRequest(new LuisRequest(messageText))), context.CancellationToken)).ToArray();
|
||||
var results = await Task.WhenAll(tasks);
|
||||
|
||||
var winners = from result in results.Select((value, index) => new { value, index })
|
||||
let resultWinner = BestIntentFrom(result.value)
|
||||
where resultWinner != null
|
||||
select new LuisServiceResult(result.value, resultWinner, this.services[result.index]);
|
||||
|
||||
var winner = this.BestResultFrom(winners);
|
||||
|
||||
if (winner == null)
|
||||
{
|
||||
throw new InvalidOperationException("No winning intent selected from Luis results.");
|
||||
}
|
||||
|
||||
if (winner.Result.Dialog?.Status == DialogResponse.DialogStatus.Question)
|
||||
{
|
||||
#pragma warning disable CS0618
|
||||
var childDialog = await MakeLuisActionDialog(winner.LuisService,
|
||||
winner.Result.Dialog.ContextId,
|
||||
winner.Result.Dialog.Prompt);
|
||||
#pragma warning restore CS0618
|
||||
context.Call(childDialog, LuisActionDialogFinished);
|
||||
}
|
||||
else
|
||||
{
|
||||
await DispatchToIntentHandler(context, item, winner.BestIntent, winner.Result);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var intent = new IntentRecommendation() { Intent = string.Empty, Score = 1.0 };
|
||||
var result = new LuisResult() { TopScoringIntent = intent };
|
||||
await DispatchToIntentHandler(context, item, intent, result);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual async Task DispatchToIntentHandler(IDialogContext context,
|
||||
IAwaitable<IMessageActivity> item,
|
||||
IntentRecommendation bestIntent,
|
||||
LuisResult result)
|
||||
{
|
||||
if (this.handlerByIntent == null)
|
||||
{
|
||||
this.handlerByIntent = new Dictionary<string, IntentActivityHandler>(GetHandlersByIntent());
|
||||
}
|
||||
|
||||
IntentActivityHandler handler = null;
|
||||
if (result == null || !this.handlerByIntent.TryGetValue(bestIntent.Intent, out handler))
|
||||
{
|
||||
handler = this.handlerByIntent[string.Empty];
|
||||
}
|
||||
|
||||
if (handler != null)
|
||||
{
|
||||
await handler(context, item, result);
|
||||
}
|
||||
else
|
||||
{
|
||||
var text = $"No default intent handler found.";
|
||||
throw new Exception(text);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual Task<string> GetLuisQueryTextAsync(IDialogContext context, IMessageActivity message)
|
||||
{
|
||||
return Task.FromResult(message.Text);
|
||||
}
|
||||
|
||||
protected virtual IDictionary<string, IntentActivityHandler> GetHandlersByIntent()
|
||||
{
|
||||
return LuisDialog.EnumerateHandlers(this).ToDictionary(kv => kv.Key, kv => kv.Value);
|
||||
}
|
||||
|
||||
[Obsolete("Action binding in LUIS should be replaced with code.")]
|
||||
protected virtual async Task<IDialog<LuisResult>> MakeLuisActionDialog(ILuisService luisService, string contextId, string prompt)
|
||||
{
|
||||
#pragma warning disable CS0618
|
||||
return new LuisActionDialog(luisService, contextId, prompt);
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
|
||||
protected virtual async Task LuisActionDialogFinished(IDialogContext context, IAwaitable<LuisResult> item)
|
||||
{
|
||||
var result = await item;
|
||||
var messageActivity = (IMessageActivity)context.Activity;
|
||||
await DispatchToIntentHandler(context, Awaitable.FromItem(messageActivity), BestIntentFrom(result), result);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The dialog wrapping Luis dialog feature.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[Obsolete("Action binding in LUIS should be replaced with code.")]
|
||||
public class LuisActionDialog : IDialog<LuisResult>
|
||||
{
|
||||
private readonly ILuisService luisService;
|
||||
private string contextId;
|
||||
private string prompt;
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of LuisActionDialog.
|
||||
/// </summary>
|
||||
/// <param name="luisService"> The Luis service.</param>
|
||||
/// <param name="contextId"> The contextId for Luis dialog returned in Luis result.</param>
|
||||
/// <param name="prompt"> The prompt that should be asked from user.</param>
|
||||
public LuisActionDialog(ILuisService luisService, string contextId, string prompt)
|
||||
{
|
||||
SetField.NotNull(out this.luisService, nameof(luisService), luisService);
|
||||
SetField.NotNull(out this.contextId, nameof(contextId), contextId);
|
||||
this.prompt = prompt;
|
||||
}
|
||||
|
||||
|
||||
public virtual async Task StartAsync(IDialogContext context)
|
||||
{
|
||||
await context.PostAsync(this.prompt);
|
||||
context.Wait(MessageReceivedAsync);
|
||||
}
|
||||
|
||||
protected virtual async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> item)
|
||||
{
|
||||
var message = await item;
|
||||
var luisRequest = new LuisRequest(query: message.Text) { ContextId = this.contextId };
|
||||
var result = await luisService.QueryAsync(luisService.BuildUri(luisService.ModifyRequest(luisRequest)), context.CancellationToken);
|
||||
if (result.Dialog.Status != DialogResponse.DialogStatus.Finished)
|
||||
{
|
||||
this.contextId = result.Dialog.ContextId;
|
||||
this.prompt = result.Dialog.Prompt;
|
||||
await context.PostAsync(this.prompt);
|
||||
context.Wait(MessageReceivedAsync);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Done(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class LuisDialog
|
||||
{
|
||||
/// <summary>
|
||||
/// Enumerate the handlers based on the attributes on the dialog instance.
|
||||
/// </summary>
|
||||
/// <param name="dialog">The dialog.</param>
|
||||
/// <returns>An enumeration of handlers.</returns>
|
||||
public static IEnumerable<KeyValuePair<string, IntentActivityHandler>> EnumerateHandlers(object dialog)
|
||||
{
|
||||
var type = dialog.GetType();
|
||||
var methods = type.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
||||
foreach (var method in methods)
|
||||
{
|
||||
var intents = method.GetCustomAttributes<LuisIntentAttribute>(inherit: true).ToArray();
|
||||
IntentActivityHandler intentHandler = null;
|
||||
|
||||
try
|
||||
{
|
||||
intentHandler = (IntentActivityHandler)Delegate.CreateDelegate(typeof(IntentActivityHandler), dialog, method, throwOnBindFailure: false);
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
// "Cannot bind to the target method because its signature or security transparency is not compatible with that of the delegate type."
|
||||
// https://github.com/Microsoft/BotBuilder/issues/634
|
||||
// https://github.com/Microsoft/BotBuilder/issues/435
|
||||
}
|
||||
|
||||
// fall back for compatibility
|
||||
if (intentHandler == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var handler = (IntentHandler)Delegate.CreateDelegate(typeof(IntentHandler), dialog, method, throwOnBindFailure: false);
|
||||
|
||||
if (handler != null)
|
||||
{
|
||||
// thunk from new to old delegate type
|
||||
intentHandler = (context, message, result) => handler(context, result);
|
||||
}
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
// "Cannot bind to the target method because its signature or security transparency is not compatible with that of the delegate type."
|
||||
// https://github.com/Microsoft/BotBuilder/issues/634
|
||||
// https://github.com/Microsoft/BotBuilder/issues/435
|
||||
}
|
||||
}
|
||||
|
||||
if (intentHandler != null)
|
||||
{
|
||||
var intentNames = intents.Select(i => i.IntentName).DefaultIfEmpty(method.Name);
|
||||
|
||||
foreach (var intentName in intentNames)
|
||||
{
|
||||
yield return new KeyValuePair<string, IntentActivityHandler>(intentName?.Trim() ?? string.Empty, intentHandler);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (intents.Length > 0)
|
||||
{
|
||||
throw new InvalidIntentHandlerException(string.Join(";", intents.Select(i => i.IntentName)), method);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,532 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.Bot.Schema;
|
||||
using System.Resources;
|
||||
using System.Globalization;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Dialogs.Internals;
|
||||
using System.Collections.Concurrent;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Resource;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Dialogs
|
||||
{
|
||||
public class RecognizeEntity<T>
|
||||
{
|
||||
public T Entity { get; set; }
|
||||
public double Score { get; set; }
|
||||
}
|
||||
public interface IPromptRecognizeNumbersOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// (Optional) Minimum value allowed.
|
||||
/// </summary>
|
||||
double? MinValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// (Optional) Maximum value allowed.
|
||||
/// </summary>
|
||||
double? MaxValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// (Optional) If true, then only integers will be recognized.
|
||||
/// </summary>
|
||||
bool? IntegerOnly { get; set; }
|
||||
}
|
||||
|
||||
public class PromptRecognizeNumbersOptions : IPromptRecognizeNumbersOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// (Optional) Minimum value allowed.
|
||||
/// </summary>
|
||||
public double? MinValue { get; set; }
|
||||
/// <summary>
|
||||
/// (Optional) Maximum value allowed.
|
||||
/// </summary>
|
||||
public double? MaxValue { get; set; }
|
||||
/// <summary>
|
||||
/// (Optional) If true, then only integers will be recognized.
|
||||
/// </summary>
|
||||
public bool? IntegerOnly { get; set; }
|
||||
}
|
||||
|
||||
public interface IPromptRecognizeValuesOptions
|
||||
{
|
||||
bool? AllowPartialMatches { get; set; }
|
||||
int? MaxTokenDistance { get; set; }
|
||||
}
|
||||
|
||||
public interface IPromptRecognizeChoicesOptions : IPromptRecognizeValuesOptions
|
||||
{
|
||||
bool? ExcludeValue { get; set; }
|
||||
bool? ExcludeAction { get; set; }
|
||||
}
|
||||
|
||||
public class PromptRecognizeChoicesOptions : IPromptRecognizeChoicesOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// (Optional) If true, the choices value will NOT be recognized over.
|
||||
/// </summary>
|
||||
public bool? ExcludeValue { get; set; }
|
||||
/// <summary>
|
||||
/// (Optional) If true, the choices action will NOT be recognized over.
|
||||
/// </summary>
|
||||
public bool? ExcludeAction { get; set; }
|
||||
/// <summary>
|
||||
/// (Optional) if true, then only some of the tokens in a value need to exist to be considered a match.The default value is "false".
|
||||
/// </summary>
|
||||
public bool? AllowPartialMatches { get; set; }
|
||||
/// <summary>
|
||||
/// (Optional) maximum tokens allowed between two matched tokens in the utterance.So with
|
||||
/// a max distance of 2 the value "second last" would match the utternace "second from the last"
|
||||
/// but it wouldn't match "Wait a second. That's not the last one is it?".
|
||||
/// The default value is "2".
|
||||
/// </summary>
|
||||
public int? MaxTokenDistance { get; set; }
|
||||
}
|
||||
|
||||
public class ChronoDuration
|
||||
{
|
||||
public string Entity { get; internal set; }
|
||||
public ChronoDurationResolution Resolution { get; set; }
|
||||
|
||||
public ChronoDuration()
|
||||
{
|
||||
this.Resolution = new ChronoDurationResolution();
|
||||
}
|
||||
}
|
||||
|
||||
public class ChronoDurationResolution
|
||||
{
|
||||
public string ResolutionType { get; set; }
|
||||
public DateTime? Start { get; set; }
|
||||
public DateTime? End { get; set; }
|
||||
}
|
||||
|
||||
public interface IPromptRecognizer
|
||||
{
|
||||
/// <summary>Recognizer using a RegEx to match expressions.</summary>
|
||||
/// <param name="message">Message context.</param>
|
||||
/// <param name="expressionKey">Name of the resource with the RegEx.</param>
|
||||
/// <param name="resourceManager">Resources with the localized expression.</param>
|
||||
IEnumerable<RecognizeEntity<string>> RecognizeLocalizedRegExp(IMessageActivity message, string expressionKey, ResourceManager resourceManager);
|
||||
|
||||
/// <summary>Recognizer for a number.</summary>
|
||||
/// <param name="message">Message context.</param>
|
||||
/// <param name="choicesDictionary">Dictionary with the options to choose from as a key and their synonyms as a value.</param>
|
||||
/// <param name="options">Options of the Recognizer. <see cref="IPromptRecognizeChoicesOptions" /></param>
|
||||
IEnumerable<RecognizeEntity<T>> RecognizeChoices<T>(IMessageActivity message, IReadOnlyDictionary<T, IReadOnlyList<T>> choicesDictionary, IPromptRecognizeChoicesOptions options = null);
|
||||
|
||||
/// <summary>Recognizer for a number.</summary>
|
||||
/// <param name="message">Message context.</param>
|
||||
/// <param name="choicesKey">Name of the resource with the choices.</param>
|
||||
/// <param name="resourceManager">Resources with the localized choices.</param>
|
||||
/// <param name="options">Options of the Recognizer. <see cref="IPromptRecognizeChoicesOptions" /></param>
|
||||
IEnumerable<RecognizeEntity<string>> RecognizeLocalizedChoices(IMessageActivity message, string choicesKey, ResourceManager resourceManager, IPromptRecognizeChoicesOptions options = null);
|
||||
|
||||
/// <summary>Recognizer for a number.</summary>
|
||||
/// <param name="message">Message context.</param>
|
||||
/// <param name="options">Options of the Recognizer. <see cref="IPromptRecognizeNumbersOptions" /></param>
|
||||
IEnumerable<RecognizeEntity<double>> RecognizeNumbers(IMessageActivity message, IPromptRecognizeNumbersOptions options = null);
|
||||
|
||||
/// <summary>Recognizer for a ordinal number.</summary>
|
||||
/// <param name="message">Message context.</param>
|
||||
IEnumerable<RecognizeEntity<long>> RecognizeOrdinals(IMessageActivity message);
|
||||
|
||||
/// <summary>Recognizer for a time or duration.</summary>
|
||||
/// <param name="message">Message context.</param>
|
||||
IEnumerable<RecognizeEntity<string>> RecognizeTimes(IMessageActivity message);
|
||||
|
||||
/// <summary>Recognizer for true/false expression.</summary>
|
||||
/// <param name="message">Message context.</param>
|
||||
IEnumerable<RecognizeEntity<bool>> RecognizeBooleans(IMessageActivity message);
|
||||
}
|
||||
|
||||
internal class ChoicesDictionary : Dictionary<string, IReadOnlyList<string>> { }
|
||||
|
||||
internal class LocalizedDictionary<T> : ConcurrentDictionary<string, T> { }
|
||||
|
||||
internal class ResourcesCache<T> : ConcurrentDictionary<string, LocalizedDictionary<T>> { }
|
||||
|
||||
[Serializable]
|
||||
public class PromptRecognizer : IPromptRecognizer
|
||||
{
|
||||
private const string ResourceKeyCardinals = "NumberTerms";
|
||||
private const string ResourceKeyOrdinals = "NumberOrdinals";
|
||||
private const string ResourceKeyReverserOrdinals = "NumberReverseOrdinals";
|
||||
private const string ResourceKeyNumberRegex = "NumberExpression";
|
||||
private const string ResourceKeyBooleans = "BooleanChoices";
|
||||
|
||||
private static Regex simpleTokenizer = new Regex(@"\w+", RegexOptions.IgnoreCase);
|
||||
private static ResourcesCache<Regex> expCache = new ResourcesCache<Regex>();
|
||||
private static ResourcesCache<ChoicesDictionary> choicesCache = new ResourcesCache<ChoicesDictionary>();
|
||||
|
||||
public PromptRecognizer()
|
||||
{
|
||||
}
|
||||
|
||||
public IEnumerable<RecognizeEntity<string>> RecognizeLocalizedRegExp(IMessageActivity message, string expressionKey, ResourceManager resourceManager)
|
||||
{
|
||||
var entities = new List<RecognizeEntity<string>>();
|
||||
var locale = message?.Locale ?? string.Empty;
|
||||
var utterance = message?.Text?.Trim().ToLowerInvariant() ?? string.Empty;
|
||||
|
||||
LocalizedDictionary<Regex> cachedLocalizedRegex;
|
||||
if (!expCache.TryGetValue(expressionKey, out cachedLocalizedRegex))
|
||||
{
|
||||
var localizedRegex = new LocalizedDictionary<Regex>();
|
||||
cachedLocalizedRegex = expCache.GetOrAdd(expressionKey, localizedRegex);
|
||||
}
|
||||
|
||||
Regex cachedRegex;
|
||||
if (!cachedLocalizedRegex.TryGetValue(locale, out cachedRegex))
|
||||
{
|
||||
var expression = GetLocalizedResource(expressionKey, locale, resourceManager);
|
||||
var regex = new Regex(expression, RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
cachedRegex = cachedLocalizedRegex.GetOrAdd(locale, regex);
|
||||
}
|
||||
|
||||
foreach (Match match in cachedRegex.Matches(utterance))
|
||||
{
|
||||
if (match.Success)
|
||||
{
|
||||
entities.Add(new RecognizeEntity<string>
|
||||
{
|
||||
Entity = match.Value,
|
||||
Score = CalculateScore(utterance, match.Value)
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
return entities;
|
||||
}
|
||||
|
||||
public IEnumerable<RecognizeEntity<string>> RecognizeLocalizedChoices(IMessageActivity message, string choicesKey, ResourceManager resourceManager, IPromptRecognizeChoicesOptions options = null)
|
||||
{
|
||||
var locale = message?.Locale ?? string.Empty;
|
||||
|
||||
LocalizedDictionary<ChoicesDictionary> cachedLocalizedChoices;
|
||||
if (!choicesCache.TryGetValue(choicesKey, out cachedLocalizedChoices))
|
||||
{
|
||||
var localizedChoices = new LocalizedDictionary<ChoicesDictionary>();
|
||||
cachedLocalizedChoices = choicesCache.GetOrAdd(choicesKey, localizedChoices);
|
||||
}
|
||||
|
||||
ChoicesDictionary cachedChoices;
|
||||
if (!cachedLocalizedChoices.TryGetValue(locale, out cachedChoices))
|
||||
{
|
||||
var choicesArray = GetLocalizedResource(choicesKey, locale, resourceManager).Split('|');
|
||||
var choices = ConvertToChoices(choicesArray);
|
||||
cachedChoices = cachedLocalizedChoices.GetOrAdd(locale, choices);
|
||||
}
|
||||
|
||||
return RecognizeChoices(message, cachedChoices, options);
|
||||
}
|
||||
|
||||
public IEnumerable<RecognizeEntity<double>> RecognizeNumbers(IMessageActivity message, IPromptRecognizeNumbersOptions options = null)
|
||||
{
|
||||
var entities = new List<RecognizeEntity<double>>();
|
||||
|
||||
Func<RecognizeEntity<double>, bool> minValueWhere = (x => ((options == null || !options.MinValue.HasValue) || x.Entity >= options.MinValue));
|
||||
Func<RecognizeEntity<double>, bool> maxValueWhere = (x => ((options == null || !options.MaxValue.HasValue) || x.Entity <= options.MaxValue));
|
||||
Func<RecognizeEntity<double>, bool> integerOnlyWhere = (x => ((options != null && options.IntegerOnly.HasValue) ? !options.IntegerOnly.Value : true) || Math.Floor(x.Entity) == x.Entity);
|
||||
Func<RecognizeEntity<string>, RecognizeEntity<double>> selector = (x => new RecognizeEntity<double> { Entity = double.Parse(x.Entity), Score = x.Score });
|
||||
|
||||
var matches = RecognizeLocalizedRegExp(message, ResourceKeyNumberRegex, Resources.ResourceManager);
|
||||
if (matches != null && matches.Any())
|
||||
{
|
||||
entities.AddRange(matches.Select(selector)
|
||||
.Where(minValueWhere)
|
||||
.Where(maxValueWhere)
|
||||
.Where(integerOnlyWhere));
|
||||
}
|
||||
|
||||
var resource = GetLocalizedResource(ResourceKeyCardinals, message?.Locale, Resources.ResourceManager);
|
||||
|
||||
var choices = ConvertToChoices(resource.Split('|'));
|
||||
|
||||
// Recognize any term based numbers
|
||||
var results = RecognizeChoices(message, choices, new PromptRecognizeChoicesOptions { ExcludeValue = true });
|
||||
if (results != null && results.Any())
|
||||
{
|
||||
entities.AddRange(results.Select(selector)
|
||||
.Where(minValueWhere)
|
||||
.Where(maxValueWhere)
|
||||
.Where(integerOnlyWhere));
|
||||
}
|
||||
|
||||
return entities;
|
||||
}
|
||||
|
||||
public IEnumerable<RecognizeEntity<long>> RecognizeOrdinals(IMessageActivity message)
|
||||
{
|
||||
var entities = new List<RecognizeEntity<long>>();
|
||||
|
||||
var resourceOrdinales = GetLocalizedResource(ResourceKeyOrdinals, message?.Locale, Resources.ResourceManager);
|
||||
var resourceReverseOrdinals = GetLocalizedResource(ResourceKeyReverserOrdinals, message?.Locale, Resources.ResourceManager);
|
||||
|
||||
var ordinals = resourceOrdinales.Split('|');
|
||||
var reverseOrdinals = resourceReverseOrdinals.Split('|');
|
||||
|
||||
var values = ordinals.Concat(reverseOrdinals);
|
||||
|
||||
var choices = ConvertToChoices(values);
|
||||
|
||||
// Recognize any term based numbers
|
||||
var results = RecognizeChoices(message, choices, new PromptRecognizeChoicesOptions { ExcludeValue = true });
|
||||
if (results != null && results.Any())
|
||||
{
|
||||
entities.AddRange(results.Select(x => new RecognizeEntity<long> { Entity = long.Parse(x.Entity), Score = x.Score }));
|
||||
}
|
||||
|
||||
return entities;
|
||||
}
|
||||
|
||||
public IEnumerable<RecognizeEntity<string>> RecognizeTimes(IMessageActivity message)
|
||||
{
|
||||
var entities = new List<RecognizeEntity<string>>();
|
||||
|
||||
var utterance = message?.Text?.Trim();
|
||||
var entity = RecognizeTime(utterance);
|
||||
|
||||
entities.Add(new RecognizeEntity<string>()
|
||||
{
|
||||
Entity = entity.Entity,
|
||||
Score = CalculateScore(utterance, entity.Entity)
|
||||
});
|
||||
|
||||
return entities;
|
||||
}
|
||||
|
||||
public IEnumerable<RecognizeEntity<T>> RecognizeChoices<T>(IMessageActivity message, IReadOnlyDictionary<T, IReadOnlyList<T>> choicesDictionary, IPromptRecognizeChoicesOptions options = null)
|
||||
{
|
||||
var entities = new List<RecognizeEntity<T>>();
|
||||
var index = 0;
|
||||
foreach (var choices in choicesDictionary)
|
||||
{
|
||||
var values = choices.Value?.ToList() ?? new List<T>();
|
||||
var excludeValue = options?.ExcludeValue ?? false;
|
||||
if (!excludeValue)
|
||||
{
|
||||
values.Add(choices.Key);
|
||||
}
|
||||
var match = RecognizeValues(message, values, options).MaxBy(x => x.Score);
|
||||
if (match != null)
|
||||
{
|
||||
entities.Add(new RecognizeEntity<T>
|
||||
{
|
||||
Entity = choices.Key,
|
||||
Score = match.Score
|
||||
});
|
||||
}
|
||||
index++;
|
||||
}
|
||||
return entities;
|
||||
}
|
||||
|
||||
public IEnumerable<RecognizeEntity<bool>> RecognizeBooleans(IMessageActivity message)
|
||||
{
|
||||
var entities = new List<RecognizeEntity<bool>>();
|
||||
|
||||
var results = RecognizeLocalizedChoices(message, ResourceKeyBooleans, Resources.ResourceManager, new PromptRecognizeChoicesOptions());
|
||||
if (results != null)
|
||||
{
|
||||
entities.AddRange(
|
||||
results.Select(x => new RecognizeEntity<bool> { Entity = bool.Parse(x.Entity), Score = x.Score })
|
||||
);
|
||||
}
|
||||
|
||||
return entities;
|
||||
}
|
||||
|
||||
private static IEnumerable<RecognizeEntity<T>> RecognizeValues<T>(IMessageActivity message, IEnumerable<T> values, IPromptRecognizeChoicesOptions options = null)
|
||||
{
|
||||
var utterance = message?.Text?.Trim().ToLowerInvariant() ?? string.Empty;
|
||||
var entities = new List<RecognizeEntity<T>>();
|
||||
IList<string> tokens = new List<string>();
|
||||
foreach (Match match in simpleTokenizer.Matches(utterance))
|
||||
{
|
||||
tokens.Add(match.Value);
|
||||
}
|
||||
var maxDistance = options?.MaxTokenDistance ?? 2;
|
||||
var index = 0;
|
||||
foreach (var value in values)
|
||||
{
|
||||
var text = value?.ToString().Trim().ToLowerInvariant();
|
||||
var topScore = 0.0;
|
||||
IList<string> vTokens = new List<string>();
|
||||
foreach (Match match in simpleTokenizer.Matches(text))
|
||||
{
|
||||
vTokens.Add(match.Value);
|
||||
}
|
||||
for (int i = 0; i < tokens.Count; i++)
|
||||
{
|
||||
var score = MatchValue(tokens.ToArray(), vTokens.ToArray(), i, maxDistance, options?.AllowPartialMatches ?? false);
|
||||
if (topScore < score)
|
||||
{
|
||||
topScore = score;
|
||||
}
|
||||
}
|
||||
if (topScore > 0.0)
|
||||
{
|
||||
entities.Add(new RecognizeEntity<T>
|
||||
{
|
||||
Entity = value,
|
||||
Score = topScore
|
||||
});
|
||||
}
|
||||
index++;
|
||||
}
|
||||
return entities;
|
||||
}
|
||||
|
||||
private static ChronoDuration RecognizeTime(string utterance)
|
||||
{
|
||||
ChronoDuration response = null;
|
||||
try
|
||||
{
|
||||
Chronic.Parser parser = new Chronic.Parser();
|
||||
var results = parser.Parse(utterance);
|
||||
|
||||
if (results != null)
|
||||
{
|
||||
response = new ChronoDuration()
|
||||
{
|
||||
Entity = results.ToTime().TimeOfDay.ToString(),
|
||||
Resolution = new ChronoDurationResolution()
|
||||
{
|
||||
Start = results.Start,
|
||||
End = results.End
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error recognizing time: {ex.Message}");
|
||||
response = null;
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private static ChoicesDictionary ConvertToChoices(IEnumerable<string> values)
|
||||
{
|
||||
var result = new ChoicesDictionary();
|
||||
foreach (var term in values)
|
||||
{
|
||||
var subTerm = term.Split('=');
|
||||
if (subTerm.Count() == 2)
|
||||
{
|
||||
var choices = subTerm[1].Split(',');
|
||||
result.Add(subTerm[0], choices);
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Add(subTerm[0], Enumerable.Empty<string>().ToList());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
private static double MatchValue(string[] tokens, string[] vTokens, int index, int maxDistance, bool allowPartialMatches)
|
||||
{
|
||||
var startPosition = index;
|
||||
double matched = 0;
|
||||
var totalDeviation = 0;
|
||||
foreach (var token in vTokens)
|
||||
{
|
||||
var pos = IndexOfToken(tokens.ToList(), token, startPosition);
|
||||
if (pos >= 0)
|
||||
{
|
||||
var distance = matched > 0 ? pos - startPosition : 0;
|
||||
if (distance <= maxDistance)
|
||||
{
|
||||
matched++;
|
||||
totalDeviation += distance;
|
||||
startPosition = pos + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var score = 0.0;
|
||||
if (matched > 0 && (matched == vTokens.Length || allowPartialMatches))
|
||||
{
|
||||
var completeness = matched / vTokens.Length;
|
||||
var accuracy = completeness * (matched / (matched + totalDeviation));
|
||||
var initialScore = accuracy * (matched / tokens.Length);
|
||||
|
||||
score = 0.4 + (0.6 * initialScore);
|
||||
}
|
||||
return score;
|
||||
}
|
||||
|
||||
private static int IndexOfToken(List<string> tokens, string token, int startPos)
|
||||
{
|
||||
if (tokens.Count <= startPos) return -1;
|
||||
return tokens.FindIndex(startPos, x => x == token);
|
||||
}
|
||||
|
||||
private static double CalculateScore(string utterance, string entity, double max = 1.0, double min = 0.5)
|
||||
{
|
||||
return Math.Min(min + (entity.Length / (double)utterance.Length), max);
|
||||
}
|
||||
|
||||
private static string GetLocalizedResource(string resourceKey, string locale, ResourceManager resourceManager)
|
||||
{
|
||||
CultureInfo culture;
|
||||
try
|
||||
{
|
||||
culture = new CultureInfo(locale);
|
||||
}
|
||||
catch
|
||||
{
|
||||
culture = new CultureInfo("en-US");
|
||||
}
|
||||
return resourceManager.GetString(resourceKey, culture);
|
||||
}
|
||||
}
|
||||
|
||||
public static partial class Extensions
|
||||
{
|
||||
/// <summary>Recognizer for a Int64 number.</summary>
|
||||
/// <param name="recognizer"><see cref="IPromptRecognizer"/></param>
|
||||
/// <param name="message">Message context.</param>
|
||||
public static IEnumerable<RecognizeEntity<Int64>> RecognizeInteger(this IPromptRecognizer recognizer, IMessageActivity message)
|
||||
{
|
||||
var entities = recognizer.RecognizeNumbers(message, new PromptRecognizeNumbersOptions { IntegerOnly = true });
|
||||
return entities.Select(x => new RecognizeEntity<Int64> { Entity = Convert.ToInt64(x.Entity), Score = x.Score });
|
||||
}
|
||||
|
||||
/// <summary>Recognizer for a double number.</summary>
|
||||
/// <param name="recognizer"><see cref="IPromptRecognizer"/></param>
|
||||
/// <param name="message">Message context.</param>
|
||||
public static IEnumerable<RecognizeEntity<double>> RecognizeDouble(this IPromptRecognizer recognizer, IMessageActivity message)
|
||||
{
|
||||
return recognizer.RecognizeNumbers(message, new PromptRecognizeNumbersOptions { IntegerOnly = false });
|
||||
}
|
||||
|
||||
/// <summary>Recognizer for a Int64 number within a range</summary>
|
||||
/// <param name="recognizer"><see cref="IPromptRecognizer"/></param>
|
||||
/// <param name="message">Message context.</param>
|
||||
/// <param name="max">Maximum value.</param>
|
||||
/// <param name="min">Minimun value.</param>
|
||||
public static IEnumerable<RecognizeEntity<Int64>> RecognizeIntegerInRange(this IPromptRecognizer recognizer, IMessageActivity message, long? min, long? max)
|
||||
{
|
||||
var entities = recognizer.RecognizeNumbers(message, new PromptRecognizeNumbersOptions { IntegerOnly = true, MinValue = min, MaxValue = max });
|
||||
return entities.Select(x => new RecognizeEntity<Int64> { Entity = Convert.ToInt64(x.Entity), Score = x.Score });
|
||||
}
|
||||
|
||||
/// <summary>Recognizes the double in range.</summary>
|
||||
/// <param name="recognizer"><see cref="IPromptRecognizer"/></param>
|
||||
/// <param name="message">Message context.</param>
|
||||
/// <param name="max">Maximum value.</param>
|
||||
/// <param name="min">Minimun value.</param>
|
||||
public static IEnumerable<RecognizeEntity<double>> RecognizeDoubleInRange(this IPromptRecognizer recognizer, IMessageActivity message, double? min, double? max)
|
||||
{
|
||||
var entities = recognizer.RecognizeNumbers(message, new PromptRecognizeNumbersOptions { IntegerOnly = false, MinValue = min, MaxValue = max });
|
||||
return entities.Select(x => new RecognizeEntity<double> { Entity = Convert.ToDouble(x.Entity), Score = x.Score });
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.Bot.Builder.V3Bridge.Base;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Internals.Fibers;
|
||||
using Microsoft.Bot.Schema;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Scorables;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Dialogs.Internals
|
||||
{
|
||||
/// <summary>
|
||||
/// This event loop dispatches incoming activities to a scorable action, and then if the
|
||||
/// scorable action does not match, some inner consumer of activities (usually the dialog system).
|
||||
/// </summary>
|
||||
public sealed class ScoringEventLoop<Score> : IEventLoop
|
||||
{
|
||||
private readonly IEventLoop innerLoop;
|
||||
private readonly IEventProducer<IActivity> innerProducer;
|
||||
private readonly IEventConsumer<IActivity> consumer;
|
||||
private readonly IScorable<IActivity, Score> scorable;
|
||||
public ScoringEventLoop(IEventLoop innerLoop, IEventProducer<IActivity> innerProducer, IEventConsumer<IActivity> consumer, IScorable<IActivity, Score> scorable)
|
||||
{
|
||||
SetField.NotNull(out this.innerLoop, nameof(innerLoop), innerLoop);
|
||||
SetField.NotNull(out this.innerProducer, nameof(innerProducer), innerProducer);
|
||||
SetField.NotNull(out this.consumer, nameof(consumer), consumer);
|
||||
SetField.NotNull(out this.scorable, nameof(scorable), scorable);
|
||||
}
|
||||
|
||||
async Task IEventLoop.PollAsync(CancellationToken token)
|
||||
{
|
||||
// for proactive dialogs
|
||||
await this.innerLoop.PollAsync(token);
|
||||
|
||||
IActivity activity;
|
||||
while (this.consumer.TryPull(out activity))
|
||||
{
|
||||
// for event wait completions
|
||||
await this.innerLoop.PollAsync(token);
|
||||
|
||||
if (await this.scorable.TryPostAsync(activity, token))
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
this.innerProducer.Post(activity);
|
||||
}
|
||||
|
||||
// for normal wait completions
|
||||
await this.innerLoop.PollAsync(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Internals.Fibers
|
||||
{
|
||||
public interface IAwaiter<out T> : INotifyCompletion
|
||||
{
|
||||
bool IsCompleted { get; }
|
||||
|
||||
T GetResult();
|
||||
}
|
||||
}
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Dialogs
|
||||
{
|
||||
using Microsoft.Bot.Builder.V3Bridge.Internals.Fibers;
|
||||
|
||||
/// <summary>
|
||||
/// Explicit interface to support the compiling of async/await.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the contained value.</typeparam>
|
||||
public interface IAwaitable<out T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the awaiter for this awaitable item.
|
||||
/// </summary>
|
||||
/// <returns>The awaiter.</returns>
|
||||
Microsoft.Bot.Builder.V3Bridge.Internals.Fibers.IAwaiter<T> GetAwaiter();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="IAwaitable{T}"/> from item passed to constructor.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"> The type of the item.</typeparam>
|
||||
public sealed class AwaitableFromItem<T> : IAwaitable<T>, IAwaiter<T>
|
||||
{
|
||||
private readonly T item;
|
||||
|
||||
public AwaitableFromItem(T item)
|
||||
{
|
||||
this.item = item;
|
||||
}
|
||||
|
||||
bool IAwaiter<T>.IsCompleted
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
T IAwaiter<T>.GetResult()
|
||||
{
|
||||
return item;
|
||||
}
|
||||
|
||||
void INotifyCompletion.OnCompleted(Action continuation)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
IAwaiter<T> IAwaitable<T>.GetAwaiter()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public partial class Awaitable
|
||||
{
|
||||
/// <summary>
|
||||
/// Wraps item in a <see cref="IAwaitable{T}"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the item.</typeparam>
|
||||
/// <param name="item">The item that will be wrapped.</param>
|
||||
public static IAwaitable<T> FromItem<T>(T item)
|
||||
{
|
||||
return new AwaitableFromItem<T>(item);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,142 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Internals.Fibers
|
||||
{
|
||||
[Serializable]
|
||||
public abstract class InvalidWaitException : InvalidOperationException
|
||||
{
|
||||
private readonly IWait wait;
|
||||
|
||||
public IWait Wait { get { return this.wait; } }
|
||||
|
||||
protected InvalidWaitException(string message, IWait wait)
|
||||
: base(message)
|
||||
{
|
||||
SetField.NotNull(out this.wait, nameof(wait), wait);
|
||||
}
|
||||
protected InvalidWaitException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
SetField.NotNullFrom(out this.wait, nameof(this.wait), info);
|
||||
}
|
||||
public override void GetObjectData(SerializationInfo info, StreamingContext context)
|
||||
{
|
||||
base.GetObjectData(info, context);
|
||||
info.AddValue(nameof(this.wait), wait);
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class InvalidNeedException : InvalidWaitException
|
||||
{
|
||||
private readonly Need need;
|
||||
private readonly Need have;
|
||||
public Need Need { get { return this.need; } }
|
||||
public Need Have { get { return this.have; } }
|
||||
|
||||
public InvalidNeedException(IWait wait, Need need)
|
||||
: base($"invalid need: expected {need}, have {wait.Need}", wait)
|
||||
{
|
||||
this.need = need;
|
||||
this.have = wait.Need;
|
||||
}
|
||||
private InvalidNeedException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
this.need = (Need)info.GetValue(nameof(this.need), typeof(Need));
|
||||
this.have = (Need)info.GetValue(nameof(this.have), typeof(Need));
|
||||
}
|
||||
|
||||
public override void GetObjectData(SerializationInfo info, StreamingContext context)
|
||||
{
|
||||
base.GetObjectData(info, context);
|
||||
info.AddValue(nameof(this.need), this.need);
|
||||
info.AddValue(nameof(this.have), this.have);
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class InvalidTypeException : InvalidWaitException
|
||||
{
|
||||
private readonly Type type;
|
||||
|
||||
public InvalidTypeException(IWait wait, Type type)
|
||||
: base($"invalid type: expected {wait.ItemType}, have {type.Name}", wait)
|
||||
{
|
||||
SetField.NotNull(out this.type, nameof(type), type);
|
||||
}
|
||||
private InvalidTypeException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
SetField.NotNullFrom(out this.type, nameof(this.type), info);
|
||||
}
|
||||
public override void GetObjectData(SerializationInfo info, StreamingContext context)
|
||||
{
|
||||
base.GetObjectData(info, context);
|
||||
info.AddValue(nameof(this.type), type);
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class InvalidNextException : InvalidWaitException
|
||||
{
|
||||
public InvalidNextException(IWait wait)
|
||||
: base($"invalid next: {wait}", wait)
|
||||
{
|
||||
}
|
||||
private InvalidNextException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class ClosureCaptureException : SerializationException
|
||||
{
|
||||
[NonSerialized]
|
||||
public readonly object Instance;
|
||||
public ClosureCaptureException(object instance)
|
||||
: base($"anonymous method closures that capture the environment are not serializable, consider removing environment capture or using a reflection serialization surrogate: {instance}")
|
||||
{
|
||||
this.Instance = instance;
|
||||
}
|
||||
private ClosureCaptureException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.Bot.Builder.V3Bridge.Dialogs;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Internals.Fibers
|
||||
{
|
||||
public static partial class Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Without pushing or popping the stack, schedule a wait to be satisfied later.
|
||||
/// </summary>
|
||||
public static IWait<C> Wait<C, T>(this IFiber<C> fiber, Rest<C, T> resumeHandler)
|
||||
{
|
||||
var wait = fiber.Waits.Make<T>();
|
||||
wait.Wait(resumeHandler);
|
||||
fiber.Wait = wait;
|
||||
return wait;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scheduled a wait for the return value, then invoke the <see cref="Call{C, T}(IFiber{C}, Rest{C, T}, T)"/> method.
|
||||
/// </summary>
|
||||
public static IWait<C> Call<C, T, R>(this IFiber<C> fiber, Rest<C, T> invokeHandler, T item, Rest<C, R> returnHandler)
|
||||
{
|
||||
// tell the leaf frame of the stack to wait for the return value
|
||||
var wait = fiber.Wait(returnHandler);
|
||||
|
||||
// call the child
|
||||
return fiber.Call<C, T>(invokeHandler, item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Push a frame on the stack, schedule a wait, and immediately satisfy that wait.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This overload is used to allow a child to later call <see cref="Done{C, T}(IFiber{C}, T)"/>
|
||||
/// to satisfy an existing wait without scheduling a new wait for the child's return value.
|
||||
/// </remarks>
|
||||
public static IWait<C> Call<C, T>(this IFiber<C> fiber, Rest<C, T> invokeHandler, T item)
|
||||
{
|
||||
// make a frame on the stack for calling the method
|
||||
fiber.Push();
|
||||
|
||||
// initiate and immediately complete a wait for calling the child
|
||||
var wait = fiber.Wait(invokeHandler);
|
||||
wait.Post(item);
|
||||
return wait;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove the frame from the stack, and satisfy the existing wait with the return value.
|
||||
/// </summary>
|
||||
public static IWait<C> Done<C, T>(this IFiber<C> fiber, T item)
|
||||
{
|
||||
// pop the stack
|
||||
fiber.Done();
|
||||
|
||||
// complete the caller's wait for the return value
|
||||
fiber.Wait.Post(item);
|
||||
return fiber.Wait;
|
||||
}
|
||||
|
||||
public static void Reset<C>(this IFiber<C> fiber)
|
||||
{
|
||||
while (fiber.Frames.Count > 0)
|
||||
{
|
||||
fiber.Done();
|
||||
}
|
||||
}
|
||||
|
||||
public static IWait<C> Post<C, T>(this IFiber<C> fiber, T item)
|
||||
{
|
||||
fiber.Wait.Post(item);
|
||||
return fiber.Wait;
|
||||
}
|
||||
|
||||
public static IWait<C> Fail<C>(this IFiber<C> fiber, Exception error)
|
||||
{
|
||||
// pop the stack
|
||||
fiber.Done();
|
||||
|
||||
// complete the caller's wait with an exception
|
||||
fiber.Wait.Fail(error);
|
||||
return fiber.Wait;
|
||||
}
|
||||
|
||||
public static void ValidateNeed(this IWait wait, Need need)
|
||||
{
|
||||
if (need != wait.Need)
|
||||
{
|
||||
throw new InvalidNeedException(wait, need);
|
||||
}
|
||||
}
|
||||
|
||||
public static IWait<C> CloneTyped<C>(this IWait<C> wait)
|
||||
{
|
||||
return (IWait<C>)wait.Clone();
|
||||
}
|
||||
|
||||
public static Task<T> ToTask<T>(this IAwaitable<T> item)
|
||||
{
|
||||
var source = new TaskCompletionSource<T>();
|
||||
try
|
||||
{
|
||||
var result = item.GetAwaiter().GetResult();
|
||||
source.SetResult(result);
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
source.SetException(error);
|
||||
}
|
||||
|
||||
return source.Task;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,253 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Internals.Fibers
|
||||
{
|
||||
/// <summary>
|
||||
/// Waiters wait for an item to be posted.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Fibers and fiber frames are both waiters.
|
||||
/// </remarks>
|
||||
public interface IWaiter<C>
|
||||
{
|
||||
/// <summary>
|
||||
/// A "mailbox" for storing a wait associated with this frame.
|
||||
/// </summary>
|
||||
IWait<C> Mark { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The active wait for this waiter.
|
||||
/// </summary>
|
||||
IWait<C> Wait { get; set; }
|
||||
}
|
||||
|
||||
public interface IFiber<C> : IWaiter<C>
|
||||
{
|
||||
IWaitFactory<C> Waits { get; }
|
||||
IReadOnlyList<IFrame<C>> Frames { get; }
|
||||
void Push();
|
||||
void Done();
|
||||
}
|
||||
|
||||
public interface IFiberLoop<C> : IFiber<C>
|
||||
{
|
||||
Task<IWait<C>> PollAsync(C context, CancellationToken token);
|
||||
}
|
||||
|
||||
public interface IFrameLoop<C>
|
||||
{
|
||||
Task<IWait<C>> PollAsync(IFiber<C> fiber, C context, CancellationToken token);
|
||||
}
|
||||
|
||||
|
||||
public interface IFrame<C> : IWaiter<C>, IFrameLoop<C>
|
||||
{
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class Frame<C> : IFrame<C>
|
||||
{
|
||||
private IWait<C> mark = NullWait<C>.Instance;
|
||||
private IWait<C> wait = NullWait<C>.Instance;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return this.wait.ToString();
|
||||
}
|
||||
|
||||
IWait<C> IWaiter<C>.Mark
|
||||
{
|
||||
get { return this.mark; }
|
||||
set { this.mark = value; }
|
||||
}
|
||||
|
||||
IWait<C> IWaiter<C>.Wait
|
||||
{
|
||||
get { return this.wait; }
|
||||
set
|
||||
{
|
||||
if (this.wait is NullWait<C>)
|
||||
{
|
||||
this.wait = null;
|
||||
}
|
||||
|
||||
if (this.wait != null)
|
||||
{
|
||||
this.wait.ValidateNeed(Need.Call);
|
||||
this.wait = null;
|
||||
}
|
||||
|
||||
this.wait = value;
|
||||
}
|
||||
}
|
||||
|
||||
async Task<IWait<C>> IFrameLoop<C>.PollAsync(IFiber<C> fiber, C context, CancellationToken token)
|
||||
{
|
||||
return await this.wait.PollAsync(fiber, context, token);
|
||||
}
|
||||
}
|
||||
|
||||
public interface IFrameFactory<C>
|
||||
{
|
||||
IFrame<C> Make();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class FrameFactory<C> : IFrameFactory<C>
|
||||
{
|
||||
IFrame<C> IFrameFactory<C>.Make()
|
||||
{
|
||||
return new Frame<C>();
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class Fiber<C> : IFiber<C>, IFiberLoop<C>
|
||||
{
|
||||
private readonly List<IFrame<C>> stack = new List<IFrame<C>>();
|
||||
private readonly IFrameFactory<C> frames;
|
||||
private readonly IWaitFactory<C> waits;
|
||||
|
||||
public Fiber(IFrameFactory<C> factory, IWaitFactory<C> waits)
|
||||
{
|
||||
SetField.NotNull(out this.frames, nameof(factory), factory);
|
||||
SetField.NotNull(out this.waits, nameof(waits), waits);
|
||||
}
|
||||
|
||||
IWaitFactory<C> IFiber<C>.Waits => this.waits;
|
||||
|
||||
IReadOnlyList<IFrame<C>> IFiber<C>.Frames => this.stack;
|
||||
|
||||
void IFiber<C>.Push()
|
||||
{
|
||||
this.stack.Push(this.frames.Make());
|
||||
}
|
||||
|
||||
void IFiber<C>.Done()
|
||||
{
|
||||
this.stack.Pop();
|
||||
}
|
||||
|
||||
IWait<C> IWaiter<C>.Mark
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.stack.Count > 0)
|
||||
{
|
||||
var leaf = this.stack.Peek();
|
||||
return leaf.Mark;
|
||||
}
|
||||
else
|
||||
{
|
||||
return NullWait<C>.Instance;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
this.stack.Peek().Mark = value;
|
||||
}
|
||||
}
|
||||
|
||||
IWait<C> IWaiter<C>.Wait
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.stack.Count > 0)
|
||||
{
|
||||
var leaf = this.stack.Peek();
|
||||
return leaf.Wait;
|
||||
}
|
||||
else
|
||||
{
|
||||
return NullWait<C>.Instance;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
this.stack.Peek().Wait = value;
|
||||
}
|
||||
}
|
||||
|
||||
async Task<IWait<C>> IFiberLoop<C>.PollAsync(C context, CancellationToken token)
|
||||
{
|
||||
while (this.stack.Count > 0)
|
||||
{
|
||||
var leaf = this.stack.Peek();
|
||||
var wait = leaf.Wait;
|
||||
switch (wait.Need)
|
||||
{
|
||||
case Need.None:
|
||||
case Need.Wait:
|
||||
case Need.Done:
|
||||
return wait;
|
||||
case Need.Poll:
|
||||
break;
|
||||
default:
|
||||
throw new InvalidNeedException(wait, Need.Poll);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var next = await leaf.PollAsync(this, context, token);
|
||||
var peek = this.stack.Peek();
|
||||
bool fine = object.ReferenceEquals(next, peek.Wait) || next is NullWait<C>;
|
||||
if (!fine)
|
||||
{
|
||||
throw new InvalidNextException(next);
|
||||
}
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
this.stack.Pop();
|
||||
if (this.stack.Count == 0)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
else
|
||||
{
|
||||
var parent = this.stack.Peek();
|
||||
parent.Wait.Fail(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NullWait<C>.Instance;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Internals.Fibers
|
||||
{
|
||||
public interface IStore<T>
|
||||
{
|
||||
void Reset();
|
||||
|
||||
bool TryLoad(out T item);
|
||||
|
||||
void Save(T item);
|
||||
|
||||
void Flush();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Internals.Fibers
|
||||
{
|
||||
public static class Methods
|
||||
{
|
||||
public static Rest<C, T> Identity<C, T>()
|
||||
{
|
||||
return IdentityMethod<C, T>.Instance.IdentityAsync;
|
||||
}
|
||||
|
||||
public static Rest<C, T> Loop<C, T>(Rest<C, T> rest, int count)
|
||||
{
|
||||
var loop = new LoopMethod<C, T>(rest, count);
|
||||
return loop.LoopAsync;
|
||||
}
|
||||
|
||||
public static Rest<C, T> Void<C, T>(Rest<C, T> rest)
|
||||
{
|
||||
var root = new VoidMethod<C, T>(rest);
|
||||
return root.RootAsync;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
private sealed class IdentityMethod<C, T>
|
||||
{
|
||||
public static readonly IdentityMethod<C, T> Instance = new IdentityMethod<C, T>();
|
||||
|
||||
private IdentityMethod()
|
||||
{
|
||||
}
|
||||
|
||||
public async Task<IWait<C>> IdentityAsync(IFiber<C> fiber, C context, IItem<T> item, CancellationToken token)
|
||||
{
|
||||
return fiber.Done(await item);
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
private sealed class LoopMethod<C, T>
|
||||
{
|
||||
private readonly Rest<C, T> rest;
|
||||
private int count;
|
||||
private T item;
|
||||
|
||||
public LoopMethod(Rest<C, T> rest, int count)
|
||||
{
|
||||
SetField.NotNull(out this.rest, nameof(rest), rest);
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public async Task<IWait<C>> LoopAsync(IFiber<C> fiber, C context, IItem<T> item, CancellationToken token)
|
||||
{
|
||||
this.item = await item;
|
||||
|
||||
--this.count;
|
||||
if (this.count >= 0)
|
||||
{
|
||||
return fiber.Call<C, T, object>(this.rest, this.item, NextAsync);
|
||||
}
|
||||
else
|
||||
{
|
||||
return fiber.Done(this.item);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IWait<C>> NextAsync(IFiber<C> fiber, C context, IItem<object> ignore, CancellationToken token)
|
||||
{
|
||||
--this.count;
|
||||
if (this.count >= 0)
|
||||
{
|
||||
return fiber.Call<C, T, object>(this.rest, this.item, NextAsync);
|
||||
}
|
||||
else
|
||||
{
|
||||
return fiber.Done(this.item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
private sealed class VoidMethod<C, T>
|
||||
{
|
||||
private readonly Rest<C, T> rest;
|
||||
|
||||
public VoidMethod(Rest<C, T> rest)
|
||||
{
|
||||
SetField.NotNull(out this.rest, nameof(rest), rest);
|
||||
}
|
||||
|
||||
public async Task<IWait<C>> RootAsync(IFiber<C> fiber, C context, IItem<T> item, CancellationToken token)
|
||||
{
|
||||
return fiber.Call<C, T, object>(this.rest, await item, DoneAsync);
|
||||
}
|
||||
|
||||
public async Task<IWait<C>> DoneAsync(IFiber<C> fiber, C context, IItem<object> ignore, CancellationToken token)
|
||||
{
|
||||
return NullWait<C>.Instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,298 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using Microsoft.Bot.Builder.V3Bridge.Scorables.Internals;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Internals.Fibers
|
||||
{
|
||||
public static class Serialization
|
||||
{
|
||||
/// <summary>
|
||||
/// Extend <see cref="ISerializationSurrogate"/> with a "tester" method used by <see cref="SurrogateSelector"/>.
|
||||
/// </summary>
|
||||
public interface ISurrogateProvider : ISerializationSurrogate
|
||||
{
|
||||
/// <summary>
|
||||
/// Determine whether this surrogate provider handles this type.
|
||||
/// </summary>
|
||||
/// <param name="type">The query type.</param>
|
||||
/// <param name="context">The serialization context.</param>
|
||||
/// <param name="priority">The priority of this provider.</param>
|
||||
/// <returns>True if this provider handles this type, false otherwise.</returns>
|
||||
bool Handles(Type type, StreamingContext context, out int priority);
|
||||
}
|
||||
|
||||
public sealed class StoreInstanceByTypeSurrogate : ISurrogateProvider
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class ObjectReference : IObjectReference
|
||||
{
|
||||
public readonly Type Type = null;
|
||||
object IObjectReference.GetRealObject(StreamingContext context)
|
||||
{
|
||||
var resolver = (IResolver)context.Context;
|
||||
var real = resolver.Resolve(this.Type, tag: null);
|
||||
return real;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly int priority;
|
||||
|
||||
public StoreInstanceByTypeSurrogate(int priority)
|
||||
{
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
bool ISurrogateProvider.Handles(Type type, StreamingContext context, out int priority)
|
||||
{
|
||||
var resolver = (IResolver)context.Context;
|
||||
var handles = resolver.CanResolve(type, tag: null);
|
||||
priority = handles ? this.priority : 0;
|
||||
return handles;
|
||||
}
|
||||
|
||||
void ISerializationSurrogate.GetObjectData(object obj, SerializationInfo info, StreamingContext context)
|
||||
{
|
||||
var type = obj.GetType();
|
||||
info.SetType(typeof(ObjectReference));
|
||||
info.AddValue(nameof(ObjectReference.Type), type);
|
||||
}
|
||||
|
||||
object ISerializationSurrogate.SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class StoreInstanceByFieldsSurrogate : ISurrogateProvider
|
||||
{
|
||||
public const BindingFlags Flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
|
||||
private readonly int priority;
|
||||
|
||||
public StoreInstanceByFieldsSurrogate(int priority)
|
||||
{
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
bool ISurrogateProvider.Handles(Type type, StreamingContext context, out int priority)
|
||||
{
|
||||
bool handles = !type.IsSerializable;
|
||||
priority = handles ? this.priority : 0;
|
||||
return handles;
|
||||
}
|
||||
|
||||
void ISerializationSurrogate.GetObjectData(object obj, SerializationInfo info, StreamingContext context)
|
||||
{
|
||||
var type = obj.GetType();
|
||||
var fields = type.GetFields(Flags);
|
||||
foreach (var field in fields)
|
||||
{
|
||||
var value = field.GetValue(obj);
|
||||
info.AddValue(field.Name, value);
|
||||
}
|
||||
}
|
||||
|
||||
object ISerializationSurrogate.SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
|
||||
{
|
||||
var type = obj.GetType();
|
||||
var fields = type.GetFields(Flags);
|
||||
foreach (var field in fields)
|
||||
{
|
||||
var value = info.GetValue(field.Name, field.FieldType);
|
||||
field.SetValue(obj, value);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ClosureCaptureErrorSurrogate : ISurrogateProvider
|
||||
{
|
||||
private readonly int priority;
|
||||
public ClosureCaptureErrorSurrogate(int priority)
|
||||
{
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
bool ISurrogateProvider.Handles(Type type, StreamingContext context, out int priority)
|
||||
{
|
||||
bool generated = Attribute.IsDefined(type, typeof(CompilerGeneratedAttribute));
|
||||
bool handles = generated && !type.IsSerializable;
|
||||
priority = handles ? this.priority : 0;
|
||||
return handles;
|
||||
}
|
||||
|
||||
void ISerializationSurrogate.GetObjectData(object obj, SerializationInfo info, StreamingContext context)
|
||||
{
|
||||
throw new ClosureCaptureException(obj);
|
||||
}
|
||||
|
||||
object ISerializationSurrogate.SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class SurrogateLogDecorator : ISurrogateProvider
|
||||
{
|
||||
private readonly HashSet<Type> visited = new HashSet<Type>();
|
||||
private readonly ISurrogateProvider inner;
|
||||
// TOOD: better tracing interface
|
||||
private readonly TraceListener trace;
|
||||
|
||||
public SurrogateLogDecorator(ISurrogateProvider inner, TraceListener trace)
|
||||
{
|
||||
SetField.NotNull(out this.inner, nameof(inner), inner);
|
||||
SetField.NotNull(out this.trace, nameof(trace), trace);
|
||||
}
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.GetType().Name}({this.inner})";
|
||||
}
|
||||
bool ISurrogateProvider.Handles(Type type, StreamingContext context, out int priority)
|
||||
{
|
||||
return this.inner.Handles(type, context, out priority);
|
||||
}
|
||||
|
||||
void ISerializationSurrogate.GetObjectData(object obj, SerializationInfo info, StreamingContext context)
|
||||
{
|
||||
this.Visit(obj);
|
||||
this.inner.GetObjectData(obj, info, context);
|
||||
}
|
||||
|
||||
object ISerializationSurrogate.SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
|
||||
{
|
||||
this.Visit(obj);
|
||||
return this.inner.SetObjectData(obj, info, context, selector);
|
||||
}
|
||||
|
||||
private void Visit(object obj)
|
||||
{
|
||||
var type = obj.GetType();
|
||||
bool added;
|
||||
lock (this.visited)
|
||||
{
|
||||
added = this.visited.Add(type);
|
||||
}
|
||||
if (added)
|
||||
{
|
||||
var message = $"{this.inner.GetType().Name}: visiting {type}";
|
||||
this.trace.WriteLine(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class JObjectSurrogate : ISurrogateProvider
|
||||
{
|
||||
private readonly int priority;
|
||||
|
||||
public JObjectSurrogate(int priority)
|
||||
{
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
bool ISurrogateProvider.Handles(Type type, StreamingContext context, out int priority)
|
||||
{
|
||||
bool handles = type == typeof(JObject);
|
||||
priority = handles ? this.priority : 0;
|
||||
return handles;
|
||||
}
|
||||
|
||||
void ISerializationSurrogate.GetObjectData(object obj, SerializationInfo info, StreamingContext context)
|
||||
{
|
||||
var instance = (JObject)obj;
|
||||
info.AddValue(typeof(JObject).Name, instance.ToString(Newtonsoft.Json.Formatting.None));
|
||||
}
|
||||
|
||||
object ISerializationSurrogate.SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
|
||||
{
|
||||
return obj = JObject.Parse((string)info.GetValue(typeof(JObject).Name, typeof(string)));
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class SurrogateSelector : ISurrogateSelector
|
||||
{
|
||||
private readonly IReadOnlyList<ISurrogateProvider> providers;
|
||||
public SurrogateSelector(IReadOnlyList<ISurrogateProvider> providers)
|
||||
{
|
||||
SetField.NotNull(out this.providers, nameof(providers), providers);
|
||||
}
|
||||
|
||||
void ISurrogateSelector.ChainSelector(ISurrogateSelector selector)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
ISurrogateSelector ISurrogateSelector.GetNextSelector()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
ISerializationSurrogate ISurrogateSelector.GetSurrogate(Type type, StreamingContext context, out ISurrogateSelector selector)
|
||||
{
|
||||
int maximumPriority = -int.MaxValue;
|
||||
ISurrogateProvider maximumProvider = null;
|
||||
for (int index = 0; index < this.providers.Count; ++index)
|
||||
{
|
||||
var provider = this.providers[index];
|
||||
int priority;
|
||||
if (provider.Handles(type, context, out priority) && priority > maximumPriority)
|
||||
{
|
||||
maximumPriority = priority;
|
||||
maximumProvider = provider;
|
||||
}
|
||||
}
|
||||
|
||||
if (maximumProvider != null)
|
||||
{
|
||||
selector = this;
|
||||
return maximumProvider;
|
||||
}
|
||||
else
|
||||
{
|
||||
selector = null;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
|
||||
using Microsoft.Bot.Builder.V3Bridge.Dialogs;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Internals.Fibers
|
||||
{
|
||||
|
||||
public interface IStackStoreFactory<C>
|
||||
{
|
||||
IStore<IFiberLoop<C>> StoreFrom(string taskId, IBotDataBag dataBag);
|
||||
}
|
||||
|
||||
public sealed class StoreFromStack<C> : IStackStoreFactory<C>
|
||||
{
|
||||
private readonly Func<string, IBotDataBag, IStore<IFiberLoop<C>>> make;
|
||||
|
||||
public StoreFromStack(Func<string, IBotDataBag, IStore<IFiberLoop<C>>> make)
|
||||
{
|
||||
SetField.NotNull(out this.make, nameof(make), make);
|
||||
}
|
||||
IStore<IFiberLoop<C>> IStackStoreFactory<C>.StoreFrom(string stackId, IBotDataBag dataBag)
|
||||
{
|
||||
return this.make(stackId, dataBag);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,166 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Internals.Fibers
|
||||
{
|
||||
public sealed class FormatterStore<T> : IStore<T>
|
||||
{
|
||||
private readonly Stream stream;
|
||||
private readonly IFormatter formatter;
|
||||
public FormatterStore(Stream stream, IFormatter formatter)
|
||||
{
|
||||
SetField.NotNull(out this.stream, nameof(stream), stream);
|
||||
SetField.NotNull(out this.formatter, nameof(formatter), formatter);
|
||||
}
|
||||
|
||||
void IStore<T>.Reset()
|
||||
{
|
||||
this.stream.SetLength(0);
|
||||
}
|
||||
|
||||
bool IStore<T>.TryLoad(out T item)
|
||||
{
|
||||
if (this.stream.Length > 0)
|
||||
{
|
||||
this.stream.Position = 0;
|
||||
using (var gzip = new GZipStream(this.stream, CompressionMode.Decompress, leaveOpen: true))
|
||||
{
|
||||
item = (T)this.formatter.Deserialize(gzip);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
item = default(T);
|
||||
return false;
|
||||
}
|
||||
|
||||
void IStore<T>.Save(T item)
|
||||
{
|
||||
this.stream.Position = 0;
|
||||
using (var gzip = new GZipStream(this.stream, CompressionMode.Compress, leaveOpen: true))
|
||||
{
|
||||
formatter.Serialize(gzip, item);
|
||||
}
|
||||
|
||||
this.stream.SetLength(this.stream.Position);
|
||||
}
|
||||
|
||||
void IStore<T>.Flush()
|
||||
{
|
||||
this.stream.Flush();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ErrorResilientStore<T> : IStore<T>
|
||||
{
|
||||
private readonly IStore<T> store;
|
||||
public ErrorResilientStore(IStore<T> store)
|
||||
{
|
||||
SetField.NotNull(out this.store, nameof(store), store);
|
||||
}
|
||||
|
||||
void IStore<T>.Reset()
|
||||
{
|
||||
this.store.Reset();
|
||||
}
|
||||
|
||||
bool IStore<T>.TryLoad(out T item)
|
||||
{
|
||||
try
|
||||
{
|
||||
return this.store.TryLoad(out item);
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
System.Diagnostics.Trace.TraceError($"Exception on deserializing stored data! Stack has been cleared. Details: {exc}");
|
||||
// exception in loading the serialized data
|
||||
item = default(T);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void IStore<T>.Save(T item)
|
||||
{
|
||||
this.store.Save(item);
|
||||
}
|
||||
|
||||
void IStore<T>.Flush()
|
||||
{
|
||||
this.store.Flush();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class FactoryStore<T> : IStore<T>
|
||||
{
|
||||
private readonly IStore<T> store;
|
||||
private readonly Func<T> factory;
|
||||
public FactoryStore(IStore<T> store, Func<T> factory)
|
||||
{
|
||||
SetField.NotNull(out this.store, nameof(store), store);
|
||||
SetField.NotNull(out this.factory, nameof(factory), factory);
|
||||
}
|
||||
|
||||
void IStore<T>.Reset()
|
||||
{
|
||||
this.store.Reset();
|
||||
}
|
||||
|
||||
bool IStore<T>.TryLoad(out T item)
|
||||
{
|
||||
if (this.store.TryLoad(out item))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
item = this.factory();
|
||||
return false;
|
||||
}
|
||||
|
||||
void IStore<T>.Save(T item)
|
||||
{
|
||||
this.store.Save(item);
|
||||
}
|
||||
|
||||
void IStore<T>.Flush()
|
||||
{
|
||||
this.store.Flush();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,418 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.Bot.Builder.V3Bridge.Base;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Dialogs;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Internals.Fibers
|
||||
{
|
||||
public interface IItem<out T> : IAwaitable<T>
|
||||
{
|
||||
}
|
||||
|
||||
public delegate Task<IWait<C>> Rest<C, in T>(IFiber<C> fiber, C context, IItem<T> item, CancellationToken token);
|
||||
|
||||
/// <summary>
|
||||
/// This is the stage of the wait, showing what the wait needs during its lifecycle.
|
||||
/// </summary>
|
||||
public enum Need
|
||||
{
|
||||
/// <summary>
|
||||
/// The wait does not need anything.
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// The wait needs an item to be posted.
|
||||
/// </summary>
|
||||
Wait,
|
||||
|
||||
/// <summary>
|
||||
/// The wait needs to be polled for execution after an item has been posted.
|
||||
/// </summary>
|
||||
Poll,
|
||||
|
||||
/// <summary>
|
||||
/// The wait is in the middle of executing the rest delegate.
|
||||
/// </summary>
|
||||
Call,
|
||||
|
||||
/// <summary>
|
||||
/// The wait has completed executing the rest delegate.
|
||||
/// </summary>
|
||||
Done
|
||||
};
|
||||
|
||||
public interface IWait
|
||||
{
|
||||
/// <summary>
|
||||
/// The stage of the wait.
|
||||
/// </summary>
|
||||
Need Need { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The type of the item parameter for the rest delegate.
|
||||
/// </summary>
|
||||
Type ItemType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The static type of the wait item.
|
||||
/// </summary>
|
||||
Type NeedType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The rest delegate method.
|
||||
/// </summary>
|
||||
Delegate Rest { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Mark this wait as satisfied with this item.
|
||||
/// </summary>
|
||||
void Post<T>(T item);
|
||||
|
||||
/// <summary>
|
||||
/// Mark this wait as satisfied with this fail exception.
|
||||
/// </summary>
|
||||
void Fail(Exception error);
|
||||
}
|
||||
|
||||
public interface IWait<C> : IWait, ICloneable
|
||||
{
|
||||
Task<IWait<C>> PollAsync(IFiber<C> fiber, C context, CancellationToken token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Null object pattern implementation of wait interface.
|
||||
/// </summary>
|
||||
public sealed class NullWait<C> : IWait<C>
|
||||
{
|
||||
public static readonly IWait<C> Instance = new NullWait<C>();
|
||||
private NullWait()
|
||||
{
|
||||
}
|
||||
|
||||
Need IWait.Need => Need.None;
|
||||
|
||||
Type IWait.NeedType => typeof(object);
|
||||
|
||||
Delegate IWait.Rest
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new InvalidNeedException(this, Need.None);
|
||||
}
|
||||
}
|
||||
|
||||
Type IWait.ItemType => typeof(object);
|
||||
|
||||
void IWait.Post<T>(T item)
|
||||
{
|
||||
throw new InvalidNeedException(this, Need.Wait);
|
||||
}
|
||||
|
||||
void IWait.Fail(Exception error)
|
||||
{
|
||||
throw new InvalidNeedException(this, Need.Wait);
|
||||
}
|
||||
|
||||
Task<IWait<C>> IWait<C>.PollAsync(IFiber<C> fiber, C context, CancellationToken token)
|
||||
{
|
||||
throw new InvalidNeedException(this, Need.Poll);
|
||||
}
|
||||
|
||||
object ICloneable.Clone()
|
||||
{
|
||||
return NullWait<C>.Instance;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IWait<C, out T> : IWait<C>
|
||||
{
|
||||
void Wait(Rest<C, T> rest);
|
||||
}
|
||||
|
||||
public interface IPost<in T>
|
||||
{
|
||||
void Post(T item);
|
||||
}
|
||||
|
||||
public sealed class PostStruct<T> : IPost<T>
|
||||
{
|
||||
private readonly IPost<object> postBoxed;
|
||||
public PostStruct(IPost<object> postBoxed)
|
||||
{
|
||||
SetField.NotNull(out this.postBoxed, nameof(postBoxed), postBoxed);
|
||||
}
|
||||
void IPost<T>.Post(T item)
|
||||
{
|
||||
this.postBoxed.Post((object)item);
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class Wait<C, T> : IItem<T>, IWait<C, T>, IPost<T>, IAwaiter<T>, IEquatable<Wait<C, T>>, ISerializable
|
||||
{
|
||||
private Rest<C, T> rest;
|
||||
private Need need;
|
||||
private T item;
|
||||
private Exception fail;
|
||||
|
||||
public Wait()
|
||||
{
|
||||
}
|
||||
|
||||
private Wait(SerializationInfo info, StreamingContext context)
|
||||
{
|
||||
SetField.NotNullFrom(out this.rest, nameof(rest), info);
|
||||
SetField.From(out this.need, nameof(need), info);
|
||||
}
|
||||
|
||||
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
|
||||
{
|
||||
info.AddValue(nameof(this.rest), this.rest);
|
||||
info.AddValue(nameof(this.need), this.need);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
IWait wait = this;
|
||||
return $"Wait: {wait.Need} {wait.NeedType?.Name} for {this.rest?.Target}.{this.rest?.Method.Name} have {wait.ItemType?.Name} {this.item}";
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return this.rest.GetHashCode();
|
||||
}
|
||||
|
||||
public override bool Equals(object other)
|
||||
{
|
||||
IEquatable<Wait<C, T>> wait = this;
|
||||
return wait.Equals(other as Wait<C, T>);
|
||||
}
|
||||
|
||||
bool IEquatable<Wait<C, T>>.Equals(Wait<C, T> other)
|
||||
{
|
||||
return other != null
|
||||
&& object.Equals(this.rest, other.rest)
|
||||
&& object.Equals(this.need, other.need)
|
||||
&& object.Equals(this.item, other.item)
|
||||
&& object.Equals(this.fail, other.fail)
|
||||
;
|
||||
}
|
||||
|
||||
Need IWait.Need => this.need;
|
||||
|
||||
Type IWait.NeedType
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.rest != null)
|
||||
{
|
||||
var method = this.rest.Method;
|
||||
var parameters = method.GetParameters();
|
||||
var itemType = parameters[2].ParameterType;
|
||||
var type = itemType.GenericTypeArguments.Single();
|
||||
return type;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Delegate IWait.Rest => this.rest;
|
||||
|
||||
Type IWait.ItemType => typeof(T);
|
||||
|
||||
async Task<IWait<C>> IWait<C>.PollAsync(IFiber<C> fiber, C context, CancellationToken token)
|
||||
{
|
||||
this.ValidateNeed(Need.Poll);
|
||||
|
||||
this.need = Need.Call;
|
||||
try
|
||||
{
|
||||
return await this.rest(fiber, context, this, token);
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.need = Need.Done;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly MethodInfo MethodPost = Types.MethodOf(() => ((IWait)null).Post(0)).GetGenericMethodDefinition();
|
||||
|
||||
void IWait.Post<D>(D item)
|
||||
{
|
||||
this.ValidateNeed(Need.Wait);
|
||||
|
||||
// try generic type variance first
|
||||
var post = this as IPost<D>;
|
||||
if (post == null)
|
||||
{
|
||||
// then work around lack of generic type variant for value types
|
||||
if (typeof(D).IsValueType)
|
||||
{
|
||||
var postBoxed = this as IPost<object>;
|
||||
if (postBoxed != null)
|
||||
{
|
||||
post = new PostStruct<D>(postBoxed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (post != null)
|
||||
{
|
||||
post.Post(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
// if we have runtime type information, use reflection and recurse
|
||||
var type = item?.GetType();
|
||||
bool reflection = type != null && !type.IsAssignableFrom(typeof(D));
|
||||
if (reflection)
|
||||
{
|
||||
var generic = MethodPost.MakeGenericMethod(type);
|
||||
generic.Invoke(this, new object[] { item });
|
||||
}
|
||||
else
|
||||
{
|
||||
// otherwise, we cannot satisfy this wait with this item
|
||||
IWait wait = this;
|
||||
wait.Fail(new InvalidTypeException(this, typeof(D)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IWait.Fail(Exception fail)
|
||||
{
|
||||
this.ValidateNeed(Need.Wait);
|
||||
|
||||
this.item = default(T);
|
||||
this.fail = fail;
|
||||
this.need = Need.Poll;
|
||||
}
|
||||
|
||||
void IPost<T>.Post(T item)
|
||||
{
|
||||
this.ValidateNeed(Need.Wait);
|
||||
|
||||
this.item = item;
|
||||
this.fail = null;
|
||||
this.need = Need.Poll;
|
||||
}
|
||||
|
||||
void IWait<C, T>.Wait(Rest<C, T> rest)
|
||||
{
|
||||
this.ValidateNeed(Need.None);
|
||||
|
||||
SetField.NotNull(out this.rest, nameof(rest), rest);
|
||||
this.need = Need.Wait;
|
||||
}
|
||||
|
||||
IAwaiter<T> IAwaitable<T>.GetAwaiter()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
bool IAwaiter<T>.IsCompleted
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (this.need)
|
||||
{
|
||||
case Need.Call:
|
||||
case Need.Done:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
T IAwaiter<T>.GetResult()
|
||||
{
|
||||
if (this.fail != null)
|
||||
{
|
||||
// http://stackoverflow.com/a/17091351
|
||||
ExceptionDispatchInfo.Capture(this.fail).Throw();
|
||||
|
||||
// just to satisfy compiler - should not reach this line
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
else
|
||||
{
|
||||
return this.item;
|
||||
}
|
||||
}
|
||||
|
||||
void INotifyCompletion.OnCompleted(Action continuation)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
object ICloneable.Clone()
|
||||
{
|
||||
var clone = new Wait<C, T>();
|
||||
clone.rest = this.rest;
|
||||
clone.need = Need.Wait;
|
||||
clone.item = default(T);
|
||||
clone.fail = null;
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IWaitFactory<C>
|
||||
{
|
||||
IWait<C, T> Make<T>();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class WaitFactory<C> : IWaitFactory<C>
|
||||
{
|
||||
IWait<C, T> IWaitFactory<C>.Make<T>()
|
||||
{
|
||||
return new Wait<C, T>();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,859 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
using Microsoft.Bot.Builder.V3Bridge.FormFlow.Advanced;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.FormFlow
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstract base class for FormFlow attributes.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public abstract class FormFlowAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// True if attribute is localizable.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Attributes that are used on classes, fields and properties should have this set.
|
||||
/// That way those attributes will be in the localization files that are generated.
|
||||
/// </remarks>
|
||||
public bool IsLocalizable { get; set; } = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute to override the default description of a field, property or enum value.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Enum | AttributeTargets.Property)]
|
||||
public class DescribeAttribute : FormFlowAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Description of the field, property or enum to use in templates and choices.
|
||||
/// </summary>
|
||||
public string Description;
|
||||
|
||||
/// <summary>
|
||||
/// Title when a card is generated from description.
|
||||
/// </summary>
|
||||
public string Title;
|
||||
|
||||
/// <summary>
|
||||
/// SubTitle when a card is generated from description.
|
||||
/// </summary>
|
||||
public string SubTitle;
|
||||
|
||||
/// <summary>
|
||||
/// URL of image to use when creating cards or buttons.
|
||||
/// </summary>
|
||||
public string Image;
|
||||
|
||||
/// <summary>
|
||||
/// Message to return when a button is pressed in a card.
|
||||
/// </summary>
|
||||
public string Message;
|
||||
|
||||
/// <summary>
|
||||
/// Description for field, property or enum value.
|
||||
/// </summary>
|
||||
/// <param name="description">Description of field, property or enum value.</param>
|
||||
/// <param name="image">URL of image to use when generating buttons.</param>
|
||||
/// <param name="message">Message to return from button.</param>
|
||||
/// <param name="title">Text if generating card.</param>
|
||||
/// <param name="subTitle">SubTitle if generating card.</param>
|
||||
public DescribeAttribute(string description = null, string image = null, string message = null, string title = null, string subTitle = null)
|
||||
{
|
||||
Description = description;
|
||||
Title = title;
|
||||
SubTitle = subTitle;
|
||||
Image = image;
|
||||
Message = message;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute to override the default terms used to match a field, property or enum value to user input.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// By default terms are generated by calling the <see cref="Advanced.Language.GenerateTerms(string, int)"/> method with a max phrase length of 3
|
||||
/// on the name of the field, property or enum value. Using this attribute you can specify your own regular expressions to match or if you specify the
|
||||
/// <see cref="MaxPhrase"/> attribute you can cause <see cref="Advanced.Language.GenerateTerms(string, int)"/> to be called on your strings with the
|
||||
/// maximum phrase length you specify. If your term is a simple alphanumeric one, then it will only be matched on word boundaries with \b unless you start your
|
||||
/// expression with parentheses in which case you control the boundary matching behavior through your regular expression.
|
||||
/// </remarks>
|
||||
[Serializable]
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
|
||||
public class TermsAttribute : FormFlowAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Regular expressions for matching user input.
|
||||
/// </summary>
|
||||
public string[] Alternatives;
|
||||
|
||||
private int _maxPhrase;
|
||||
/// <summary>
|
||||
/// The maximum pharse length to use when calling <see cref="Advanced.Language.GenerateTerms(string, int)"/> on your supplied terms.
|
||||
/// </summary>
|
||||
public int MaxPhrase
|
||||
{
|
||||
get
|
||||
{
|
||||
return _maxPhrase;
|
||||
}
|
||||
set
|
||||
{
|
||||
_maxPhrase = value;
|
||||
Alternatives = Alternatives.SelectMany(alt => Advanced.Language.GenerateTerms(alt, _maxPhrase)).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Regular expressions or terms used when matching user input.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If <see cref="MaxPhrase"/> is specified the supplied alternatives will be passed to <see cref="Advanced.Language.GenerateTerms(string, int)"/> to generate regular expressions
|
||||
/// with a maximum phrase size of <see cref="MaxPhrase"/>.
|
||||
/// </remarks>
|
||||
/// <param name="alternatives">Regular expressions or terms.</param>
|
||||
public TermsAttribute(params string[] alternatives)
|
||||
{
|
||||
Alternatives = alternatives;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifies how to show choices generated by {||} in a \ref patterns string.
|
||||
/// </summary>
|
||||
public enum ChoiceStyleOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Use the default <see cref="TemplateBaseAttribute.ChoiceStyle"/> from the <see cref="FormConfiguration.DefaultPrompt"/>.
|
||||
/// </summary>
|
||||
Default,
|
||||
|
||||
/// <summary>
|
||||
/// Automatically choose how to render choices.
|
||||
/// </summary>
|
||||
Auto,
|
||||
|
||||
/// <summary>
|
||||
/// Automatically generate text and switch between the <see cref="Inline"/> and <see cref="PerLine"/> styles based on the number of choices.
|
||||
/// </summary>
|
||||
AutoText,
|
||||
|
||||
/// <summary>
|
||||
/// Show choices on the same line.
|
||||
/// </summary>
|
||||
Inline,
|
||||
|
||||
/// <summary>
|
||||
/// Show choices with one per line.
|
||||
/// </summary>
|
||||
PerLine,
|
||||
|
||||
/// <summary> Show choices on the same line without surrounding parentheses. </summary>
|
||||
InlineNoParen,
|
||||
|
||||
/// <summary>
|
||||
/// Show choices as buttons if possible.
|
||||
/// </summary>
|
||||
Buttons,
|
||||
|
||||
/// <summary>
|
||||
/// Show choices as a carousel if possibe.
|
||||
/// </summary>
|
||||
Carousel
|
||||
};
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// How to normalize the case of words.
|
||||
/// </summary>
|
||||
public enum CaseNormalization
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Use the default from the <see cref="FormConfiguration.DefaultPrompt"/>.
|
||||
/// </summary>
|
||||
Default,
|
||||
|
||||
/// <summary>
|
||||
/// First letter of each word is capitalized
|
||||
/// </summary>
|
||||
InitialUpper,
|
||||
|
||||
/// <summary>
|
||||
/// Normalize words to lower case.
|
||||
/// </summary>
|
||||
Lower,
|
||||
|
||||
/// <summary>
|
||||
/// Normalize words to upper case.
|
||||
/// </summary>
|
||||
Upper,
|
||||
|
||||
/// <summary>
|
||||
/// Don't normalize words.
|
||||
/// </summary>
|
||||
None
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Three state boolean value.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is necessary because C# attributes do not support nullable properties.
|
||||
/// </remarks>
|
||||
public enum BoolDefault
|
||||
{
|
||||
/// <summary>
|
||||
/// Use the default from the <see cref="FormConfiguration.DefaultPrompt"/>.
|
||||
/// </summary>
|
||||
Default,
|
||||
|
||||
/// <summary>
|
||||
/// Boolean true.
|
||||
/// </summary>
|
||||
True,
|
||||
|
||||
/// <summary>
|
||||
/// Boolean false.
|
||||
/// </summary>
|
||||
False
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Control how the user gets feedback after each entry.
|
||||
/// </summary>
|
||||
public enum FeedbackOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Use the default from the <see cref="FormConfiguration.DefaultPrompt"/>.
|
||||
/// </summary>
|
||||
Default,
|
||||
|
||||
/// <summary>
|
||||
/// Provide feedback using the <see cref="TemplateUsage.Feedback"/> template only if part of the user input was not understood.
|
||||
/// </summary>
|
||||
Auto,
|
||||
|
||||
/// <summary>
|
||||
/// Provide feedback after every user input.
|
||||
/// </summary>
|
||||
Always,
|
||||
|
||||
/// <summary>
|
||||
/// Never provide feedback.
|
||||
/// </summary>
|
||||
Never
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Define the prompt used when asking about a field.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Prompts by default will come from \ref Templates.
|
||||
/// This attribute allows you to override this with one more \ref patterns strings.
|
||||
/// The actual prompt will be randomly selected from the alternatives you provide.
|
||||
/// </remarks>
|
||||
[Serializable]
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
|
||||
public class PromptAttribute : TemplateBaseAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Define a prompt with one or more \ref patterns patterns to choose from randomly.
|
||||
/// </summary>
|
||||
/// <param name="patterns">Patterns to select from.</param>
|
||||
public PromptAttribute(params string[] patterns)
|
||||
: base(patterns)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Define a prompt based on a <see cref="TemplateAttribute"/>.
|
||||
/// </summary>
|
||||
/// <param name="pattern">Template to use.</param>
|
||||
public PromptAttribute(TemplateAttribute pattern)
|
||||
: base(pattern)
|
||||
{
|
||||
IsLocalizable = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// All of the built-in templates.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A good way to understand these is to look at the default templates defined in <see cref="FormConfiguration.Templates"/>
|
||||
/// </remarks>
|
||||
public enum TemplateUsage
|
||||
{
|
||||
/// <summary> An enum constant representing the none option. </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// How to ask for a boolean.
|
||||
/// </summary>
|
||||
Bool,
|
||||
|
||||
/// <summary>
|
||||
/// What you can enter when entering a bool.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Within this template {0} is the current choice if any and {1} is no preference if optional.
|
||||
/// </remarks>
|
||||
BoolHelp,
|
||||
|
||||
/// <summary>
|
||||
/// Clarify an ambiguous choice.
|
||||
/// </summary>
|
||||
/// <remarks>This template can use {0} to capture the term that was ambiguous.</remarks>
|
||||
Clarify,
|
||||
|
||||
/// <summary>
|
||||
/// Default confirmation.
|
||||
/// </summary>
|
||||
Confirmation,
|
||||
|
||||
/// <summary>
|
||||
/// Show the current choice.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is how the current choice is represented as an option.
|
||||
/// If you change this, you should also change <see cref="FormConfiguration.CurrentChoice"/>
|
||||
/// so that what people can type matches what you show.
|
||||
/// </remarks>
|
||||
CurrentChoice,
|
||||
|
||||
/// <summary>
|
||||
/// How to ask for a <see cref="DateTime"/>.
|
||||
/// </summary>
|
||||
DateTime,
|
||||
|
||||
/// <summary>
|
||||
/// What you can enter when entering a <see cref="DateTime"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Within this template {0} is the current choice if any and {1} is no preference if optional.
|
||||
/// </remarks>
|
||||
/// <remarks>
|
||||
/// This template can use {0} to get the current choice or {1} for no preference if field is optional.
|
||||
/// </remarks>
|
||||
DateTimeHelp,
|
||||
|
||||
/// <summary>
|
||||
/// How to ask for a double.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Within this template if numerical limits are specified using <see cref="NumericAttribute"/>,
|
||||
/// {0} is the minimum possible value and {1} is the maximum possible value.
|
||||
/// </remarks>
|
||||
Double,
|
||||
|
||||
/// <summary>
|
||||
/// What you can enter when entering a double.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Within this template {0} is the current choice if any and {1} is no preference if optional.
|
||||
/// If limits are specified through <see cref="NumericAttribute"/>, then {2} will be the minimum possible value
|
||||
/// and {3} the maximum possible value.
|
||||
/// </remarks>
|
||||
/// <remarks>
|
||||
/// Within this template, {0} is current choice if any, {1} is no preference for optional and {1} and {2} are min/max if specified.
|
||||
/// </remarks>
|
||||
DoubleHelp,
|
||||
|
||||
/// <summary>
|
||||
/// What you can enter when selecting a single value from a numbered enumeration.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Within this template, {0} is the minimum choice. {1} is the maximum choice and {2} is a description of all the possible words.
|
||||
/// </remarks>
|
||||
EnumOneNumberHelp,
|
||||
|
||||
/// <summary>
|
||||
/// What you can enter when selecting multiple values from a numbered enumeration.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Within this template, {0} is the minimum choice. {1} is the maximum choice and {2} is a description of all the possible words.
|
||||
/// </remarks>
|
||||
EnumManyNumberHelp,
|
||||
|
||||
/// <summary>
|
||||
/// What you can enter when selecting one value from an enumeration.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Within this template, {2} is a list of the possible values.
|
||||
/// </remarks>
|
||||
EnumOneWordHelp,
|
||||
|
||||
/// <summary>
|
||||
/// What you can enter when selecting mutiple values from an enumeration.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Within this template, {2} is a list of the possible values.
|
||||
/// </remarks>
|
||||
EnumManyWordHelp,
|
||||
|
||||
/// <summary>
|
||||
/// How to ask for one value from an enumeration.
|
||||
/// </summary>
|
||||
EnumSelectOne,
|
||||
|
||||
/// <summary>
|
||||
/// How to ask for multiple values from an enumeration.
|
||||
/// </summary>
|
||||
EnumSelectMany,
|
||||
|
||||
/// <summary>
|
||||
/// How to show feedback after user input.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Within this template, unmatched input is available through {0}, but it should be wrapped in an optional {?} in \ref patterns in case everything was matched.
|
||||
/// </remarks>
|
||||
Feedback,
|
||||
|
||||
/// <summary>
|
||||
/// What to display when asked for help.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This template controls the overall help experience. {0} will be recognizer specific help and {1} will be command help.
|
||||
/// </remarks>
|
||||
Help,
|
||||
|
||||
/// <summary>
|
||||
/// What to display when asked for help while clarifying.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This template controls the overall help experience. {0} will be recognizer specific help and {1} will be command help.
|
||||
/// </remarks>
|
||||
HelpClarify,
|
||||
|
||||
/// <summary>
|
||||
/// What to display when asked for help while in a confirmation.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This template controls the overall help experience. {0} will be recognizer specific help and {1} will be command help.
|
||||
/// </remarks>
|
||||
HelpConfirm,
|
||||
|
||||
/// <summary>
|
||||
/// What to display when asked for help while navigating.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This template controls the overall help experience. {0} will be recognizer specific help and {1} will be command help.
|
||||
/// </remarks>
|
||||
HelpNavigation,
|
||||
|
||||
/// <summary>
|
||||
/// How to ask for an integer.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Within this template if numerical limits are specified using <see cref="NumericAttribute"/>,
|
||||
/// {0} is the minimum possible value and {1} is the maximum possible value.
|
||||
/// </remarks>
|
||||
Integer,
|
||||
|
||||
/// <summary>
|
||||
/// What you can enter while entering an integer.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Within this template, {0} is current choice if any, {1} is no preference for optional and {1} and {2} are min/max if specified.
|
||||
/// </remarks>
|
||||
IntegerHelp,
|
||||
|
||||
/// <summary>
|
||||
/// How to ask for a navigation.
|
||||
/// </summary>
|
||||
Navigation,
|
||||
|
||||
/// <summary>
|
||||
/// Help pattern for navigation commands.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Within this template, {0} has the list of possible field names.
|
||||
/// </remarks>
|
||||
NavigationCommandHelp,
|
||||
|
||||
/// <summary>
|
||||
/// Navigation format for one line in navigation choices.
|
||||
/// </summary>
|
||||
NavigationFormat,
|
||||
|
||||
/// <summary>
|
||||
/// What you can enter when navigating.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Within this template, if numeric choies are allowed {0} is the minimum possible choice
|
||||
/// and {1} the maximum possible choice.
|
||||
/// </remarks>
|
||||
NavigationHelp,
|
||||
|
||||
/// <summary>
|
||||
/// How to show no preference in an optional field.
|
||||
/// </summary>
|
||||
NoPreference,
|
||||
|
||||
/// <summary>
|
||||
/// Response when an input is not understood.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When no input is matched this template is used and gets {0} for what the user entered.
|
||||
/// </remarks>
|
||||
NotUnderstood,
|
||||
|
||||
/// <summary>
|
||||
/// Format for one entry in status.
|
||||
/// </summary>
|
||||
StatusFormat,
|
||||
|
||||
/// <summary>
|
||||
/// How to ask for a string.
|
||||
/// </summary>
|
||||
String,
|
||||
|
||||
/// <summary>
|
||||
/// What to display when asked for help when entering a string.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Within this template {0} is the current choice if any and {1} is no preference if optional.
|
||||
/// </remarks>
|
||||
StringHelp,
|
||||
|
||||
/// <summary>
|
||||
/// How to represent a value that has not yet been specified.
|
||||
/// </summary>
|
||||
Unspecified
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Define a template for generating strings.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Templates provide a pattern that uses the template language defined in \ref patterns. See <see cref="TemplateUsage"/> to see a description of all the different kinds of templates.
|
||||
/// You can also look at <see cref="FormConfiguration.Templates"/> to see all the default templates that are provided. Templates can be overriden at the form, class/struct of field level.
|
||||
/// They also support randomly selecting between templates which is a good way to introduce some variation in your responses.
|
||||
/// </remarks>
|
||||
[Serializable]
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
|
||||
public class TemplateAttribute : TemplateBaseAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// What kind of template this is.
|
||||
/// </summary>
|
||||
public readonly TemplateUsage Usage;
|
||||
|
||||
/// <summary>
|
||||
/// Specify a set of templates to randomly choose between for a particular usage.
|
||||
/// </summary>
|
||||
/// <param name="usage">How the template will be used.</param>
|
||||
/// <param name="patterns">The set of \ref patterns to randomly choose from.</param>
|
||||
public TemplateAttribute(TemplateUsage usage, params string[] patterns)
|
||||
: base(patterns)
|
||||
{
|
||||
Usage = usage;
|
||||
}
|
||||
|
||||
#region Documentation
|
||||
/// <summary> Initialize from another template. </summary>
|
||||
/// <param name="other"> The other template. </param>
|
||||
#endregion
|
||||
public TemplateAttribute(TemplateAttribute other)
|
||||
: base(other)
|
||||
{
|
||||
Usage = other.Usage;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Define a field or property as optional.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// An optional field is one where having no value is an acceptable response. By default every field is considered required and must be filled in to complete the form.
|
||||
/// </remarks>
|
||||
[Serializable]
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
|
||||
public class OptionalAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Mark a field or property as optional.
|
||||
/// </summary>
|
||||
public OptionalAttribute()
|
||||
{ }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provide limits on the possible values in a numeric field or property.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// By default the limits are the min and max of the underlying field type.
|
||||
/// </remarks>
|
||||
[Serializable]
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
|
||||
public class NumericAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Min possible value.
|
||||
/// </summary>
|
||||
public readonly double Min;
|
||||
|
||||
/// <summary>
|
||||
/// Max possible value.
|
||||
/// </summary>
|
||||
public readonly double Max;
|
||||
|
||||
/// <summary>
|
||||
/// Specify the range of possible values for a number field.
|
||||
/// </summary>
|
||||
/// <param name="min">Min value allowed.</param>
|
||||
/// <param name="max">Max value allowed.</param>
|
||||
public NumericAttribute(double min, double max)
|
||||
{
|
||||
Min = min;
|
||||
Max = max;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provide a regular expression to validate a string field.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If the regular expression is not matched the <see cref="TemplateUsage.NotUnderstood"/> template will be used for feedback.
|
||||
/// </remarks>
|
||||
[Serializable]
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
|
||||
public class PatternAttribute : Attribute
|
||||
{
|
||||
public readonly string Pattern;
|
||||
|
||||
/// <summary>
|
||||
/// Regular expression for validating the content of a string field.
|
||||
/// </summary>
|
||||
/// <param name="pattern">Regular expression for validation.</param>
|
||||
public PatternAttribute(string pattern)
|
||||
{
|
||||
Pattern = pattern;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Define a field or property as excluded.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The ignored field is a field that is excluded from the list of fields.
|
||||
/// </remarks>
|
||||
[Serializable]
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
|
||||
public class IgnoreFieldAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Mark a field or property as excluded.
|
||||
/// </summary>
|
||||
public IgnoreFieldAttribute()
|
||||
{ }
|
||||
}
|
||||
}
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.FormFlow.Advanced
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstract base class used by all attributes that use \ref patterns.
|
||||
/// </summary>
|
||||
public abstract class TemplateBaseAttribute : FormFlowAttribute
|
||||
{
|
||||
private static Random _generator = new Random();
|
||||
|
||||
/// <summary>
|
||||
/// When processing choices {||} in a \ref patterns string, provide a choice for the default value if present.
|
||||
/// </summary>
|
||||
public BoolDefault AllowDefault { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Control case when showing choices in {||} references in a \ref patterns string.
|
||||
/// </summary>
|
||||
public CaseNormalization ChoiceCase { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Format string used for presenting each choice when showing {||} choices in a \ref patterns string.
|
||||
/// </summary>
|
||||
/// <remarks>The choice format is passed two arguments, {0} is the number of the choice and {1} is the field name.</remarks>
|
||||
public string ChoiceFormat { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When constructing inline lists of choices using {||} in a \ref patterns string, the string used before the last choice.
|
||||
/// </summary>
|
||||
public string ChoiceLastSeparator { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When constructing inline choice lists for {||} in a \ref patterns string controls whether to include parentheses around choices.
|
||||
/// </summary>
|
||||
public BoolDefault ChoiceParens { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When constructing inline lists using {||} in a \ref patterns string, the string used between all choices except the last.
|
||||
/// </summary>
|
||||
public string ChoiceSeparator { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// How to display choices {||} when processed in a \ref patterns string.
|
||||
/// </summary>
|
||||
public ChoiceStyleOptions ChoiceStyle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Control what kind of feedback the user gets after each input.
|
||||
/// </summary>
|
||||
public FeedbackOptions Feedback { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Control case when showing {&} field name references in a \ref patterns string.
|
||||
/// </summary>
|
||||
public CaseNormalization FieldCase { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When constructing lists using {[]} in a \ref patterns string, the string used before the last value in the list.
|
||||
/// </summary>
|
||||
public string LastSeparator { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When constructing lists using {[]} in a \ref patterns string, the string used between all values except the last.
|
||||
/// </summary>
|
||||
public string Separator { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Control case when showing {} value references in a \ref patterns string.
|
||||
/// </summary>
|
||||
public CaseNormalization ValueCase { get; set; }
|
||||
|
||||
internal bool AllowNumbers
|
||||
{
|
||||
get
|
||||
{
|
||||
// You can match on numbers only if they are included in Choices and choices are shown
|
||||
return ChoiceFormat.Contains("{0}") && Patterns.Any((pattern) => pattern.Contains("{||}"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The pattern to use when generating a string using <see cref="Advanced.IPrompt{T}"/>.
|
||||
/// </summary>
|
||||
/// <remarks>If multiple patterns were specified, then each call to this function will return a random pattern.</remarks>
|
||||
/// <returns>Pattern to use.</returns>
|
||||
public string Pattern()
|
||||
{
|
||||
var choice = 0;
|
||||
if (Patterns.Length > 1)
|
||||
{
|
||||
lock (_generator)
|
||||
{
|
||||
choice = _generator.Next(Patterns.Length);
|
||||
}
|
||||
}
|
||||
return Patterns[choice];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// All possible templates.
|
||||
/// </summary>
|
||||
/// <returns>The possible templates.</returns>
|
||||
public string[] Patterns { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Any default values in this template will be overridden by the supplied <paramref name="defaultTemplate"/>.
|
||||
/// </summary>
|
||||
/// <param name="defaultTemplate">Default template to use to override default values.</param>
|
||||
public void ApplyDefaults(TemplateBaseAttribute defaultTemplate)
|
||||
{
|
||||
if (AllowDefault == BoolDefault.Default) AllowDefault = defaultTemplate.AllowDefault;
|
||||
if (ChoiceCase == CaseNormalization.Default) ChoiceCase = defaultTemplate.ChoiceCase;
|
||||
if (ChoiceFormat == null) ChoiceFormat = defaultTemplate.ChoiceFormat;
|
||||
if (ChoiceLastSeparator == null) ChoiceLastSeparator = defaultTemplate.ChoiceLastSeparator;
|
||||
if (ChoiceParens == BoolDefault.Default) ChoiceParens = defaultTemplate.ChoiceParens;
|
||||
if (ChoiceSeparator == null) ChoiceSeparator = defaultTemplate.ChoiceSeparator;
|
||||
if (ChoiceStyle == ChoiceStyleOptions.Default) ChoiceStyle = defaultTemplate.ChoiceStyle;
|
||||
if (FieldCase == CaseNormalization.Default) FieldCase = defaultTemplate.FieldCase;
|
||||
if (Feedback == FeedbackOptions.Default) Feedback = defaultTemplate.Feedback;
|
||||
if (LastSeparator == null) LastSeparator = defaultTemplate.LastSeparator;
|
||||
if (Separator == null) Separator = defaultTemplate.Separator;
|
||||
if (ValueCase == CaseNormalization.Default) ValueCase = defaultTemplate.ValueCase;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize with multiple patterns that will be chosen from randomly.
|
||||
/// </summary>
|
||||
/// <param name="patterns">Possible patterns.</param>
|
||||
public TemplateBaseAttribute(params string[] patterns)
|
||||
{
|
||||
Patterns = patterns;
|
||||
Initialize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize from another template.
|
||||
/// </summary>
|
||||
/// <param name="other">The template to copy from.</param>
|
||||
public TemplateBaseAttribute(TemplateBaseAttribute other)
|
||||
{
|
||||
Patterns = other.Patterns;
|
||||
AllowDefault = other.AllowDefault;
|
||||
ChoiceCase = other.ChoiceCase;
|
||||
ChoiceFormat = other.ChoiceFormat;
|
||||
ChoiceLastSeparator = other.ChoiceLastSeparator;
|
||||
ChoiceParens = other.ChoiceParens;
|
||||
ChoiceSeparator = other.ChoiceSeparator;
|
||||
ChoiceStyle = other.ChoiceStyle;
|
||||
FieldCase = other.FieldCase;
|
||||
Feedback = other.Feedback;
|
||||
LastSeparator = other.LastSeparator;
|
||||
Separator = other.Separator;
|
||||
ValueCase = other.ValueCase;
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
AllowDefault = BoolDefault.Default;
|
||||
ChoiceCase = CaseNormalization.Default;
|
||||
ChoiceFormat = null;
|
||||
ChoiceLastSeparator = null;
|
||||
ChoiceParens = BoolDefault.Default;
|
||||
ChoiceSeparator = null;
|
||||
ChoiceStyle = ChoiceStyleOptions.Default;
|
||||
FieldCase = CaseNormalization.Default;
|
||||
Feedback = FeedbackOptions.Default;
|
||||
LastSeparator = null;
|
||||
Separator = null;
|
||||
ValueCase = CaseNormalization.Default;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.FormFlow.Advanced
|
||||
{
|
||||
/// <summary>
|
||||
/// Confirmation
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Form state.</typeparam>
|
||||
public class Confirmation<T> : Field<T>
|
||||
where T : class
|
||||
{
|
||||
/// <summary>
|
||||
/// Construct a confirmation.
|
||||
/// </summary>
|
||||
/// <param name="prompt">Confirmation prompt expressed using \ref patterns.</param>
|
||||
/// <param name="condition">Delegate for whether confirmation applies.</param>
|
||||
/// <param name="dependencies">Fields that must have values before confirmation can run.</param>
|
||||
/// <param name="form">Form that contains confirmation.</param>
|
||||
public Confirmation(PromptAttribute prompt, ActiveDelegate<T> condition, IEnumerable<string> dependencies, IForm<T> form)
|
||||
: base("confirmation" + form.Steps.Count, FieldRole.Confirm)
|
||||
{
|
||||
SetPrompt(prompt);
|
||||
SetType(typeof(bool));
|
||||
SetDependencies(dependencies.ToArray());
|
||||
SetActive(condition);
|
||||
SetFieldDescription(new DescribeAttribute(form.Configuration.Confirmation) { IsLocalizable = false });
|
||||
var noStep = (dependencies.Any() ? new NextStep(dependencies) : new NextStep());
|
||||
_next = (value, state) => (bool)value ? new NextStep() : noStep;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a confirmation dynamically.
|
||||
/// </summary>
|
||||
/// <param name="generateMessage">Delegate for building confirmation.</param>
|
||||
/// <param name="condition">Delegate to see if confirmation is active.</param>
|
||||
/// <param name="dependencies">Fields that must have values before confirmation can run.</param>
|
||||
/// <param name="form">Form that contains confirmation.</param>
|
||||
public Confirmation(MessageDelegate<T> generateMessage, ActiveDelegate<T> condition, IEnumerable<string> dependencies, IForm<T> form)
|
||||
: base("confirmation" + form.Steps.Count, FieldRole.Confirm)
|
||||
{
|
||||
SetDefine(async (state, field) => { field.SetPrompt(await generateMessage(state)); return true; });
|
||||
SetType(typeof(bool));
|
||||
SetDependencies(dependencies.ToArray());
|
||||
SetActive(condition);
|
||||
SetFieldDescription(new DescribeAttribute(form.Configuration.Confirmation) { IsLocalizable = false });
|
||||
var noStep = (dependencies.Any() ? new NextStep(dependencies) : new NextStep());
|
||||
SetNext((value, state) => (bool)value ? new NextStep() : noStep);
|
||||
}
|
||||
|
||||
public override object GetValue(T state)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public override IEnumerable<string> Dependencies
|
||||
{
|
||||
get
|
||||
{
|
||||
return _dependencies;
|
||||
}
|
||||
}
|
||||
|
||||
#region IFieldPrompt
|
||||
public override bool Active(T state)
|
||||
{
|
||||
return _condition(state);
|
||||
}
|
||||
|
||||
public override NextStep Next(object value, T state)
|
||||
{
|
||||
return _next((bool)value, state);
|
||||
}
|
||||
|
||||
public override void SetValue(T state, object value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override bool IsUnknown(T state)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void SetUnknown(T state)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,213 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.FormFlow.Advanced
|
||||
{
|
||||
public static partial class Extensions
|
||||
{
|
||||
internal static IStep<T> Step<T>(this IForm<T> form, string name) where T : class
|
||||
{
|
||||
IStep<T> result = null;
|
||||
foreach (var step in form.Steps)
|
||||
{
|
||||
if (step.Name == name)
|
||||
{
|
||||
result = step;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
internal static int StepIndex<T>(this IForm<T> form, IStep<T> step) where T : class
|
||||
{
|
||||
var index = -1;
|
||||
for (var i = 0; i < form.Steps.Count; ++i)
|
||||
{
|
||||
if (form.Steps[i] == step)
|
||||
{
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
internal static IField<T> BuildCommandRecognizer<T>(this IForm<T> form) where T : class
|
||||
{
|
||||
var field = new Field<T>("__commands__", FieldRole.Value);
|
||||
field.SetPrompt(new PromptAttribute(""));
|
||||
foreach (var entry in form.Configuration.Commands)
|
||||
{
|
||||
field.AddDescription(entry.Key, entry.Value.Description);
|
||||
field.AddTerms(entry.Key, entry.Value.Terms);
|
||||
}
|
||||
foreach (var nav in form.Fields)
|
||||
{
|
||||
var fterms = nav.FieldTerms;
|
||||
if (fterms != null)
|
||||
{
|
||||
field.AddDescription(nav.Name, nav.FieldDescription);
|
||||
field.AddTerms(nav.Name, fterms.ToArray());
|
||||
}
|
||||
}
|
||||
field.Form = form;
|
||||
return field;
|
||||
}
|
||||
|
||||
internal static IEnumerable<string> Dependencies<T>(this IForm<T> form, int istep)
|
||||
where T : class
|
||||
{
|
||||
for (var i = 0; i < istep; ++i)
|
||||
{
|
||||
if (form.Steps[i].Type == StepType.Field)
|
||||
{
|
||||
yield return form.Steps[i].Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Type implements ICollection.
|
||||
/// </summary>
|
||||
/// <param name="type">Type to check.</param>
|
||||
/// <returns>True if implements ICollection.</returns>
|
||||
public static bool IsICollection(this Type type)
|
||||
{
|
||||
return Array.Exists(type.GetInterfaces(), IsGenericCollectionType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Type implements IEnumerable.
|
||||
/// </summary>
|
||||
/// <param name="type">Type to check.</param>
|
||||
/// <returns>True if implements IEnumerable.</returns>
|
||||
public static bool IsIEnumerable(this Type type)
|
||||
{
|
||||
return Array.Exists(type.GetInterfaces(), IsGenericEnumerableType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Type implements IList.
|
||||
/// </summary>
|
||||
/// <param name="type">Type to check.</param>
|
||||
/// <returns>True if implements IList.</returns>
|
||||
public static bool IsIList(this Type type)
|
||||
{
|
||||
return Array.Exists(type.GetInterfaces(), IsListCollectionType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Type implements generic ICollection.
|
||||
/// </summary>
|
||||
/// <param name="type">Type to check.</param>
|
||||
/// <returns>True if implements generic ICollection.</returns>
|
||||
public static bool IsGenericCollectionType(this Type type)
|
||||
{
|
||||
return type.IsGenericType && (typeof(ICollection<>) == type.GetGenericTypeDefinition());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Type implements generic IEnumerable.
|
||||
/// </summary>
|
||||
/// <param name="type">Type to check.</param>
|
||||
/// <returns>True if implements generic IEnumerable.</returns>
|
||||
public static bool IsGenericEnumerableType(this Type type)
|
||||
{
|
||||
return type.IsGenericType && (typeof(IEnumerable<>) == type.GetGenericTypeDefinition());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Type is integral.
|
||||
/// </summary>
|
||||
/// <param name="type">Type to check.</param>
|
||||
/// <returns>True if integral.</returns>
|
||||
public static bool IsIntegral(this Type type)
|
||||
{
|
||||
return (type == typeof(sbyte) ||
|
||||
type == typeof(byte) ||
|
||||
type == typeof(short) ||
|
||||
type == typeof(ushort) ||
|
||||
type == typeof(int) ||
|
||||
type == typeof(uint) ||
|
||||
type == typeof(long) ||
|
||||
type == typeof(ulong));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Type is float or double.
|
||||
/// </summary>
|
||||
/// <param name="type">Type to check.</param>
|
||||
/// <returns>True if float or double.</returns>
|
||||
public static bool IsDouble(this Type type)
|
||||
{
|
||||
return type == typeof(float) || type == typeof(double);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Type implements generic IList.
|
||||
/// </summary>
|
||||
/// <param name="type">Type to check.</param>
|
||||
/// <returns>True if implements generic IList.</returns>
|
||||
public static bool IsListCollectionType(this Type type)
|
||||
{
|
||||
return type.IsGenericType && (typeof(IList<>) == type.GetGenericTypeDefinition());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Type is nullable.
|
||||
/// </summary>
|
||||
/// <param name="type">Type to check.</param>
|
||||
/// <returns>True if nullable.</returns>
|
||||
public static bool IsNullable(this Type type)
|
||||
{
|
||||
return (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the underlying type of generic IEnumerable.
|
||||
/// </summary>
|
||||
/// <param name="type">Type to check.</param>
|
||||
/// <returns>True if implements generic IEnumerable.</returns>
|
||||
public static Type GetGenericElementType(this Type type)
|
||||
{
|
||||
return (from i in type.GetInterfaces()
|
||||
where i.IsGenericType && typeof(IEnumerable<>) == i.GetGenericTypeDefinition()
|
||||
select i.GetGenericArguments()[0]).FirstOrDefault();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,813 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.FormFlow.Advanced
|
||||
{
|
||||
#region Documentation
|
||||
/// <summary> Define field delegate. </summary>
|
||||
/// <typeparam name="T"> Form state type. </typeparam>
|
||||
/// <param name="state"> Form state. </param>
|
||||
/// <param name="field">Field being dynamically defined.</param>
|
||||
/// <returns>True if field is defined.</returns>
|
||||
/// <remarks>Delegate for dynamically defining a field prompt and recognizer. You can make use of the fluent methods
|
||||
/// on <see cref="Field{T}"/> to change the characteristics of the field.</remarks>
|
||||
#endregion
|
||||
public delegate Task<bool> DefineAsyncDelegate<T>(T state, Field<T> field)
|
||||
where T : class;
|
||||
|
||||
/// <summary>
|
||||
/// A delegate for deciding on the next step in the form to execute.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Form state type.</typeparam>
|
||||
/// <param name="value">Value just entered for field.</param>
|
||||
/// <param name="state">Current state object.</param>
|
||||
/// <returns></returns>
|
||||
public delegate NextStep NextDelegate<T>(object value, T state)
|
||||
where T : class;
|
||||
|
||||
/// <summary>Base class with declarative implementation of IField. </summary>
|
||||
/// <typeparam name="T">Underlying form state.</typeparam>
|
||||
public class Field<T> : IField<T>
|
||||
where T : class
|
||||
{
|
||||
/// <summary> Construct field. </summary>
|
||||
/// <param name="name"> Name of field. </param>
|
||||
/// <param name="role"> Role field plays in form. </param>
|
||||
public Field(string name, FieldRole role)
|
||||
{
|
||||
_name = name;
|
||||
_role = role;
|
||||
_min = -double.MaxValue;
|
||||
_max = double.MaxValue;
|
||||
_limited = false;
|
||||
}
|
||||
|
||||
#region IField
|
||||
|
||||
public string Name { get { return _name; } }
|
||||
|
||||
public virtual IForm<T> Form
|
||||
{
|
||||
get { return this._form; }
|
||||
set
|
||||
{
|
||||
_form = value;
|
||||
foreach (var template in _form.Configuration.Templates)
|
||||
{
|
||||
if (!_templates.ContainsKey(template.Usage))
|
||||
{
|
||||
AddTemplate(template);
|
||||
}
|
||||
}
|
||||
if (_define == null)
|
||||
{
|
||||
DefinePrompt();
|
||||
DefineRecognizer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region IFieldState
|
||||
public virtual object GetValue(T state)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public virtual void SetValue(T state, object value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public virtual bool IsUnknown(T state)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public virtual void SetUnknown(T state)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public virtual Type Type
|
||||
{
|
||||
get { return _type; }
|
||||
}
|
||||
|
||||
public virtual bool Optional
|
||||
{
|
||||
get
|
||||
{
|
||||
return _optional;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool IsNullable
|
||||
{
|
||||
get
|
||||
{
|
||||
return _isNullable;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool Limits(out double min, out double max)
|
||||
{
|
||||
min = _min;
|
||||
max = _max;
|
||||
return _limited;
|
||||
}
|
||||
|
||||
public virtual string Pattern
|
||||
{
|
||||
get { return _pattern; }
|
||||
}
|
||||
|
||||
public virtual IEnumerable<string> Dependencies
|
||||
{
|
||||
get
|
||||
{
|
||||
return _dependencies;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IFieldDescription
|
||||
public virtual FieldRole Role
|
||||
{
|
||||
get
|
||||
{
|
||||
return _role;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual DescribeAttribute FieldDescription
|
||||
{
|
||||
get
|
||||
{
|
||||
return _description;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual IEnumerable<string> FieldTerms
|
||||
{
|
||||
get
|
||||
{
|
||||
return _terms.Alternatives;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual IEnumerable<string> Terms(object value)
|
||||
{
|
||||
return _valueTerms[value].Alternatives;
|
||||
}
|
||||
|
||||
public virtual DescribeAttribute ValueDescription(object value)
|
||||
{
|
||||
return _valueDescriptions[value];
|
||||
}
|
||||
|
||||
public virtual IEnumerable<DescribeAttribute> ValueDescriptions
|
||||
{
|
||||
get
|
||||
{
|
||||
return _valueDescriptions.Values;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual IEnumerable<object> Values
|
||||
{
|
||||
get
|
||||
{
|
||||
return _valueDescriptions.Keys;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool AllowsMultiple
|
||||
{
|
||||
get
|
||||
{
|
||||
return _allowsMultiple;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool AllowDefault
|
||||
{
|
||||
get
|
||||
{
|
||||
return _promptDefinition.AllowDefault != BoolDefault.False;
|
||||
}
|
||||
}
|
||||
|
||||
public bool AllowNumbers
|
||||
{
|
||||
get
|
||||
{
|
||||
return _promptDefinition.AllowNumbers;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IFieldResources
|
||||
|
||||
public virtual void SaveResources()
|
||||
{
|
||||
var localizer = _form.Resources;
|
||||
if (_description.IsLocalizable)
|
||||
{
|
||||
localizer.Add(_name + nameof(_description), _description.Description);
|
||||
localizer.Add(_name + nameof(_description.Image), _description.Image);
|
||||
localizer.Add(_name + nameof(_description.Title), _description.Title);
|
||||
localizer.Add(_name + nameof(_description.SubTitle), _description.SubTitle);
|
||||
localizer.Add(_name + nameof(_description.Message), _description.Message);
|
||||
}
|
||||
if (_terms.IsLocalizable)
|
||||
{
|
||||
localizer.Add(_name + nameof(_terms), _terms.Alternatives);
|
||||
}
|
||||
localizer.Add(_name + nameof(_valueDescriptions), _valueDescriptions);
|
||||
localizer.Add(_name + nameof(_valueTerms), _valueTerms);
|
||||
if (_promptDefinition != null && _promptDefinition.IsLocalizable)
|
||||
{
|
||||
localizer.Add(_name + nameof(_promptDefinition), _promptDefinition.Patterns);
|
||||
}
|
||||
localizer.Add(_name, _templates);
|
||||
}
|
||||
|
||||
public virtual void Localize()
|
||||
{
|
||||
var localizer = _form.Resources;
|
||||
string strValue;
|
||||
string[] terms;
|
||||
if (localizer.Lookup(_name + nameof(_description), out strValue))
|
||||
{
|
||||
_description.Description = strValue;
|
||||
}
|
||||
if (localizer.Lookup(_name + nameof(_description.Image), out strValue))
|
||||
{
|
||||
_description.Image = strValue;
|
||||
}
|
||||
if (localizer.Lookup(_name + nameof(_description.Title), out strValue))
|
||||
{
|
||||
_description.Title = strValue;
|
||||
}
|
||||
if (localizer.Lookup(_name + nameof(_description.SubTitle), out strValue))
|
||||
{
|
||||
_description.SubTitle = strValue;
|
||||
}
|
||||
if (localizer.Lookup(_name + nameof(_description.Message), out strValue))
|
||||
{
|
||||
_description.Message = strValue;
|
||||
}
|
||||
if (localizer.LookupValues(_name + nameof(_terms), out terms))
|
||||
{
|
||||
_terms = new TermsAttribute(terms);
|
||||
}
|
||||
localizer.LookupDictionary(_name + nameof(_valueDescriptions), _valueDescriptions);
|
||||
localizer.LookupDictionary(_name + nameof(_valueTerms), _valueTerms);
|
||||
string[] patterns;
|
||||
if (localizer.LookupValues(_name + nameof(_promptDefinition), out patterns))
|
||||
{
|
||||
_promptDefinition.Patterns = patterns;
|
||||
}
|
||||
localizer.LookupTemplates(_name, _templates);
|
||||
if (!_promptSet)
|
||||
{
|
||||
_promptDefinition = null;
|
||||
}
|
||||
_prompt = null;
|
||||
_recognizer = null;
|
||||
if (_define == null)
|
||||
{
|
||||
DefinePrompt();
|
||||
DefineRecognizer();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IFieldPrompt
|
||||
|
||||
public virtual bool Active(T state)
|
||||
{
|
||||
return _condition(state);
|
||||
}
|
||||
|
||||
public virtual TemplateAttribute Template(TemplateUsage usage)
|
||||
{
|
||||
TemplateAttribute template;
|
||||
_templates.TryGetValue(usage, out template);
|
||||
if (template != null)
|
||||
{
|
||||
template.ApplyDefaults(_form.Configuration.DefaultPrompt);
|
||||
}
|
||||
return template;
|
||||
}
|
||||
|
||||
public virtual IPrompt<T> Prompt
|
||||
{
|
||||
get
|
||||
{
|
||||
return _prompt;
|
||||
}
|
||||
}
|
||||
|
||||
public async virtual Task<bool> DefineAsync(T state)
|
||||
{
|
||||
bool result = true;
|
||||
if (_define != null)
|
||||
{
|
||||
if (!_promptSet)
|
||||
{
|
||||
_promptDefinition = null;
|
||||
}
|
||||
_recognizer = null;
|
||||
_help = null;
|
||||
_prompt = null;
|
||||
result = await _define(state, this);
|
||||
DefinePrompt();
|
||||
DefineRecognizer();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public async virtual Task<ValidateResult> ValidateAsync(T state, object value)
|
||||
{
|
||||
return await _validate(state, value);
|
||||
}
|
||||
|
||||
public virtual IPrompt<T> Help
|
||||
{
|
||||
get
|
||||
{
|
||||
return _help;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual NextStep Next(object value, T state)
|
||||
{
|
||||
return _next(value, state);
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region Publics
|
||||
/// <summary>Set the field description. </summary>
|
||||
/// <param name="description">Field description. </param>
|
||||
/// <returns> A <see cref="Field{T}"/>. </returns>
|
||||
public Field<T> SetFieldDescription(string description)
|
||||
{
|
||||
_description = new DescribeAttribute(description);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the full field description.
|
||||
/// </summary>
|
||||
/// <param name="description">The field description.</param>
|
||||
/// <returns>A <see cref="Field{T}"/>. </returns>
|
||||
public Field<T> SetFieldDescription(DescribeAttribute description)
|
||||
{
|
||||
_description = description;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary> Set the terms associated with the field. </summary>
|
||||
/// <param name="terms"> The terms. </param>
|
||||
/// <returns> A <see cref="Field{T}"/>. </returns>
|
||||
public Field<T> SetFieldTerms(params string[] terms)
|
||||
{
|
||||
_terms = new TermsAttribute(terms);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary> Adds a description for a value. </summary>
|
||||
/// <param name="value"> The value. </param>
|
||||
/// <param name="description"> Description of the value. </param>
|
||||
/// <param name="image">Image to use for value as button.</param>
|
||||
/// <param name="message">Message to return when button is pressed.</param>
|
||||
/// <returns> A <see cref="Field{T}"/>. </returns>
|
||||
public Field<T> AddDescription(object value, string description, string image = null, string message = null)
|
||||
{
|
||||
_valueDescriptions[value] = new DescribeAttribute(description, image, message);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary> Adds a full description for a value. </summary>
|
||||
/// <param name="value"> The value. </param>
|
||||
/// <param name="description"> Description of the value. </param>
|
||||
/// <returns> A <see cref="Field{T}"/>. </returns>
|
||||
public Field<T> AddDescription(object value, DescribeAttribute description)
|
||||
{
|
||||
_valueDescriptions[value] = description;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary> Adds terms for a value. </summary>
|
||||
/// <param name="value"> The value. </param>
|
||||
/// <param name="terms"> The terms. </param>
|
||||
/// <returns> A <see cref="Field{T}"/>. </returns>
|
||||
public Field<T> AddTerms(object value, params string[] terms)
|
||||
{
|
||||
_valueTerms[value] = new TermsAttribute(terms);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary> Adds terms for a value. </summary>
|
||||
/// <param name="value"> The value. </param>
|
||||
/// <param name="terms"> The terms to add. </param>
|
||||
/// <returns> A <see cref="Field{T}"/>. </returns>
|
||||
public Field<T> AddTerms(object value, TermsAttribute terms)
|
||||
{
|
||||
_valueTerms[value] = terms;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary> Removes the description and terms associated with a value. </summary>
|
||||
/// <param name="value"> The value to remove. </param>
|
||||
/// <returns> A <see cref="Field{T}"/>. </returns>
|
||||
public Field<T> RemoveValue(object value)
|
||||
{
|
||||
_valueDescriptions.Remove(value);
|
||||
_valueTerms.Remove(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary> Removes all values and their associated descriptions and terms. </summary>
|
||||
/// <returns> A <see cref="Field{T}"/>.</returns>
|
||||
public Field<T> RemoveValues()
|
||||
{
|
||||
_valueDescriptions.Clear();
|
||||
_valueTerms.Clear();
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary> Sets the type of the underlying field state. </summary>
|
||||
/// <param name="type"> The field type. </param>
|
||||
/// <returns> A <see cref="Field{T}"/>. </returns>
|
||||
public Field<T> SetType(Type type)
|
||||
{
|
||||
_type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary> Set whether or not a field is optional. </summary>
|
||||
/// <param name="optional"> True if field is optional. </param>
|
||||
/// <returns> A <see cref="Field{T}"/>. </returns>
|
||||
public Field<T> SetOptional(bool optional = true)
|
||||
{
|
||||
_optional = optional;
|
||||
return this;
|
||||
}
|
||||
|
||||
#region Documentation
|
||||
/// <summary> Sets whether or not multiple values are allowed. </summary>
|
||||
/// <param name="multiple"> True if multiple values are allowed. </param>
|
||||
/// <returns> A <see cref="Field{T}"/>. </returns>
|
||||
#endregion
|
||||
public Field<T> SetAllowsMultiple(bool multiple = true)
|
||||
{
|
||||
_allowsMultiple = multiple;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary> Set whether or not field is nullable. </summary>
|
||||
/// <param name="nullable"> True if field is nullable. </param>
|
||||
/// <returns> A <see cref="Field{T}"/>. </returns>
|
||||
public Field<T> SetIsNullable(bool nullable = true)
|
||||
{
|
||||
_isNullable = nullable;
|
||||
return this;
|
||||
}
|
||||
|
||||
#region Documentation
|
||||
/// <summary> Define a delegate for checking state to see if field applies. </summary>
|
||||
/// <param name="condition"> The condition delegate. </param>
|
||||
/// <returns> A <see cref="Field{T}"/>. </returns>
|
||||
#endregion
|
||||
public Field<T> SetActive(ActiveDelegate<T> condition)
|
||||
{
|
||||
if (condition != null) _condition = condition;
|
||||
return this;
|
||||
}
|
||||
|
||||
#region Documentation
|
||||
/// <summary> Define a delegate for dynamically defining field. </summary>
|
||||
/// <param name="definition"> The definition delegate. </param>
|
||||
/// <returns> A <see cref="Field{T}"/>. </returns>
|
||||
/// <remarks>When you dynamically define a field through this delegate you can use all of the fluent methods
|
||||
/// defined on <see cref="Field{T}"/> to change the descriptions and terms dynamically.</remarks>
|
||||
#endregion
|
||||
public Field<T> SetDefine(DefineAsyncDelegate<T> definition)
|
||||
{
|
||||
if (definition != null) _define = definition;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary> Sets the field prompt. </summary>
|
||||
/// <param name="prompt"> The prompt. </param>
|
||||
/// <returns> A <see cref="Field{T}"/>. </returns>
|
||||
public Field<T> SetPrompt(PromptAttribute prompt)
|
||||
{
|
||||
_promptDefinition = prompt;
|
||||
_promptSet = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary> Sets the recognizer for the field. </summary>
|
||||
/// <param name="recognizer"> The recognizer for the field. </param>
|
||||
/// <returns> A <see cref="Field{T}"/>. </returns>
|
||||
/// <remarks>
|
||||
/// This should only be called when you are dynamically defining a field using a <see cref="DefineAsyncDelegate{T}"/> because
|
||||
/// recognizers usually require the field and often change if the localization changes.
|
||||
/// </remarks>
|
||||
public Field<T> SetRecognizer(IRecognize<T> recognizer)
|
||||
{
|
||||
_recognizer = recognizer;
|
||||
_buildPrompts = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary> Replace a template in the field. </summary>
|
||||
/// <param name="template"> The template. </param>
|
||||
/// <returns> A <see cref="Field{T}"/>. </returns>
|
||||
public Field<T> ReplaceTemplate(TemplateAttribute template)
|
||||
{
|
||||
AddTemplate(template);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary> Set the field validation. </summary>
|
||||
/// <param name="validate"> The validator. </param>
|
||||
/// <returns> A <see cref="Field{T}"/>. </returns>
|
||||
public Field<T> SetValidate(ValidateAsyncDelegate<T> validate)
|
||||
{
|
||||
if (validate != null) _validate = validate;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary> Set numeric limits. </summary>
|
||||
/// <param name="min"> The minimum. </param>
|
||||
/// <param name="max"> The maximum. </param>
|
||||
/// <returns> A <see cref="Field{T}"/>. </returns>
|
||||
public Field<T> SetLimits(double min, double max)
|
||||
{
|
||||
SetLimits(min, max, true);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Regular expression for validating strings.
|
||||
/// </summary>
|
||||
/// <param name="pattern">Validation regular expression.</param>
|
||||
/// <returns> A <see cref="Field{T}"/>. </returns>
|
||||
public Field<T> SetPattern(string pattern)
|
||||
{
|
||||
_pattern = pattern;
|
||||
var regex = new Regex(pattern, RegexOptions.Compiled);
|
||||
_validate = async (T state, object value) =>
|
||||
{
|
||||
var result = new ValidateResult { Value = value };
|
||||
if (value == null)
|
||||
{
|
||||
result.IsValid = _optional;
|
||||
}
|
||||
else
|
||||
{
|
||||
var match = regex.Match((string)value);
|
||||
result.IsValid = match.Success;
|
||||
}
|
||||
if (!result.IsValid)
|
||||
{
|
||||
result.Feedback = new Prompter<T>(Template(TemplateUsage.NotUnderstood), _form, null).Prompt(state, this, value).Prompt;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
#region Documentation
|
||||
/// <summary> Define the fields this field depends on. </summary>
|
||||
/// <param name="dependencies"> A variable-length parameters list containing dependencies. </param>
|
||||
/// <returns> A <see cref="Field{T}"/>. </returns>
|
||||
#endregion
|
||||
public Field<T> SetDependencies(params string[] dependencies)
|
||||
{
|
||||
_dependencies = dependencies;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delegate for deciding on the next form step to execute.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="Field{T}"/>.</returns>
|
||||
public Field<T> SetNext(NextDelegate<T> next)
|
||||
{
|
||||
_next = next;
|
||||
return this;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internals
|
||||
protected void DefinePrompt()
|
||||
{
|
||||
if (_promptDefinition == null)
|
||||
{
|
||||
TemplateUsage usage = TemplateUsage.None;
|
||||
if (_type == null || _type.IsEnum)
|
||||
{
|
||||
usage = _allowsMultiple ? TemplateUsage.EnumSelectMany : TemplateUsage.EnumSelectOne;
|
||||
}
|
||||
else if (_type == typeof(string))
|
||||
{
|
||||
usage = TemplateUsage.String;
|
||||
}
|
||||
else if (_type.IsIntegral())
|
||||
{
|
||||
usage = TemplateUsage.Integer;
|
||||
}
|
||||
else if (_type == typeof(bool))
|
||||
{
|
||||
usage = TemplateUsage.Bool;
|
||||
}
|
||||
else if (_type.IsDouble())
|
||||
{
|
||||
usage = TemplateUsage.Double;
|
||||
}
|
||||
else if (_type == typeof(DateTime))
|
||||
{
|
||||
usage = TemplateUsage.DateTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"{_name} is not a type FormFlow understands.");
|
||||
}
|
||||
if (usage != TemplateUsage.None)
|
||||
{
|
||||
_promptDefinition = new PromptAttribute(Template(usage));
|
||||
}
|
||||
_promptSet = false;
|
||||
}
|
||||
_promptDefinition.ApplyDefaults(_form.Configuration.DefaultPrompt);
|
||||
}
|
||||
|
||||
protected void DefineRecognizer()
|
||||
{
|
||||
if (_recognizer == null)
|
||||
{
|
||||
if (_type == null || _type.IsEnum)
|
||||
{
|
||||
_recognizer = new RecognizeEnumeration<T>(this);
|
||||
}
|
||||
else if (_type == typeof(bool))
|
||||
{
|
||||
_recognizer = new RecognizeBool<T>(this);
|
||||
}
|
||||
else if (_type == typeof(string))
|
||||
{
|
||||
_recognizer = new RecognizeString<T>(this);
|
||||
}
|
||||
else if (_type.IsIntegral())
|
||||
{
|
||||
_recognizer = new RecognizeNumber<T>(this);
|
||||
}
|
||||
else if (_type.IsDouble())
|
||||
{
|
||||
_recognizer = new RecognizeDouble<T>(this);
|
||||
}
|
||||
else if (_type == typeof(DateTime))
|
||||
{
|
||||
_recognizer = new RecognizeDateTime<T>(this);
|
||||
}
|
||||
else if (_type.IsIEnumerable())
|
||||
{
|
||||
var elt = _type.GetGenericElementType();
|
||||
if (elt.IsEnum)
|
||||
{
|
||||
_recognizer = new RecognizeEnumeration<T>(this);
|
||||
}
|
||||
}
|
||||
_buildPrompts = true;
|
||||
}
|
||||
if (_buildPrompts)
|
||||
{
|
||||
var template = Template(TemplateUsage.Help);
|
||||
_help = new Prompter<T>(template, _form, _recognizer);
|
||||
var prompt = _promptDefinition;
|
||||
_prompt = new Prompter<T>(_promptDefinition, _form, _recognizer);
|
||||
_buildPrompts = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected void SetLimits(double min, double max, bool limited)
|
||||
{
|
||||
_min = min;
|
||||
_max = max;
|
||||
_limited = limited;
|
||||
}
|
||||
|
||||
protected void AddTemplate(TemplateAttribute template)
|
||||
{
|
||||
_templates[template.Usage] = template;
|
||||
}
|
||||
|
||||
protected IForm<T> _form;
|
||||
protected string _name;
|
||||
protected FieldRole _role;
|
||||
protected ActiveDelegate<T> _condition = new ActiveDelegate<T>((state) => true);
|
||||
protected DefineAsyncDelegate<T> _define = null;
|
||||
protected ValidateAsyncDelegate<T> _validate = new ValidateAsyncDelegate<T>(async (state, value) => new ValidateResult { IsValid = true, Value = value });
|
||||
protected NextDelegate<T> _next = new NextDelegate<T>((value, state) => new NextStep());
|
||||
protected double _min, _max;
|
||||
protected bool _limited;
|
||||
protected string _pattern;
|
||||
protected string[] _dependencies = Array.Empty<string>();
|
||||
protected bool _allowsMultiple;
|
||||
protected Type _type;
|
||||
protected bool _optional;
|
||||
protected bool _isNullable;
|
||||
protected bool _keepZero;
|
||||
protected DescribeAttribute _description = new DescribeAttribute(null);
|
||||
protected TermsAttribute _terms = new TermsAttribute();
|
||||
protected Dictionary<object, DescribeAttribute> _valueDescriptions = new Dictionary<object, DescribeAttribute>();
|
||||
protected Dictionary<object, TermsAttribute> _valueTerms = new Dictionary<object, TermsAttribute>();
|
||||
protected Dictionary<TemplateUsage, TemplateAttribute> _templates = new Dictionary<TemplateUsage, TemplateAttribute>();
|
||||
protected bool _promptSet;
|
||||
protected PromptAttribute _promptDefinition;
|
||||
protected bool _buildPrompts = true;
|
||||
protected IRecognize<T> _recognizer;
|
||||
protected IPrompt<T> _help;
|
||||
protected IPrompt<T> _prompt;
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary of all fields indexed by name.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Underlying form state.</typeparam>
|
||||
public class Fields<T> : IFields<T>
|
||||
where T : class
|
||||
{
|
||||
public IField<T> Field(string name)
|
||||
{
|
||||
IField<T> field;
|
||||
_fields.TryGetValue(name, out field);
|
||||
return field;
|
||||
}
|
||||
|
||||
public void Add(IField<T> field)
|
||||
{
|
||||
_fields[field.Name] = field;
|
||||
}
|
||||
|
||||
public IEnumerator<IField<T>> GetEnumerator()
|
||||
{
|
||||
return (from entry in _fields select entry.Value).GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return (from entry in _fields select entry.Value).GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary> Mapping from field name to field definition. </summary>
|
||||
protected Dictionary<string, IField<T>> _fields = new Dictionary<string, IField<T>>();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,445 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.FormFlow.Advanced
|
||||
{
|
||||
#region Documentation
|
||||
/// <summary> Fill in field information through reflection.</summary>
|
||||
/// <remarks> The resulting information can be overridden through the fluent interface.</remarks>
|
||||
/// <typeparam name="T"> The form state. </typeparam>
|
||||
#endregion
|
||||
public class FieldReflector<T> : Field<T>
|
||||
where T : class
|
||||
{
|
||||
#region Documentation
|
||||
/// <summary> Construct an <see cref="IField{T}"/> through reflection. </summary>
|
||||
/// <param name="name"> Path to the field in your form state. </param>
|
||||
/// <param name="ignoreAnnotations"> True to ignore annotations. </param>
|
||||
#endregion
|
||||
public FieldReflector(string name, bool ignoreAnnotations = false)
|
||||
: base(name, FieldRole.Value)
|
||||
{
|
||||
_ignoreAnnotations = ignoreAnnotations;
|
||||
AddField(typeof(T), _name.Split('.'), 0);
|
||||
}
|
||||
|
||||
#region IField
|
||||
|
||||
#region IFieldState
|
||||
public override object GetValue(T state)
|
||||
{
|
||||
object current = state;
|
||||
Type ftype = null;
|
||||
foreach (var step in _path)
|
||||
{
|
||||
ftype = StepType(step);
|
||||
var field = step as FieldInfo;
|
||||
if (field != null)
|
||||
{
|
||||
current = field.GetValue(current);
|
||||
}
|
||||
else
|
||||
{
|
||||
var prop = (PropertyInfo)step;
|
||||
current = prop.GetValue(current);
|
||||
}
|
||||
if (current == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Convert value types to null if appropriate
|
||||
return (ftype.IsEnum
|
||||
? ((int)current == 0 ? null : current)
|
||||
: (ftype == typeof(DateTime) && ((DateTime)current) == DateTime.MinValue)
|
||||
? null
|
||||
: current);
|
||||
}
|
||||
|
||||
public override void SetValue(T state, object value)
|
||||
{
|
||||
object current = state;
|
||||
object lastClass = state;
|
||||
var last = _path.Last();
|
||||
foreach (var step in _path)
|
||||
{
|
||||
var field = step as FieldInfo;
|
||||
var prop = step as PropertyInfo;
|
||||
Type ftype = StepType(step);
|
||||
if (step == last)
|
||||
{
|
||||
object newValue = value;
|
||||
var utype = Nullable.GetUnderlyingType(ftype) ?? ftype;
|
||||
if (ftype.IsIEnumerable())
|
||||
{
|
||||
if (value != null && ftype != typeof(string))
|
||||
{
|
||||
// Build list and coerce elements
|
||||
var list = Activator.CreateInstance(ftype);
|
||||
var addMethod = list.GetType().GetMethod("Add");
|
||||
foreach (var elt in (System.Collections.IEnumerable)value)
|
||||
{
|
||||
addMethod.Invoke(list, new object[] { elt });
|
||||
}
|
||||
newValue = list;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
if (!ftype.IsNullable() && (ftype.IsEnum || ftype.IsIntegral() || ftype.IsDouble()))
|
||||
{
|
||||
// Null value for non-nullable numbers and enums is 0
|
||||
newValue = 0;
|
||||
}
|
||||
}
|
||||
else if (utype.IsIntegral())
|
||||
{
|
||||
newValue = Convert.ChangeType(value, utype);
|
||||
}
|
||||
else if (utype.IsDouble())
|
||||
{
|
||||
newValue = Convert.ChangeType(value, utype);
|
||||
}
|
||||
else if (utype == typeof(bool))
|
||||
{
|
||||
newValue = Convert.ChangeType(value, utype);
|
||||
}
|
||||
}
|
||||
if (field != null)
|
||||
{
|
||||
field.SetValue(lastClass, newValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
prop.SetValue(lastClass, newValue);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
current = (field == null ? prop.GetValue(current) : field.GetValue(current));
|
||||
if (current == null)
|
||||
{
|
||||
var obj = Activator.CreateInstance(ftype);
|
||||
current = obj;
|
||||
if (field != null)
|
||||
{
|
||||
field.SetValue(lastClass, current);
|
||||
}
|
||||
else
|
||||
{
|
||||
prop.SetValue(lastClass, current);
|
||||
}
|
||||
}
|
||||
lastClass = current;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsUnknown(T state)
|
||||
{
|
||||
var unknown = false;
|
||||
var value = GetValue(state);
|
||||
if (value == null)
|
||||
{
|
||||
unknown = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var step = _path.Last();
|
||||
var ftype = StepType(step);
|
||||
if (ftype.IsValueType && ftype.IsEnum)
|
||||
{
|
||||
unknown = ((int)value == 0);
|
||||
}
|
||||
else if (ftype == typeof(DateTime))
|
||||
{
|
||||
unknown = ((DateTime)value) == default(DateTime);
|
||||
}
|
||||
else if (ftype.IsIEnumerable())
|
||||
{
|
||||
unknown = !((System.Collections.IEnumerable)value).GetEnumerator().MoveNext();
|
||||
}
|
||||
}
|
||||
return unknown;
|
||||
}
|
||||
|
||||
public override void SetUnknown(T state)
|
||||
{
|
||||
var step = _path.Last();
|
||||
var field = step as FieldInfo;
|
||||
var prop = step as PropertyInfo;
|
||||
var ftype = StepType(step);
|
||||
if (ftype.IsEnum)
|
||||
{
|
||||
SetValue(state, 0);
|
||||
}
|
||||
else if (ftype == typeof(DateTime))
|
||||
{
|
||||
SetValue(state, default(DateTime));
|
||||
}
|
||||
else
|
||||
{
|
||||
SetValue(state, null);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region Internals
|
||||
protected Type StepType(object step)
|
||||
{
|
||||
var field = step as FieldInfo;
|
||||
var prop = step as PropertyInfo;
|
||||
return (step == null ? null : (field == null ? prop.PropertyType : field.FieldType));
|
||||
}
|
||||
|
||||
protected void AddField(Type type, string[] path, int ipath)
|
||||
{
|
||||
if (ipath < path.Length)
|
||||
{
|
||||
ProcessTemplates(type);
|
||||
var step = path[ipath];
|
||||
object field = type.GetField(step, BindingFlags.Public | BindingFlags.Instance);
|
||||
Type ftype;
|
||||
if (field == null)
|
||||
{
|
||||
var prop = type.GetProperty(step, BindingFlags.Public | BindingFlags.Instance);
|
||||
if (prop == null)
|
||||
{
|
||||
throw new MissingFieldException($"{step} is not a field or property in your type.");
|
||||
}
|
||||
field = prop;
|
||||
ftype = prop.PropertyType;
|
||||
_path.Add(prop);
|
||||
}
|
||||
else
|
||||
{
|
||||
ftype = (field as FieldInfo).FieldType;
|
||||
_path.Add(field);
|
||||
}
|
||||
if (ftype.IsNullable())
|
||||
{
|
||||
_isNullable = true;
|
||||
_keepZero = true;
|
||||
ftype = Nullable.GetUnderlyingType(ftype);
|
||||
}
|
||||
else if (ftype.IsEnum || ftype.IsClass)
|
||||
{
|
||||
_isNullable = true;
|
||||
}
|
||||
if (ftype.IsClass)
|
||||
{
|
||||
if (ftype == typeof(string))
|
||||
{
|
||||
_type = ftype;
|
||||
ProcessFieldAttributes(field);
|
||||
}
|
||||
else if (ftype.IsIEnumerable())
|
||||
{
|
||||
var elt = ftype.GetGenericElementType();
|
||||
_type = elt;
|
||||
_allowsMultiple = true;
|
||||
ProcessFieldAttributes(field);
|
||||
if (elt.IsEnum)
|
||||
{
|
||||
ProcessEnumAttributes(elt);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddField(ftype, path, ipath + 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ftype.IsEnum)
|
||||
{
|
||||
ProcessFieldAttributes(field);
|
||||
ProcessEnumAttributes(ftype);
|
||||
}
|
||||
else if (ftype == typeof(bool))
|
||||
{
|
||||
ProcessFieldAttributes(field);
|
||||
}
|
||||
else if (ftype.IsIntegral())
|
||||
{
|
||||
long min = long.MinValue;
|
||||
long max = long.MaxValue;
|
||||
if (ftype == typeof(sbyte)) { min = sbyte.MinValue; max = sbyte.MaxValue; }
|
||||
else if (ftype == typeof(byte)) { min = byte.MinValue; max = byte.MaxValue; }
|
||||
else if (ftype == typeof(short)) { min = short.MinValue; max = short.MaxValue; }
|
||||
else if (ftype == typeof(ushort)) { min = ushort.MinValue; max = ushort.MaxValue; }
|
||||
else if (ftype == typeof(int)) { min = int.MinValue; max = int.MaxValue; }
|
||||
else if (ftype == typeof(uint)) { min = uint.MinValue; max = uint.MaxValue; }
|
||||
else if (ftype == typeof(long)) { min = long.MinValue; max = long.MaxValue; }
|
||||
else if (ftype == typeof(ulong)) { min = long.MinValue; max = long.MaxValue; }
|
||||
SetLimits(min, max, false);
|
||||
ProcessFieldAttributes(field);
|
||||
}
|
||||
else if (ftype.IsDouble())
|
||||
{
|
||||
double min = long.MinValue;
|
||||
double max = long.MaxValue;
|
||||
if (ftype == typeof(float)) { min = float.MinValue; max = float.MaxValue; }
|
||||
else if (ftype == typeof(double)) { min = double.MinValue; max = double.MaxValue; }
|
||||
SetLimits(min, max, false);
|
||||
ProcessFieldAttributes(field);
|
||||
}
|
||||
else if (ftype == typeof(DateTime))
|
||||
{
|
||||
ProcessFieldAttributes(field);
|
||||
}
|
||||
_type = ftype;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void ProcessTemplates(Type type)
|
||||
{
|
||||
if (!_ignoreAnnotations)
|
||||
{
|
||||
foreach (var attribute in type.GetCustomAttributes(typeof(TemplateAttribute)))
|
||||
{
|
||||
AddTemplate((TemplateAttribute)attribute);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void ProcessFieldAttributes(object step)
|
||||
{
|
||||
_optional = false;
|
||||
if (!_ignoreAnnotations)
|
||||
{
|
||||
var field = step as FieldInfo;
|
||||
var prop = step as PropertyInfo;
|
||||
var name = (field == null ? prop.Name : field.Name);
|
||||
var describe = (field == null ? prop.GetCustomAttribute<DescribeAttribute>() : field.GetCustomAttribute<DescribeAttribute>());
|
||||
var terms = (field == null ? prop.GetCustomAttribute<TermsAttribute>() : field.GetCustomAttribute<TermsAttribute>());
|
||||
var prompt = (field == null ? prop.GetCustomAttribute<PromptAttribute>() : field.GetCustomAttribute<PromptAttribute>());
|
||||
var optional = (field == null ? prop.GetCustomAttribute<OptionalAttribute>() : field.GetCustomAttribute<OptionalAttribute>());
|
||||
var numeric = (field == null ? prop.GetCustomAttribute<NumericAttribute>() : field.GetCustomAttribute<NumericAttribute>());
|
||||
var pattern = (field == null ? prop.GetCustomAttribute<PatternAttribute>() : field.GetCustomAttribute<PatternAttribute>());
|
||||
if (describe != null)
|
||||
{
|
||||
_description = describe;
|
||||
}
|
||||
else
|
||||
{
|
||||
_description = new DescribeAttribute(Language.CamelCase(name));
|
||||
}
|
||||
|
||||
if (terms != null)
|
||||
{
|
||||
_terms = terms;
|
||||
}
|
||||
else
|
||||
{
|
||||
_terms = new TermsAttribute(Language.GenerateTerms((string.IsNullOrWhiteSpace(_description.Description) ? Language.CamelCase(name) : _description.Description), 3));
|
||||
}
|
||||
|
||||
if (prompt != null)
|
||||
{
|
||||
SetPrompt(prompt);
|
||||
}
|
||||
|
||||
if (numeric != null)
|
||||
{
|
||||
double oldMin, oldMax;
|
||||
Limits(out oldMin, out oldMax);
|
||||
SetLimits(numeric.Min, numeric.Max, numeric.Min != oldMin || numeric.Max != oldMax);
|
||||
}
|
||||
|
||||
if (pattern != null)
|
||||
{
|
||||
SetPattern(pattern.Pattern);
|
||||
}
|
||||
|
||||
_optional = (optional != null);
|
||||
|
||||
foreach (var attribute in (field == null ? prop.GetCustomAttributes<TemplateAttribute>() : field.GetCustomAttributes<TemplateAttribute>()))
|
||||
{
|
||||
var template = (TemplateAttribute)attribute;
|
||||
AddTemplate(template);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void ProcessEnumAttributes(Type type)
|
||||
{
|
||||
foreach (var enumField in type.GetFields(BindingFlags.Static | BindingFlags.Public))
|
||||
{
|
||||
var enumValue = enumField.GetValue(null);
|
||||
if (_keepZero || (int)enumValue > 0)
|
||||
{
|
||||
var describe = enumField.GetCustomAttribute<DescribeAttribute>();
|
||||
var terms = enumField.GetCustomAttribute<TermsAttribute>();
|
||||
if (describe != null && !_ignoreAnnotations)
|
||||
{
|
||||
if (describe.Description == null)
|
||||
{
|
||||
describe.Description = Language.CamelCase(enumValue.ToString());
|
||||
}
|
||||
_valueDescriptions.Add(enumValue, describe);
|
||||
}
|
||||
else
|
||||
{
|
||||
_valueDescriptions.Add(enumValue, new DescribeAttribute(Language.CamelCase(enumValue.ToString())));
|
||||
}
|
||||
|
||||
if (terms != null && !_ignoreAnnotations)
|
||||
{
|
||||
_valueTerms.Add(enumValue, terms);
|
||||
}
|
||||
else
|
||||
{
|
||||
_valueTerms.Add(enumValue, new TermsAttribute(Language.GenerateTerms(Language.CamelCase(enumValue.ToString()), 4)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> True to ignore annotations. </summary>
|
||||
protected bool _ignoreAnnotations;
|
||||
|
||||
/// <summary> Path to field value in state. </summary>
|
||||
protected List<object> _path = new List<object>();
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,523 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using Microsoft.Bot.Builder.V3Bridge.FormFlow.Advanced;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Resource;
|
||||
using Microsoft.Bot.Schema;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Resources;
|
||||
using System.Threading;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Dialogs;
|
||||
using System.Threading.Tasks;
|
||||
using System.Text;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Resource;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.FormFlow
|
||||
{
|
||||
#region Documentation
|
||||
/// <summary>Abstract base class for Form Builders.</summary>
|
||||
/// <typeparam name="T">Form state class. </typeparam>
|
||||
#endregion
|
||||
public abstract class FormBuilderBase<T> : IFormBuilder<T>
|
||||
where T : class
|
||||
{
|
||||
public virtual IForm<T> Build(Assembly resourceAssembly = null, string resourceName = null)
|
||||
{
|
||||
if (resourceAssembly == null)
|
||||
{
|
||||
resourceAssembly = typeof(T).Assembly;
|
||||
}
|
||||
if (resourceName == null)
|
||||
{
|
||||
resourceName = typeof(T).FullName;
|
||||
}
|
||||
if (this._form._prompter == null)
|
||||
{
|
||||
this._form._prompter = async (context, prompt, state, field) =>
|
||||
{
|
||||
var preamble = context.MakeMessage();
|
||||
var promptMessage = context.MakeMessage();
|
||||
if (prompt.GenerateMessages(preamble, promptMessage))
|
||||
{
|
||||
await context.PostAsync(preamble);
|
||||
}
|
||||
await context.PostAsync(promptMessage);
|
||||
return prompt;
|
||||
};
|
||||
}
|
||||
var lang = resourceAssembly.GetCustomAttribute<NeutralResourcesLanguageAttribute>();
|
||||
if (lang != null && !string.IsNullOrWhiteSpace(lang.CultureName))
|
||||
{
|
||||
IEnumerable<string> missing, extra;
|
||||
string name = null;
|
||||
foreach (var resource in resourceAssembly.GetManifestResourceNames())
|
||||
{
|
||||
if (resource.Contains(resourceName))
|
||||
{
|
||||
var pieces = resource.Split('.');
|
||||
name = string.Join(".", pieces.Take(pieces.Count() - 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (name != null)
|
||||
{
|
||||
var rm = new ResourceManager(name, resourceAssembly);
|
||||
var rs = rm.GetResourceSet(Thread.CurrentThread.CurrentUICulture, true, true);
|
||||
_form.Localize(rs.GetEnumerator(), out missing, out extra);
|
||||
if (missing.Any())
|
||||
{
|
||||
throw new MissingManifestResourceException($"Missing resources {missing}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Validate();
|
||||
return this._form;
|
||||
}
|
||||
|
||||
public FormConfiguration Configuration { get { return _form.Configuration; } }
|
||||
|
||||
public bool HasField(string name)
|
||||
{
|
||||
return _form.Fields.Field(name) != null;
|
||||
}
|
||||
|
||||
public virtual IFormBuilder<T> Message(string message, ActiveDelegate<T> condition = null, IEnumerable<string> dependencies = null)
|
||||
{
|
||||
_form._steps.Add(new MessageStep<T>(new PromptAttribute(message), condition, dependencies, _form));
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual IFormBuilder<T> Message(PromptAttribute prompt, ActiveDelegate<T> condition = null, IEnumerable<string> dependencies = null)
|
||||
{
|
||||
_form._steps.Add(new MessageStep<T>(prompt, condition, dependencies, _form));
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual IFormBuilder<T> Message(MessageDelegate<T> generateMessage, ActiveDelegate<T> condition = null, IEnumerable<string> dependencies = null)
|
||||
{
|
||||
_form._steps.Add(new MessageStep<T>(generateMessage, condition, dependencies, _form));
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual IFormBuilder<T> Field(IField<T> field)
|
||||
{
|
||||
return AddField(field);
|
||||
}
|
||||
|
||||
public virtual IFormBuilder<T> Confirm(string prompt, ActiveDelegate<T> condition = null, IEnumerable<string> dependencies = null)
|
||||
{
|
||||
return Confirm(new PromptAttribute(prompt) { ChoiceFormat = Resources.ConfirmChoiceFormat, AllowDefault = BoolDefault.False }, condition, dependencies);
|
||||
}
|
||||
|
||||
public virtual IFormBuilder<T> Confirm(PromptAttribute prompt, ActiveDelegate<T> condition = null, IEnumerable<string> dependencies = null)
|
||||
{
|
||||
if (condition == null) condition = state => true;
|
||||
dependencies = dependencies ?? _form.Dependencies(_form.Steps.Count());
|
||||
var confirmation = new Confirmation<T>(prompt, condition, dependencies, _form);
|
||||
confirmation.Form = _form;
|
||||
_form._fields.Add(confirmation);
|
||||
_form._steps.Add(new ConfirmStep<T>(confirmation));
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual IFormBuilder<T> Confirm(MessageDelegate<T> generateMessage, ActiveDelegate<T> condition = null, IEnumerable<string> dependencies = null)
|
||||
{
|
||||
if (condition == null) condition = state => true;
|
||||
dependencies = dependencies ?? _form.Dependencies(_form.Steps.Count());
|
||||
var confirmation = new Confirmation<T>(generateMessage, condition, dependencies, _form);
|
||||
confirmation.Form = _form;
|
||||
_form._fields.Add(confirmation);
|
||||
_form._steps.Add(new ConfirmStep<T>(confirmation));
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual IFormBuilder<T> OnCompletion(OnCompletionAsyncDelegate<T> callback)
|
||||
{
|
||||
_form._completion = callback;
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual IFormBuilder<T> Prompter(PromptAsyncDelegate<T> prompter)
|
||||
{
|
||||
_form._prompter = prompter;
|
||||
return this;
|
||||
}
|
||||
|
||||
public abstract IFormBuilder<T> Field(string name, ActiveDelegate<T> active = null, ValidateAsyncDelegate<T> validate = null);
|
||||
public abstract IFormBuilder<T> Field(string name, string prompt, ActiveDelegate<T> active = null, ValidateAsyncDelegate<T> validate = null);
|
||||
public abstract IFormBuilder<T> Field(string name, PromptAttribute prompt, ActiveDelegate<T> active = null, ValidateAsyncDelegate<T> validate = null);
|
||||
public abstract IFormBuilder<T> AddRemainingFields(IEnumerable<string> exclude = null);
|
||||
|
||||
private Dictionary<TemplateUsage, int> _templateArgs = new Dictionary<TemplateUsage, int>
|
||||
{
|
||||
{TemplateUsage.Bool, 0 },
|
||||
{ TemplateUsage.BoolHelp, 1},
|
||||
{ TemplateUsage.Clarify, 1},
|
||||
{ TemplateUsage.Confirmation, 0 },
|
||||
{ TemplateUsage.CurrentChoice, 0},
|
||||
{ TemplateUsage.DateTime, 0},
|
||||
{ TemplateUsage.DateTimeHelp, 2},
|
||||
{ TemplateUsage.Double, 2},
|
||||
{ TemplateUsage.DoubleHelp, 4},
|
||||
{ TemplateUsage.EnumManyNumberHelp, 3},
|
||||
{ TemplateUsage.EnumOneNumberHelp, 3},
|
||||
{ TemplateUsage.EnumManyWordHelp, 3},
|
||||
{ TemplateUsage.EnumOneWordHelp, 3},
|
||||
{ TemplateUsage.EnumSelectOne, 0},
|
||||
{ TemplateUsage.EnumSelectMany, 0},
|
||||
{ TemplateUsage.Feedback, 1},
|
||||
{ TemplateUsage.Help, 2},
|
||||
{ TemplateUsage.HelpClarify, 2},
|
||||
{ TemplateUsage.HelpConfirm, 2},
|
||||
{ TemplateUsage.HelpNavigation, 2},
|
||||
{ TemplateUsage.Integer, 2},
|
||||
{ TemplateUsage.IntegerHelp, 4},
|
||||
{ TemplateUsage.Navigation, 0},
|
||||
{ TemplateUsage.NavigationCommandHelp, 1},
|
||||
{ TemplateUsage.NavigationFormat, 0},
|
||||
{ TemplateUsage.NavigationHelp, 2},
|
||||
{ TemplateUsage.NoPreference, 0},
|
||||
{ TemplateUsage.NotUnderstood, 1},
|
||||
{ TemplateUsage.StatusFormat, 0},
|
||||
{ TemplateUsage.String, 0},
|
||||
{ TemplateUsage.StringHelp, 2},
|
||||
{ TemplateUsage.Unspecified, 0},
|
||||
};
|
||||
|
||||
private int TemplateArgs(TemplateUsage usage)
|
||||
{
|
||||
int args;
|
||||
if (!_templateArgs.TryGetValue(usage, out args))
|
||||
{
|
||||
throw new ArgumentException("Missing template usage for validation");
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
private void Validate()
|
||||
{
|
||||
foreach (var step in _form._steps)
|
||||
{
|
||||
// Validate prompt
|
||||
var annotation = step.Annotation;
|
||||
if (annotation != null)
|
||||
{
|
||||
var name = step.Type == StepType.Field ? step.Name : "";
|
||||
foreach (var pattern in annotation.Patterns)
|
||||
{
|
||||
ValidatePattern(pattern, _form.Fields.Field(name), 5);
|
||||
}
|
||||
if (step.Type != StepType.Message)
|
||||
{
|
||||
foreach (TemplateUsage usage in Enum.GetValues(typeof(TemplateUsage)))
|
||||
{
|
||||
if (usage != TemplateUsage.None)
|
||||
{
|
||||
foreach (var pattern in step.Field.Template(usage).Patterns)
|
||||
{
|
||||
ValidatePattern(pattern, _form.Fields.Field(name), TemplateArgs(usage));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ValidatePattern(_form.Configuration.DefaultPrompt.ChoiceFormat, null, 2);
|
||||
}
|
||||
|
||||
private void ValidatePattern(string pattern, IField<T> field, int maxArgs)
|
||||
{
|
||||
if (!Prompter<T>.ValidatePattern(_form, pattern, field, maxArgs))
|
||||
{
|
||||
throw new ArgumentException(string.Format("Illegal pattern: \"{0}\"", pattern));
|
||||
}
|
||||
}
|
||||
|
||||
private IFormBuilder<T> AddField(IField<T> field)
|
||||
{
|
||||
field.Form = _form;
|
||||
_form._fields.Add(field);
|
||||
var step = new FieldStep<T>(field.Name, _form);
|
||||
var stepIndex = this._form._steps.FindIndex(s => s.Name == field.Name);
|
||||
if (stepIndex >= 0)
|
||||
{
|
||||
_form._steps[stepIndex] = step;
|
||||
}
|
||||
else
|
||||
{
|
||||
_form._steps.Add(step);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
protected internal sealed class Form : IForm<T>
|
||||
{
|
||||
internal readonly FormConfiguration _configuration = new FormConfiguration();
|
||||
internal readonly Fields<T> _fields = new Fields<T>();
|
||||
internal readonly List<IStep<T>> _steps = new List<IStep<T>>();
|
||||
internal OnCompletionAsyncDelegate<T> _completion = null;
|
||||
internal PromptAsyncDelegate<T> _prompter = null;
|
||||
internal ILocalizer _resources = new Localizer() { Culture = CultureInfo.CurrentUICulture };
|
||||
|
||||
public Form()
|
||||
{
|
||||
}
|
||||
|
||||
internal override ILocalizer Resources
|
||||
{
|
||||
get
|
||||
{
|
||||
return _resources;
|
||||
}
|
||||
}
|
||||
|
||||
public override void SaveResources(IResourceWriter writer)
|
||||
{
|
||||
_resources = new Localizer() { Culture = CultureInfo.CurrentUICulture };
|
||||
foreach (var step in _steps)
|
||||
{
|
||||
step.SaveResources();
|
||||
}
|
||||
_resources.Save(writer);
|
||||
}
|
||||
|
||||
public override void Localize(IDictionaryEnumerator reader, out IEnumerable<string> missing, out IEnumerable<string> extra)
|
||||
{
|
||||
foreach (var step in _steps)
|
||||
{
|
||||
step.SaveResources();
|
||||
}
|
||||
_resources = _resources.Load(reader, out missing, out extra);
|
||||
foreach (var step in _steps)
|
||||
{
|
||||
step.Localize();
|
||||
}
|
||||
}
|
||||
|
||||
internal override FormConfiguration Configuration
|
||||
{
|
||||
get
|
||||
{
|
||||
return _configuration;
|
||||
}
|
||||
}
|
||||
|
||||
internal override IReadOnlyList<IStep<T>> Steps
|
||||
{
|
||||
get
|
||||
{
|
||||
return _steps;
|
||||
}
|
||||
}
|
||||
|
||||
internal override async Task<FormPrompt> Prompt(IDialogContext context, FormPrompt prompt, T state, IField<T> field)
|
||||
{
|
||||
return prompt == null ? prompt : await _prompter(context, prompt, state, field);
|
||||
}
|
||||
|
||||
internal override OnCompletionAsyncDelegate<T> Completion
|
||||
{
|
||||
get
|
||||
{
|
||||
return _completion;
|
||||
}
|
||||
}
|
||||
|
||||
public override IFields<T> Fields
|
||||
{
|
||||
get
|
||||
{
|
||||
return _fields;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected internal Form _form = new Form();
|
||||
}
|
||||
|
||||
#region Documentation
|
||||
/// <summary> Build a form by specifying messages, fields and confirmations via reflection or programatically.</summary>
|
||||
/// <typeparam name="T">Form state class. </typeparam>
|
||||
/// <remarks>
|
||||
/// Fields will be defined through reflection over the type <typeparamref name="T"/> and attributes like
|
||||
/// <see cref="DescribeAttribute"/>,
|
||||
/// <see cref="NumericAttribute"/>,
|
||||
/// <see cref="OptionalAttribute"/>
|
||||
/// <see cref="PatternAttribute"/>,
|
||||
/// <see cref="PromptAttribute"/>,
|
||||
/// <see cref="TermsAttribute"/> and
|
||||
/// <see cref="TemplateAttribute"/>.
|
||||
/// For all of the attributes, reasonable defaults will be generated.
|
||||
/// </remarks>
|
||||
#endregion
|
||||
public sealed class FormBuilder<T> : FormBuilderBase<T>
|
||||
where T : class
|
||||
{
|
||||
private readonly bool _ignoreAnnotations;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new form builder for building a form using reflection.
|
||||
/// </summary>
|
||||
/// <param name="ignoreAnnotations">True to ignore any attributes on the form class.</param>
|
||||
public FormBuilder(bool ignoreAnnotations = false)
|
||||
: base()
|
||||
{
|
||||
_ignoreAnnotations = ignoreAnnotations;
|
||||
}
|
||||
|
||||
public override IForm<T> Build(Assembly resourceAssembly = null, string resourceName = null)
|
||||
{
|
||||
if (!_form._steps.Any((step) => step.Type == StepType.Field))
|
||||
{
|
||||
var paths = new List<string>();
|
||||
FieldPaths(typeof(T), "", paths);
|
||||
foreach (var path in paths)
|
||||
{
|
||||
Field(new FieldReflector<T>(path, _ignoreAnnotations));
|
||||
}
|
||||
Confirm(new PromptAttribute(_form.Configuration.Template(TemplateUsage.Confirmation)));
|
||||
}
|
||||
return base.Build(resourceAssembly, resourceName);
|
||||
}
|
||||
|
||||
public override IFormBuilder<T> Field(string name, ActiveDelegate<T> active = null, ValidateAsyncDelegate<T> validate = null)
|
||||
{
|
||||
var field = new FieldReflector<T>(name, _ignoreAnnotations);
|
||||
field.SetActive(active);
|
||||
field.SetValidate(validate);
|
||||
return Field(field);
|
||||
}
|
||||
|
||||
public override IFormBuilder<T> Field(string name, string prompt, ActiveDelegate<T> active = null, ValidateAsyncDelegate<T> validate = null)
|
||||
{
|
||||
return Field(name, new PromptAttribute(prompt), active, validate);
|
||||
}
|
||||
|
||||
public override IFormBuilder<T> Field(string name, PromptAttribute prompt, ActiveDelegate<T> active = null, ValidateAsyncDelegate<T> validate = null)
|
||||
{
|
||||
var field = new FieldReflector<T>(name, _ignoreAnnotations);
|
||||
field.SetActive(active);
|
||||
field.SetValidate(validate);
|
||||
field.SetPrompt(prompt);
|
||||
return Field(field);
|
||||
}
|
||||
|
||||
public override IFormBuilder<T> AddRemainingFields(IEnumerable<string> exclude = null)
|
||||
{
|
||||
var exclusions = (exclude == null ? Array.Empty<string>() : exclude.ToArray());
|
||||
var paths = new List<string>();
|
||||
FieldPaths(typeof(T), "", paths);
|
||||
foreach (var path in paths)
|
||||
{
|
||||
if (!exclusions.Contains(path) && !HasField(path))
|
||||
{
|
||||
Field(new FieldReflector<T>(path, _ignoreAnnotations));
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private void FieldPaths(Type type, string path, List<string> paths)
|
||||
{
|
||||
var newPath = (path == "" ? path : path + ".");
|
||||
foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Instance).Where(f => !f.IsDefined(typeof(IgnoreFieldAttribute))))
|
||||
{
|
||||
TypePaths(field.FieldType, newPath + field.Name, paths);
|
||||
}
|
||||
|
||||
foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
|
||||
{
|
||||
if (property.CanRead && property.CanWrite)
|
||||
{
|
||||
TypePaths(property.PropertyType, newPath + property.Name, paths);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TypePaths(Type type, string path, List<string> paths)
|
||||
{
|
||||
if (type.IsClass)
|
||||
{
|
||||
if (type == typeof(string))
|
||||
{
|
||||
paths.Add(path);
|
||||
}
|
||||
else if (type.IsIEnumerable())
|
||||
{
|
||||
var elt = type.GetGenericElementType();
|
||||
if (elt.IsEnum)
|
||||
{
|
||||
paths.Add(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: What to do about enumerations of things other than enums?
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
FieldPaths(type, path, paths);
|
||||
}
|
||||
}
|
||||
else if (type.IsEnum)
|
||||
{
|
||||
paths.Add(path);
|
||||
}
|
||||
else if (type == typeof(bool))
|
||||
{
|
||||
paths.Add(path);
|
||||
}
|
||||
else if (type.IsIntegral())
|
||||
{
|
||||
paths.Add(path);
|
||||
}
|
||||
else if (type.IsDouble())
|
||||
{
|
||||
paths.Add(path);
|
||||
}
|
||||
else if (type.IsNullable() && type.IsValueType)
|
||||
{
|
||||
paths.Add(path);
|
||||
}
|
||||
else if (type == typeof(DateTime))
|
||||
{
|
||||
paths.Add(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,851 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.Bot.Builder.V3Bridge.Dialogs;
|
||||
using Microsoft.Bot.Builder.V3Bridge.FormFlow.Advanced;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Internals.Fibers;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Luis.Models;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.FormFlow
|
||||
{
|
||||
/// <summary>
|
||||
/// Static factory methods for creating form dialogs.
|
||||
/// </summary>
|
||||
public static class FormDialog
|
||||
{
|
||||
/// <summary>
|
||||
/// Create an <see cref="IFormDialog{T}"/> using the default <see cref="BuildFormDelegate{T}"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The form type.</typeparam>
|
||||
/// <param name="options">The form options.</param>
|
||||
/// <returns>The form dialog.</returns>
|
||||
public static IFormDialog<T> FromType<T>(FormOptions options = FormOptions.None) where T : class, new()
|
||||
{
|
||||
return new FormDialog<T>(new T(), null, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an <see cref="IFormDialog{T}"/> using the <see cref="BuildFormDelegate{T}"/> parameter.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The form type.</typeparam>
|
||||
/// <param name="buildForm">The delegate to build the form.</param>
|
||||
/// <param name="options">The form options.</param>
|
||||
/// <returns>The form dialog.</returns>
|
||||
public static IFormDialog<T> FromForm<T>(BuildFormDelegate<T> buildForm, FormOptions options = FormOptions.None) where T : class, new()
|
||||
{
|
||||
return new FormDialog<T>(new T(), buildForm, options);
|
||||
}
|
||||
|
||||
|
||||
#region IForm<T> statics
|
||||
#if DEBUG
|
||||
internal static bool DebugRecognizers = false;
|
||||
#endif
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Options for form execution.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum FormOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// No options.
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// Prompt when the dialog starts.
|
||||
/// </summary>
|
||||
PromptInStart,
|
||||
|
||||
/// <summary>
|
||||
/// Prompt for fields that already have a value in the initial state when processing form.
|
||||
/// </summary>
|
||||
PromptFieldsWithValues
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Delegate for building the form.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The form state type.</typeparam>
|
||||
/// <returns>An <see cref="IForm{T}"/>.</returns>
|
||||
/// <remarks>This is a delegate so that we can rebuild the form and don't have to serialize
|
||||
/// the form definition with every message.</remarks>
|
||||
public delegate IForm<T> BuildFormDelegate<T>() where T : class;
|
||||
|
||||
/// <summary>
|
||||
/// Form dialog to fill in your state.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type to fill in.</typeparam>
|
||||
/// <remarks>
|
||||
/// This is the root class for managing a FormFlow dialog. It is usually created
|
||||
/// through the factory methods <see cref="FormDialog.FromForm{T}(BuildFormDelegate{T}, FormOptions)"/>
|
||||
/// or <see cref="FormDialog.FromType{T}"/>.
|
||||
/// </remarks>
|
||||
[Serializable]
|
||||
public sealed class FormDialog<T> : IFormDialog<T>, ISerializable
|
||||
where T : class
|
||||
{
|
||||
// constructor arguments
|
||||
private readonly T _state;
|
||||
private readonly BuildFormDelegate<T> _buildForm;
|
||||
private readonly IEnumerable<EntityRecommendation> _entities;
|
||||
private readonly FormOptions _options;
|
||||
|
||||
// instantiated in constructor, saved when serialized
|
||||
private readonly FormState _formState;
|
||||
|
||||
// instantiated in constructor, re-instantiated when deserialized
|
||||
private readonly IForm<T> _form;
|
||||
private readonly IField<T> _commands;
|
||||
|
||||
internal T State => _state;
|
||||
|
||||
private static IForm<T> BuildDefaultForm()
|
||||
{
|
||||
return new FormBuilder<T>().AddRemainingFields().Build();
|
||||
}
|
||||
|
||||
#region Documentation
|
||||
/// <summary> Constructor for creating a FormFlow dialog. </summary>
|
||||
/// <param name="state"> The initial state. </param>
|
||||
/// <param name="buildForm"> A delegate for building the form. </param>
|
||||
/// <param name="options"> Options for controlling the form. </param>
|
||||
/// <param name="entities"> Optional entities to process into the form. </param>
|
||||
/// <param name="cultureInfo"> The culture to use. </param>
|
||||
/// <remarks>For building forms <see cref="IFormBuilder{T}"/>.</remarks>
|
||||
#endregion
|
||||
public FormDialog(T state, BuildFormDelegate<T> buildForm = null, FormOptions options = FormOptions.None, IEnumerable<EntityRecommendation> entities = null, CultureInfo cultureInfo = null)
|
||||
{
|
||||
buildForm = buildForm ?? BuildDefaultForm;
|
||||
entities = entities ?? Enumerable.Empty<EntityRecommendation>();
|
||||
if (cultureInfo != null)
|
||||
{
|
||||
CultureInfo.CurrentUICulture = cultureInfo;
|
||||
CultureInfo.CurrentCulture = cultureInfo;
|
||||
}
|
||||
|
||||
// constructor arguments
|
||||
SetField.NotNull(out this._state, nameof(state), state);
|
||||
SetField.NotNull(out this._buildForm, nameof(buildForm), buildForm);
|
||||
SetField.NotNull(out this._entities, nameof(entities), entities);
|
||||
this._options = options;
|
||||
|
||||
// make our form
|
||||
var form = _buildForm();
|
||||
|
||||
// instantiated in constructor, saved when serialized
|
||||
this._formState = new FormState(form.Steps.Count);
|
||||
|
||||
// instantiated in constructor, re-instantiated when deserialized
|
||||
this._form = form;
|
||||
this._commands = this._form.BuildCommandRecognizer();
|
||||
}
|
||||
|
||||
private FormDialog(SerializationInfo info, StreamingContext context)
|
||||
{
|
||||
// constructor arguments
|
||||
SetField.NotNullFrom(out this._state, nameof(this._state), info);
|
||||
SetField.NotNullFrom(out this._buildForm, nameof(this._buildForm), info);
|
||||
SetField.NotNullFrom(out this._entities, nameof(this._entities), info);
|
||||
this._options = info.GetValue<FormOptions>(nameof(this._options));
|
||||
|
||||
// instantiated in constructor, saved when serialized
|
||||
SetField.NotNullFrom(out this._formState, nameof(this._formState), info);
|
||||
|
||||
// instantiated in constructor, re-instantiated when deserialized
|
||||
this._form = _buildForm();
|
||||
this._commands = this._form.BuildCommandRecognizer();
|
||||
}
|
||||
|
||||
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
|
||||
{
|
||||
// constructor arguments
|
||||
info.AddValue(nameof(this._state), this._state);
|
||||
info.AddValue(nameof(this._buildForm), this._buildForm);
|
||||
info.AddValue(nameof(this._entities), this._entities);
|
||||
info.AddValue(nameof(this._options), this._options);
|
||||
|
||||
// instantiated in constructor, saved when serialized
|
||||
info.AddValue(nameof(this._formState), this._formState);
|
||||
}
|
||||
|
||||
#region IFormDialog<T> implementation
|
||||
|
||||
IForm<T> IFormDialog<T>.Form { get { return this._form; } }
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDialog implementation
|
||||
async Task IDialog<T>.StartAsync(IDialogContext context)
|
||||
{
|
||||
if (this._entities.Any())
|
||||
{
|
||||
var inputs = new List<Tuple<int, string>>();
|
||||
var entityGroups = (from entity in this._entities group entity by entity.Role ?? entity.Type);
|
||||
foreach (var entityGroup in entityGroups)
|
||||
{
|
||||
var step = _form.Step(entityGroup.Key);
|
||||
if (step != null)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
var first = true;
|
||||
foreach (var entity in entityGroup)
|
||||
{
|
||||
if (first)
|
||||
{
|
||||
first = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Append(' ');
|
||||
}
|
||||
builder.Append(entity.Entity);
|
||||
}
|
||||
inputs.Add(Tuple.Create(_form.StepIndex(step), builder.ToString()));
|
||||
}
|
||||
}
|
||||
if (inputs.Any())
|
||||
{
|
||||
// Descending because last entry is first processed
|
||||
_formState.FieldInputs = (from input in inputs orderby input.Item1 descending select input).ToList();
|
||||
}
|
||||
}
|
||||
await SkipSteps();
|
||||
_formState.Step = 0;
|
||||
_formState.StepState = null;
|
||||
|
||||
if (this._options.HasFlag(FormOptions.PromptInStart))
|
||||
{
|
||||
await MessageReceived(context, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Wait(MessageReceived);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task MessageReceived(IDialogContext context, IAwaitable<Schema.IMessageActivity> toBot)
|
||||
{
|
||||
try
|
||||
{
|
||||
var message = toBot == null ? null : await toBot;
|
||||
|
||||
// Ensure we have initial definition for field steps
|
||||
foreach (var step in _form.Steps)
|
||||
{
|
||||
if (step.Type == StepType.Field && step.Field.Prompt == null)
|
||||
{
|
||||
await step.DefineAsync(_state);
|
||||
}
|
||||
}
|
||||
|
||||
var next = (_formState.Next == null ? new NextStep() : ActiveSteps(_formState.Next, _state));
|
||||
bool waitForMessage = false;
|
||||
FormPrompt lastPrompt = _formState.LastPrompt;
|
||||
|
||||
Func<FormPrompt, IStep<T>, Task<FormPrompt>> PostAsync = async (prompt, step) =>
|
||||
{
|
||||
return await _form.Prompt(context, prompt, _state, step.Field);
|
||||
};
|
||||
|
||||
Func<IStep<T>, IEnumerable<TermMatch>, Task<bool>> DoStepAsync = async (step, matches) =>
|
||||
{
|
||||
var result = await step.ProcessAsync(context, _state, _formState, message, matches);
|
||||
await SkipSteps();
|
||||
next = result.Next;
|
||||
if (result.Feedback?.Prompt != null)
|
||||
{
|
||||
await PostAsync(result.Feedback, step);
|
||||
if (_formState.Phase() != StepPhase.Completed)
|
||||
{
|
||||
if (!_formState.ProcessInputs)
|
||||
{
|
||||
await PostAsync(lastPrompt, step);
|
||||
waitForMessage = true;
|
||||
}
|
||||
else if (result.Prompt?.Buttons != null)
|
||||
{
|
||||
// We showed buttons so allow them to be pressed
|
||||
waitForMessage = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// After simple feedback, reset to ready
|
||||
_formState.SetPhase(StepPhase.Ready);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (result.Prompt != null)
|
||||
{
|
||||
lastPrompt = await PostAsync(result.Prompt, step);
|
||||
waitForMessage = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
while (!waitForMessage && MoveToNext(next))
|
||||
{
|
||||
IStep<T> step = null;
|
||||
IEnumerable<TermMatch> matches = null;
|
||||
if (next.Direction == StepDirection.Named && next.Names.Length > 1)
|
||||
{
|
||||
// We need to choose between multiple next steps
|
||||
bool start = (_formState.Next == null);
|
||||
_formState.Next = next;
|
||||
step = new NavigationStep<T>(_form.Steps[_formState.Step].Name, _form, _state, _formState);
|
||||
if (start)
|
||||
{
|
||||
lastPrompt = await PostAsync(step.Start(context, _state, _formState), step);
|
||||
waitForMessage = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Responding
|
||||
matches = step.Match(context, _state, _formState, message);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Processing current step
|
||||
step = _form.Steps[_formState.Step];
|
||||
if (await step.DefineAsync(_state))
|
||||
{
|
||||
if (_formState.Phase() == StepPhase.Ready)
|
||||
{
|
||||
if (step.Type == StepType.Message)
|
||||
{
|
||||
await PostAsync(step.Start(context, _state, _formState), step);
|
||||
next = new NextStep();
|
||||
}
|
||||
else if (_formState.ProcessInputs)
|
||||
{
|
||||
message = MessageActivityHelper.BuildMessageWithText(_formState.FieldInputs.Last().Item2);
|
||||
lastPrompt = step.Start(context, _state, _formState);
|
||||
}
|
||||
else
|
||||
{
|
||||
lastPrompt = await PostAsync(step.Start(context, _state, _formState), step);
|
||||
waitForMessage = true;
|
||||
}
|
||||
}
|
||||
else if (_formState.Phase() == StepPhase.Responding)
|
||||
{
|
||||
matches = step.Match(context, _state, _formState, message);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_formState.SetPhase(StepPhase.Completed);
|
||||
lastPrompt = null;
|
||||
next = new NextStep(StepDirection.Next);
|
||||
}
|
||||
}
|
||||
|
||||
if (matches != null)
|
||||
{
|
||||
var inputText = MessageActivityHelper.GetSanitizedTextInput(message);
|
||||
matches = MatchAnalyzer.Coalesce(matches, inputText).ToArray();
|
||||
if (MatchAnalyzer.IsFullMatch(inputText, matches))
|
||||
{
|
||||
await DoStepAsync(step, matches);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Filter non-active steps out of command matches
|
||||
var messageText = message.Text;
|
||||
var commands =
|
||||
(messageText == null || messageText.Trim().StartsWith("\""))
|
||||
? new TermMatch[0]
|
||||
: (from command in MatchAnalyzer.Coalesce(_commands.Prompt.Recognizer.Matches(message), messageText)
|
||||
where (command.Value is FormCommand
|
||||
|| (!_formState.ProcessInputs && _form.Fields.Field((string)command.Value).Active(_state)))
|
||||
select command).ToArray();
|
||||
|
||||
if (commands.Length == 1 && MatchAnalyzer.IsFullMatch(messageText, commands))
|
||||
{
|
||||
FormPrompt feedback;
|
||||
next = DoCommand(context, _state, _formState, step, commands, out feedback);
|
||||
if (feedback != null)
|
||||
{
|
||||
await PostAsync(feedback, step);
|
||||
await PostAsync(lastPrompt, step);
|
||||
waitForMessage = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (matches.Count() == 0 && commands.Count() == 0)
|
||||
{
|
||||
await PostAsync(step.NotUnderstood(context, _state, _formState, message), step);
|
||||
if (_formState.ProcessInputs && !step.InClarify(_formState))
|
||||
{
|
||||
_formState.SetPhase(StepPhase.Ready);
|
||||
}
|
||||
else
|
||||
{
|
||||
waitForMessage = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Go with response since it looks possible
|
||||
var bestMatch = MatchAnalyzer.BestMatches(matches, commands);
|
||||
if (bestMatch == 0)
|
||||
{
|
||||
await DoStepAsync(step, matches);
|
||||
}
|
||||
else
|
||||
{
|
||||
FormPrompt feedback;
|
||||
next = DoCommand(context, _state, _formState, step, commands, out feedback);
|
||||
if (feedback != null)
|
||||
{
|
||||
await PostAsync(feedback, step);
|
||||
await PostAsync(lastPrompt, step);
|
||||
waitForMessage = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
next = ActiveSteps(next, _state);
|
||||
}
|
||||
|
||||
if (next.Direction == StepDirection.Complete || next.Direction == StepDirection.Quit)
|
||||
{
|
||||
if (next.Direction == StepDirection.Complete)
|
||||
{
|
||||
if (_form.Completion != null)
|
||||
{
|
||||
await _form.Completion(context, _state);
|
||||
}
|
||||
context.Done(_state);
|
||||
}
|
||||
else if (next.Direction == StepDirection.Quit)
|
||||
{
|
||||
throw new FormCanceledException<T>("Form quit.")
|
||||
{
|
||||
LastForm = _state,
|
||||
Last = _form.Steps[_formState.Step].Name,
|
||||
Completed = (from step in _form.Steps
|
||||
where _formState.Phase(_form.StepIndex(step)) == StepPhase.Completed
|
||||
select step.Name).ToArray()
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_formState.LastPrompt = (FormPrompt)lastPrompt?.Clone();
|
||||
context.Wait(MessageReceived);
|
||||
}
|
||||
}
|
||||
catch (Exception inner)
|
||||
{
|
||||
if (!(inner is FormCanceledException<T>))
|
||||
{
|
||||
throw new FormCanceledException<T>(inner.Message, inner)
|
||||
{
|
||||
LastForm = _state,
|
||||
Last = _form.Steps[_formState.Step].Name,
|
||||
Completed = (from step in _form.Steps
|
||||
where _formState.Phase(_form.StepIndex(step)) == StepPhase.Completed
|
||||
select step.Name).ToArray()
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation
|
||||
|
||||
private async Task SkipSteps()
|
||||
{
|
||||
if (!_options.HasFlag(FormOptions.PromptFieldsWithValues))
|
||||
{
|
||||
// Skip steps that already have a value if they are nullable and valid.
|
||||
foreach (var step in _form.Steps)
|
||||
{
|
||||
int stepi = _form.StepIndex(step);
|
||||
if (step.Type == StepType.Field
|
||||
&& _formState.Phase(stepi) == StepPhase.Ready
|
||||
&& !step.Field.IsUnknown(_state)
|
||||
&& step.Field.IsNullable)
|
||||
{
|
||||
var defined = await step.DefineAsync(_state);
|
||||
if (defined)
|
||||
{
|
||||
var val = step.Field.GetValue(_state);
|
||||
var result = await step.Field.ValidateAsync(_state, val);
|
||||
if (result.IsValid)
|
||||
{
|
||||
bool ok = true;
|
||||
double min, max;
|
||||
if (step.Field.Limits(out min, out max))
|
||||
{
|
||||
var num = (double)Convert.ChangeType(val, typeof(double));
|
||||
ok = (num >= min && num <= max);
|
||||
}
|
||||
if (ok)
|
||||
{
|
||||
_formState.SetPhase(stepi, StepPhase.Completed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private NextStep ActiveSteps(NextStep next, T state)
|
||||
{
|
||||
var result = next;
|
||||
if (next.Direction == StepDirection.Named)
|
||||
{
|
||||
var names = (from name in next.Names where _form.Fields.Field(name).Active(state) select name);
|
||||
var count = names.Count();
|
||||
if (count == 0)
|
||||
{
|
||||
result = new NextStep();
|
||||
}
|
||||
else if (count != result.Names.Length)
|
||||
{
|
||||
result = new NextStep(names);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the next step to execute.
|
||||
/// </summary>
|
||||
/// <param name="next">What step to execute next.</param>
|
||||
/// <returns>True if can switch to step.</returns>
|
||||
private bool MoveToNext(NextStep next)
|
||||
{
|
||||
bool found = false;
|
||||
switch (next.Direction)
|
||||
{
|
||||
case StepDirection.Complete:
|
||||
break;
|
||||
case StepDirection.Named:
|
||||
_formState.StepState = null;
|
||||
if (next.Names.Length == 0)
|
||||
{
|
||||
goto case StepDirection.Next;
|
||||
}
|
||||
else if (next.Names.Length == 1)
|
||||
{
|
||||
var name = next.Names.First();
|
||||
var nextStep = -1;
|
||||
for (var i = 0; i < _form.Steps.Count(); ++i)
|
||||
{
|
||||
if (_form.Steps[i].Name == name)
|
||||
{
|
||||
nextStep = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (nextStep == -1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("NextStep", "Does not correspond to a field in the form.");
|
||||
}
|
||||
if (_form.Steps[nextStep].Active(_state))
|
||||
{
|
||||
var current = _form.Steps[_formState.Step];
|
||||
_formState.SetPhase(_form.Fields.Field(current.Name).IsUnknown(_state) ? StepPhase.Ready : StepPhase.Completed);
|
||||
_formState.History.Push(_formState.Step);
|
||||
_formState.Step = nextStep;
|
||||
_formState.SetPhase(StepPhase.Ready);
|
||||
found = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we went to a state which is not active fall through to the next active if any
|
||||
goto case StepDirection.Next;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Always mark multiple names as found so we can handle the user navigation
|
||||
found = true;
|
||||
}
|
||||
break;
|
||||
case StepDirection.Next:
|
||||
{
|
||||
var start = _formState.Step;
|
||||
// Reset any non-optional field step that has been reset to no value
|
||||
for (var i = 0; i < _form.Steps.Count; ++i)
|
||||
{
|
||||
var step = _form.Steps[i];
|
||||
if (step.Type == StepType.Field && _formState.Phase(i) == StepPhase.Completed && !step.Field.Optional && step.Field.IsUnknown(_state))
|
||||
{
|
||||
_formState.SetPhase(i, StepPhase.Ready);
|
||||
}
|
||||
}
|
||||
// Next ready step including current one
|
||||
for (var offset = 0; offset < _form.Steps.Count; ++offset)
|
||||
{
|
||||
var istep = (start + offset) % _form.Steps.Count;
|
||||
var step = _form.Steps[istep];
|
||||
_formState.Step = istep;
|
||||
if (offset > 0)
|
||||
{
|
||||
_formState.StepState = null;
|
||||
_formState.Next = null;
|
||||
}
|
||||
if ((_formState.Phase(istep) == StepPhase.Ready || _formState.Phase(istep) == StepPhase.Responding)
|
||||
&& step.Active(_state))
|
||||
{
|
||||
// Ensure all dependencies have values
|
||||
foreach (var dependency in step.Dependencies)
|
||||
{
|
||||
var dstep = _form.Step(dependency);
|
||||
var dstepi = _form.StepIndex(dstep);
|
||||
if (dstep.Active(_state) && _formState.Phases[dstepi] != StepPhase.Completed)
|
||||
{
|
||||
_formState.Step = dstepi;
|
||||
break;
|
||||
}
|
||||
}
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
next.Direction = StepDirection.Complete;
|
||||
}
|
||||
else
|
||||
{
|
||||
var normalStep = _formState.Step;
|
||||
// Process initial messages first, then FieldInputs
|
||||
if ((_formState.ProcessInputs || _form.Steps[normalStep].Type != StepType.Message) && _formState.FieldInputs != null)
|
||||
{
|
||||
// Override normal choice with FieldInputs
|
||||
Func<bool> NextFieldInput = () =>
|
||||
{
|
||||
var foundInput = false;
|
||||
while (_formState.FieldInputs.Any() && !foundInput)
|
||||
{
|
||||
var possible = _formState.FieldInputs.Last().Item1;
|
||||
if (_form.Steps[possible].Active(_state))
|
||||
{
|
||||
_formState.Step = possible;
|
||||
foundInput = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_formState.FieldInputs.Pop();
|
||||
}
|
||||
}
|
||||
if (!_formState.FieldInputs.Any())
|
||||
{
|
||||
if (_options.HasFlag(FormOptions.PromptFieldsWithValues))
|
||||
{
|
||||
_formState.Reset();
|
||||
}
|
||||
else
|
||||
{
|
||||
_formState.ProcessInputs = false;
|
||||
_formState.FieldInputs = null;
|
||||
_formState.Step = 0;
|
||||
}
|
||||
// Skip initial messages since we showed them already
|
||||
while (_formState.Step < _form.Steps.Count() && _form.Steps[_formState.Step].Type == StepType.Message)
|
||||
{
|
||||
_formState.SetPhase(StepPhase.Completed);
|
||||
++_formState.Step;
|
||||
}
|
||||
}
|
||||
return foundInput;
|
||||
};
|
||||
if (!_formState.ProcessInputs)
|
||||
{
|
||||
// Start of processing inputs
|
||||
_formState.ProcessInputs = NextFieldInput();
|
||||
}
|
||||
else if (_formState.Phase(start) == StepPhase.Completed || _formState.Phase(start) == StepPhase.Ready)
|
||||
{
|
||||
// Reset state of just completed step
|
||||
if (_options.HasFlag(FormOptions.PromptFieldsWithValues))
|
||||
{
|
||||
_formState.SetPhase(StepPhase.Ready);
|
||||
}
|
||||
// Move on to next field input if any
|
||||
_formState.FieldInputs.Pop();
|
||||
NextFieldInput();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_formState.Step != start && _form.Steps[start].Type != StepType.Message)
|
||||
{
|
||||
_formState.History.Push(start);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case StepDirection.Previous:
|
||||
while (_formState.History.Count() > 0)
|
||||
{
|
||||
var lastStepIndex = _formState.History.Pop();
|
||||
var lastStep = _form.Steps[lastStepIndex];
|
||||
if (lastStep.Active(_state))
|
||||
{
|
||||
var step = _form.Steps[_formState.Step];
|
||||
_formState.SetPhase(step.Field.IsUnknown(_state) ? StepPhase.Ready : StepPhase.Completed);
|
||||
_formState.Step = lastStepIndex;
|
||||
_formState.SetPhase(StepPhase.Ready);
|
||||
_formState.StepState = null;
|
||||
_formState.Next = null;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
next.Direction = StepDirection.Quit;
|
||||
}
|
||||
break;
|
||||
case StepDirection.Quit:
|
||||
break;
|
||||
case StepDirection.Reset:
|
||||
_formState.Reset();
|
||||
// Because we redo phase they can go through everything again but with defaults.
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
private NextStep DoCommand(IDialogContext context, T state, FormState form, IStep<T> step, IEnumerable<TermMatch> matches, out FormPrompt feedback)
|
||||
{
|
||||
// TODO: What if there are more than one command?
|
||||
feedback = null;
|
||||
var next = new NextStep();
|
||||
var value = matches.First().Value;
|
||||
if (value is FormCommand)
|
||||
{
|
||||
switch ((FormCommand)value)
|
||||
{
|
||||
case FormCommand.Backup:
|
||||
{
|
||||
next.Direction = step.Back(context, state, form) ? StepDirection.Next : StepDirection.Previous;
|
||||
}
|
||||
break;
|
||||
case FormCommand.Help:
|
||||
{
|
||||
var field = step.Field;
|
||||
var builder = new StringBuilder();
|
||||
foreach (var entry in _form.Configuration.Commands)
|
||||
{
|
||||
builder.Append("* ");
|
||||
builder.AppendLine(entry.Value.Help);
|
||||
}
|
||||
var navigation = new Prompter<T>(field.Template(TemplateUsage.NavigationCommandHelp), _form, null);
|
||||
var active = (from istep in _form.Steps
|
||||
where !form.ProcessInputs && istep.Type == StepType.Field && istep.Active(state)
|
||||
select istep.Field.FieldDescription.Description).ToArray();
|
||||
if (active.Length > 1)
|
||||
{
|
||||
var activeList = Language.BuildList(active, navigation.Annotation.ChoiceSeparator, navigation.Annotation.ChoiceLastSeparator);
|
||||
builder.Append("* ");
|
||||
builder.Append(navigation.Prompt(state, null, activeList));
|
||||
}
|
||||
feedback = step.Help(state, form, builder.ToString());
|
||||
}
|
||||
break;
|
||||
case FormCommand.Quit: next.Direction = StepDirection.Quit; break;
|
||||
case FormCommand.Reset: next.Direction = StepDirection.Reset; break;
|
||||
case FormCommand.Status:
|
||||
{
|
||||
var prompt = new PromptAttribute("{*}");
|
||||
feedback = new Prompter<T>(prompt, _form, null).Prompt(state, null);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var name = (string)value;
|
||||
var istep = _form.Step(name);
|
||||
if (istep != null && istep.Active(state))
|
||||
{
|
||||
next = new NextStep(new string[] { name });
|
||||
}
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.Luis.Models
|
||||
{
|
||||
[Serializable]
|
||||
public partial class EntityRecommendation
|
||||
{
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public partial class IntentRecommendation
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Microsoft.Bot.Builder.V3Bridge.FormFlow.Advanced;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.FormFlow
|
||||
{
|
||||
[Serializable]
|
||||
internal class FormState
|
||||
{
|
||||
// Last sent prompt which is used when feedback is supplied
|
||||
public FormPrompt LastPrompt;
|
||||
|
||||
// Used when navigating to reflect choices for next
|
||||
public NextStep Next;
|
||||
|
||||
// Currently executing step
|
||||
public int Step;
|
||||
|
||||
// History of executed steps
|
||||
public Stack<int> History;
|
||||
|
||||
// Current phase of each step
|
||||
public StepPhase[] Phases;
|
||||
|
||||
// Internal state of a step
|
||||
public object StepState;
|
||||
|
||||
// Field number and input
|
||||
public List<Tuple<int, string>> FieldInputs;
|
||||
|
||||
// True when we have started processing FieldInputs
|
||||
public bool ProcessInputs;
|
||||
|
||||
public FormState(int steps)
|
||||
{
|
||||
Phases = new StepPhase[steps];
|
||||
Reset();
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
LastPrompt = new FormPrompt();
|
||||
Next = null;
|
||||
Step = 0;
|
||||
History = new Stack<int>();
|
||||
Phases = new StepPhase[Phases.Length];
|
||||
StepState = null;
|
||||
FieldInputs = null;
|
||||
ProcessInputs = false;
|
||||
}
|
||||
|
||||
public StepPhase Phase()
|
||||
{
|
||||
return Phases[Step];
|
||||
}
|
||||
|
||||
public StepPhase Phase(int step)
|
||||
{
|
||||
return Phases[step];
|
||||
}
|
||||
|
||||
public void SetPhase(StepPhase phase)
|
||||
{
|
||||
Phases[Step] = phase;
|
||||
}
|
||||
|
||||
public void SetPhase(int step, StepPhase phase)
|
||||
{
|
||||
Phases[step] = phase;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,437 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using Microsoft.Bot.Builder.V3Bridge.FormFlow;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.FormFlow.Advanced
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface that defines basic access to a field.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The form state that is read or written to.</typeparam>
|
||||
public interface IFieldState<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Get this field value from form state.
|
||||
/// </summary>
|
||||
/// <param name="state">Form state to get field value from.</param>
|
||||
/// <returns>Current value found in state.</returns>
|
||||
object GetValue(T state);
|
||||
|
||||
/// <summary>
|
||||
/// Set this field value in form state.
|
||||
/// </summary>
|
||||
/// <param name="state">Form state to set field value in.</param>
|
||||
/// <param name="value">New value.</param>
|
||||
void SetValue(T state, object value);
|
||||
|
||||
/// <summary>
|
||||
/// Test to see if the field value form state has a value.
|
||||
/// </summary>
|
||||
/// <param name="state">Form state to check.</param>
|
||||
/// <returns>True if value is unknown.</returns>
|
||||
/// <remarks>
|
||||
/// For value types (numbers, bools, date time) a value is unknown only if the field is nullable and it is null.
|
||||
/// For enum based values (both simple and enumerated) they can also be nullable or the 0 enum value if not nullable.
|
||||
/// For non value types like string the test is to see if the field is actually null.
|
||||
/// </remarks>
|
||||
bool IsUnknown(T state);
|
||||
|
||||
/// <summary>
|
||||
/// Set this field value in form state to unknown.
|
||||
/// </summary>
|
||||
/// <param name="state">Form state with field value to set to unknown.</param>
|
||||
/// <remarks>
|
||||
/// For value types (numbers, bools, date time) the value is set to null if nullable.
|
||||
/// For enum types it is set to null if nullable or 0 if not.
|
||||
/// For non value types like string set the value to null.
|
||||
/// </remarks>
|
||||
void SetUnknown(T state);
|
||||
|
||||
/// <summary> Gets the type of the field. </summary>
|
||||
/// <value> The type. </value>
|
||||
Type Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Test to see if field is optional which means that an unknown value is legal.
|
||||
/// </summary>
|
||||
/// <returns>True if field is optional.</returns>
|
||||
bool Optional { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Test to see if field is nullable.
|
||||
/// </summary>
|
||||
/// <returns>True if field is nullable.</returns>
|
||||
bool IsNullable { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Limits of numeric values.
|
||||
/// </summary>
|
||||
/// <param name="min">Minimum possible value.</param>
|
||||
/// <param name="max">Maximum possible value.</param>
|
||||
/// <returns>True if limits limit the underlying data type.</returns>
|
||||
/// <remarks>
|
||||
/// This typically reflects the result of setting <see cref="NumericAttribute"/> limits on the possible values.</remarks>
|
||||
bool Limits(out double min, out double max);
|
||||
|
||||
/// <summary>
|
||||
/// Regular expression for validating a string.
|
||||
/// </summary>
|
||||
/// <returns>Validation regular expression.</returns>
|
||||
/// <remarks> This typically reflects the result of setting <see cref="PatternAttribute"/>.</remarks>
|
||||
string Pattern { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the other fields this one depends on.
|
||||
/// </summary>
|
||||
/// <returns>List of field names this one depends on.</returns>
|
||||
/// <remarks>This is mainly useful for <see cref="Advanced.Confirmation{T}"/> fields.</remarks>
|
||||
IEnumerable<string> Dependencies { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The role the field plays in a form.
|
||||
/// </summary>
|
||||
public enum FieldRole
|
||||
{
|
||||
/// <summary>
|
||||
/// Field is used to get a value to set in the form state.
|
||||
/// </summary>
|
||||
/// <remarks>This is the kind of field generated by <see cref="IFormBuilder{T}.Field"/>.</remarks>
|
||||
Value,
|
||||
|
||||
/// <summary>
|
||||
/// Field is used to confirm some settings during the dialog.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is the kind of field generated by <see cref="IFormBuilder{T}.Confirm"/>.
|
||||
/// </remarks>
|
||||
Confirm
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Describe the information displayed about a field and its values.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Throughout this class Description refers to the name of a field or a value
|
||||
/// whereas "terms" tell what people can type to match the field or terms in it.
|
||||
/// When generating terms it is a good idea to include anything that might be reasonable
|
||||
/// for someone to type. The form dialog itself will help clarify any ambiguity. One
|
||||
/// way to do this is to use <see cref="TermsAttribute.MaxPhrase"/> which ensures that <see cref="Language.GenerateTerms"/>
|
||||
/// is called on your base terms.
|
||||
/// </remarks>
|
||||
public interface IFieldDescription
|
||||
{
|
||||
/// <summary>
|
||||
/// Role field plays in a form.
|
||||
/// </summary>
|
||||
/// <returns>Role field plays in form.</returns>
|
||||
FieldRole Role { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Description of the field itself.
|
||||
/// </summary>
|
||||
/// <returns>Field description.</returns>
|
||||
/// <remarks>
|
||||
/// This is the value that will be used in \ref patterns by {&}, choices with {||} or buttons.
|
||||
/// </remarks>
|
||||
DescribeAttribute FieldDescription { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Terms for matching this field.
|
||||
/// </summary>
|
||||
/// <returns>List of term regex for matching the field name.</returns>
|
||||
IEnumerable<string> FieldTerms { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Return the description of a specific value.
|
||||
/// </summary>
|
||||
/// <param name="value">Value being described.</param>
|
||||
/// <returns>Description of value.</returns>
|
||||
DescribeAttribute ValueDescription(object value);
|
||||
|
||||
/// <summary>
|
||||
/// Return all possible value descriptions in order to support enumeration.
|
||||
/// </summary>
|
||||
/// <returns>All possible value descriptions.</returns>
|
||||
IEnumerable<DescribeAttribute> ValueDescriptions { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Given a value return terms that can be used in a dialog to match the object.
|
||||
/// </summary>
|
||||
/// <param name="value">Value that would result from a match.</param>
|
||||
/// <returns>Enumeration of regex.</returns>
|
||||
IEnumerable<string> Terms(object value);
|
||||
|
||||
/// <summary>
|
||||
/// All possible values or null if it is a data type like number.
|
||||
/// </summary>
|
||||
/// <returns>All possible values.</returns>
|
||||
IEnumerable<object> Values { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Are multiple matches allowed.
|
||||
/// </summary>
|
||||
/// <returns>True if more than one value is allowed.</returns>
|
||||
/// <remarks>This is true is you have a list of enumerated values.</remarks>
|
||||
bool AllowsMultiple { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Allow the default value as an option.
|
||||
/// </summary>
|
||||
/// <returns>True if default values are allowed.</returns>
|
||||
bool AllowDefault { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Allow user input to match numbers shown with enumerated choices.
|
||||
/// </summary>
|
||||
/// <returns>True if numbers are allowed as input.</returns>
|
||||
bool AllowNumbers { get; }
|
||||
}
|
||||
|
||||
#region Documentation
|
||||
/// <summary> Interface for saving/localizing generated resources. </summary>
|
||||
#endregion
|
||||
public interface IFieldResources
|
||||
{
|
||||
/// <summary> Adds any string resources to form localizer. </summary>
|
||||
void SaveResources();
|
||||
|
||||
/// <summary> Loads any string resources from the form localizer. </summary>
|
||||
void Localize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Direction for next step.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// As each step in a form completes, the step can determine the next step to take.
|
||||
/// Usually this is just to move onto the next active, uncompleted step, but you can
|
||||
/// also move back or present a list of choices to the user.
|
||||
/// A step is active if <see cref="IFieldPrompt{T}.Active(T)"/> returns true on the current state.
|
||||
/// A step is ready if it has not already been successfully completed.
|
||||
/// </remarks>
|
||||
public enum StepDirection
|
||||
{
|
||||
/// <summary>
|
||||
/// The form is complete and <see cref="IFormBuilder{T}.OnCompletion(OnCompletionAsyncDelegate{T})"/> should be called.
|
||||
/// </summary>
|
||||
Complete,
|
||||
|
||||
/// <summary>
|
||||
/// Move to a named step. If there is more than one name, the user will be asked to choose.
|
||||
/// </summary>
|
||||
Named,
|
||||
|
||||
/// <summary>
|
||||
/// Move to the next step that is <see cref="IFieldPrompt{T}.Active(T)"/> and uncompleted.
|
||||
/// </summary>
|
||||
Next,
|
||||
|
||||
/// <summary>
|
||||
/// Move to the previously executed step.
|
||||
/// </summary>
|
||||
Previous,
|
||||
|
||||
/// <summary>
|
||||
/// Quit the form and return failure to the parent dialog.
|
||||
/// </summary>
|
||||
Quit,
|
||||
|
||||
/// <summary>
|
||||
/// Reset the form to start over.
|
||||
/// </summary>
|
||||
Reset
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Next step to take.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class NextStep
|
||||
{
|
||||
/// <summary>
|
||||
/// By default move on to the next active, uncompleted step.
|
||||
/// </summary>
|
||||
public NextStep()
|
||||
{
|
||||
Direction = StepDirection.Next;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move as specified in direction.
|
||||
/// </summary>
|
||||
/// <param name="direction">What step to do next.</param>
|
||||
public NextStep(StepDirection direction)
|
||||
{
|
||||
Direction = direction;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ask the user which of the fields to move to next.
|
||||
/// </summary>
|
||||
/// <param name="names">Enumeration of possible next steps.</param>
|
||||
public NextStep(IEnumerable<string> names)
|
||||
{
|
||||
Direction = StepDirection.Named;
|
||||
Names = names.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Direction for next step.
|
||||
/// </summary>
|
||||
public StepDirection Direction;
|
||||
|
||||
/// <summary>
|
||||
/// If this is a named step, one or more named steps to move to. If there are more than one, the user will choose.
|
||||
/// </summary>
|
||||
public string[] Names;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This provides control information about a field.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Form state that is being completed.</typeparam>
|
||||
public interface IFieldPrompt<T>
|
||||
where T : class
|
||||
{
|
||||
/// <summary>
|
||||
/// Test to see if field is currently active based on the current state.
|
||||
/// </summary>
|
||||
/// <returns>True if field is active.</returns>
|
||||
/// <remarks>
|
||||
/// One way to control this is to supply a <see cref="ActiveDelegate{T}"/> to the
|
||||
/// <see cref="IFormBuilder{T}.Field"/> or <see cref="IFormBuilder{T}.Confirm"/> steps.
|
||||
/// </remarks>
|
||||
bool Active(T state);
|
||||
|
||||
/// <summary>
|
||||
/// Return a template for building a prompt.
|
||||
/// </summary>
|
||||
/// <param name="usage">Kind of template we are looking for.</param>
|
||||
/// <returns>NULL if no template, otherwise a template annotation.</returns>
|
||||
TemplateAttribute Template(TemplateUsage usage);
|
||||
|
||||
#region Documentation
|
||||
/// <summary> Returns the prompt description. </summary>
|
||||
/// <returns> An <see cref="IPrompt{T}"/> describing prompt and recognizer. </returns>
|
||||
/// <remarks>If a prompt is dynamically computed this should be null until <see cref="DefineAsync(T)"/> is called.</remarks>
|
||||
#endregion
|
||||
IPrompt<T> Prompt { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Build the prompt and recognizer for dynamically defined fields.
|
||||
/// </summary>
|
||||
/// <returns>True if field is defined.</returns>
|
||||
/// <remarks>
|
||||
/// This method is called before asking for <see cref="Prompt"/>.
|
||||
/// This provides an opportunity to dynamically define the field based on the current
|
||||
/// state or external information. The <see cref="IFieldState{T}.Dependencies"/> method
|
||||
/// identifies fields that this one depends on. All of them will be complete before the field
|
||||
/// will be shown to the user, but this method might be called earlier in order to define the field
|
||||
/// for things like status and initial matching or validation.
|
||||
/// </remarks>
|
||||
Task<bool> DefineAsync(T state);
|
||||
|
||||
/// <summary>
|
||||
/// Validate value to be set on state and return feedback if not valid.
|
||||
/// </summary>
|
||||
/// <param name="state">State before setting value.</param>
|
||||
/// <param name="value">Value to be set in field.</param>
|
||||
/// <returns>Result including feedback and if valid.</returns>
|
||||
/// <remarks>
|
||||
/// One way to control this is to supply a <see cref="ValidateAsyncDelegate{T}"/> to the
|
||||
/// <see cref="IFormBuilder{T}.Field"/> or <see cref="IFormBuilder{T}.Confirm"/> steps.
|
||||
/// </remarks>
|
||||
Task<ValidateResult> ValidateAsync(T state, object value);
|
||||
|
||||
/// <summary>
|
||||
/// Return the help description for this field.
|
||||
/// </summary>
|
||||
/// <returns>The prompt to use for generating help.</returns>
|
||||
/// <remarks>
|
||||
/// Help is a mixture of field specific help, what a recognizer understands and available commands.
|
||||
/// </remarks>
|
||||
IPrompt<T> Help { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Next step to execute.
|
||||
/// </summary>
|
||||
/// <param name="value">Value in response to prompt.</param>
|
||||
/// <param name="state">Current form state.</param>
|
||||
/// <returns>Next step to execute.</returns>
|
||||
NextStep Next(object value, T state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface for all the information about a specific field.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Form state interface applies to.</typeparam>
|
||||
public interface IField<T> : IFieldState<T>, IFieldDescription, IFieldPrompt<T>, IFieldResources
|
||||
where T : class
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of this field.
|
||||
/// </summary>
|
||||
/// <returns>Name of this field.</returns>
|
||||
/// <remarks>
|
||||
/// For a value field this is the path in the form state that leads to the value being filled in.
|
||||
/// For a confirm field this is a randomly generated name.
|
||||
/// </remarks>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Form that owns this field
|
||||
/// </summary>
|
||||
IForm<T> Form { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface to track all of the fields in a form.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public interface IFields<T> : IEnumerable<IField<T>>
|
||||
where T : class
|
||||
{
|
||||
/// <summary>
|
||||
/// Return a specific field or null if not present.
|
||||
/// </summary>
|
||||
/// <param name="name">Name of field to find.</param>
|
||||
/// <returns>Field description for name or null.</returns>
|
||||
IField<T> Field(string name);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using Microsoft.Bot.Builder.V3Bridge.Dialogs;
|
||||
using Microsoft.Bot.Builder.V3Bridge.FormFlow.Advanced;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Resources;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.FormFlow
|
||||
{
|
||||
#region Documentation
|
||||
/// <summary> Form definition interface. </summary>
|
||||
/// <typeparam name="T"> Form state. </typeparam>
|
||||
#endregion
|
||||
public abstract class IForm<T>
|
||||
where T : class
|
||||
{
|
||||
/// <summary>
|
||||
/// Fields that make up form.
|
||||
/// </summary>
|
||||
public abstract IFields<T> Fields { get; }
|
||||
|
||||
#region Documentation
|
||||
/// <summary> Save all string resources to binary stream for future localization. </summary>
|
||||
/// <param name="writer"> Where to write resources. </param>
|
||||
#endregion
|
||||
public abstract void SaveResources(IResourceWriter writer);
|
||||
|
||||
#region Documentation
|
||||
/// <summary> Localize all string resources from binary stream. </summary>
|
||||
/// <param name="reader"> Where to read resources. </param>
|
||||
/// <param name="missing"> [out] Any values in the form, but missing from the stream. </param>
|
||||
/// <param name="extra"> [out] Any values in the stream, but missing from the form. </param>
|
||||
/// <remarks>When you localize all form string resources will be overridden if present in the stream.
|
||||
/// Otherwise the value will remain unchanged.
|
||||
/// </remarks>
|
||||
#endregion
|
||||
public abstract void Localize(IDictionaryEnumerator reader, out IEnumerable<string> missing, out IEnumerable<string> extra);
|
||||
|
||||
// Internals
|
||||
internal abstract ILocalizer Resources { get; }
|
||||
internal abstract FormConfiguration Configuration { get; }
|
||||
internal abstract IReadOnlyList<IStep<T>> Steps { get; }
|
||||
internal abstract OnCompletionAsyncDelegate<T> Completion { get; }
|
||||
internal abstract Task<FormPrompt> Prompt(IDialogContext context, FormPrompt prompt, T state, IField<T> field);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,440 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using Microsoft.Bot.Builder.V3Bridge.Resource;
|
||||
using Microsoft.Bot.Builder.V3Bridge.FormFlow.Advanced;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Resource;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.FormFlow
|
||||
{
|
||||
#region Documentation
|
||||
/// <summary> Given <paramref name="state"/> return a <see cref="PromptAttribute"/> with a template for the message to display. </summary>
|
||||
/// <typeparam name="T"> Form state type. </typeparam>
|
||||
/// <param name="state"> Form state. </param>
|
||||
/// <returns> A PromptAttribute describing the message to display. </returns>
|
||||
#endregion
|
||||
public delegate Task<PromptAttribute> MessageDelegate<T>(T state);
|
||||
|
||||
#region Documentation
|
||||
/// <summary> Interface for building a form. </summary>
|
||||
/// <remarks>
|
||||
/// A form consists of a series of steps that can be one of:
|
||||
/// <list type="list">
|
||||
/// <item>A message to the user.</item>
|
||||
/// <item>A prompt sent to the user where the response is to fill in a form state value.</item>
|
||||
/// <item>A confirmation of the current state with the user.</item>
|
||||
/// </list>
|
||||
/// By default the steps are executed in the order of the <see cref="Message"/>, <see cref="Field"/> and <see cref="Confirm"/> calls.
|
||||
/// If you do not take explicit control, the steps will be executed in the order defined in the
|
||||
/// form state with a final confirmation.
|
||||
/// This interface allows you to flently build a form by composing together fields,
|
||||
/// messages and confirmation. The fluent building blocks provide common patterns
|
||||
/// like fields being based on your state class, but you can also build up your
|
||||
/// own definition of a form by using Advanced.IField.
|
||||
/// If you want to build a form using C# reflection over your state class use FormBuilder.
|
||||
/// To declaratively build a form through JSON Schema you can use Json.FormBuilderJson.
|
||||
///
|
||||
/// Forms are sensitive to the current thread UI culture. The Microsoft.Bot.Builder.V3Bridge strings will localize
|
||||
/// to that culture if available. You can also localize the strings generated for your form by calling IForm.SaveResources
|
||||
/// or by using the RView tool and adding that resource to your project. For strings in dynamic fields, messages or confirmations you will
|
||||
/// need to use the normal C# mechanisms to localize them. Look in the overview documentation for more information.
|
||||
/// </remarks>
|
||||
/// <typeparam name="T">Form state.</typeparam>
|
||||
#endregion
|
||||
public interface IFormBuilder<T>
|
||||
where T : class
|
||||
{
|
||||
/// <summary>
|
||||
/// Build the form based on the methods called on the builder.
|
||||
/// </summary>
|
||||
/// <param name="resourceAssembly">Assembly for localization resources.</param>
|
||||
/// <param name="resourceName">Name of resources to use for localization.</param>
|
||||
/// <returns>The constructed form.</returns>
|
||||
/// <remarks>
|
||||
/// The default assembly is the one that contains <typeparamref name="T"/>
|
||||
/// and the default resourceName if the name of that type.
|
||||
/// </remarks>
|
||||
IForm<T> Build(Assembly resourceAssembly = null, string resourceName = null);
|
||||
|
||||
/// <summary>
|
||||
/// The form configuration supplies default templates and settings.
|
||||
/// </summary>
|
||||
/// <returns>The current form configuration.</returns>
|
||||
FormConfiguration Configuration { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Show a message that does not require a response.
|
||||
/// </summary>
|
||||
/// <param name="message">A \ref patterns string to fill in and send.</param>
|
||||
/// <param name="condition">Whether or not this step is active.</param>
|
||||
/// <param name="dependencies">Fields message depends on.</param>
|
||||
/// <returns>Modified IFormBuilder.</returns>
|
||||
IFormBuilder<T> Message(string message, ActiveDelegate<T> condition = null, IEnumerable<string> dependencies = null);
|
||||
|
||||
/// <summary>
|
||||
/// Show a message with more format control that does not require a response.
|
||||
/// </summary>
|
||||
/// <param name="prompt">Message to fill in and send.</param>
|
||||
/// <param name="condition">Whether or not this step is active.</param>
|
||||
/// <param name="dependencies">Fields message depends on.</param>
|
||||
/// <returns>Modified IFormBuilder.</returns>
|
||||
IFormBuilder<T> Message(PromptAttribute prompt, ActiveDelegate<T> condition = null, IEnumerable<string> dependencies = null);
|
||||
|
||||
#region Documentation
|
||||
/// <summary> Generate a message using a delegate to dynamically build the message. </summary>
|
||||
/// <param name="generateMessage"> Delegate for building message. </param>
|
||||
/// <param name="condition"> Whether or not this step is active. </param>
|
||||
/// <param name="dependencies">Fields message depends on.</param>
|
||||
/// <returns>Modified IFormBuilder.</returns>
|
||||
#endregion
|
||||
IFormBuilder<T> Message(MessageDelegate<T> generateMessage, ActiveDelegate<T> condition = null, IEnumerable<string> dependencies = null);
|
||||
|
||||
/// <summary>
|
||||
/// Derfine a field step by supplying your own field definition.
|
||||
/// </summary>
|
||||
/// <param name="field">Field definition to use.</param>
|
||||
/// <returns>Modified IFormBuilder.</returns>
|
||||
/// <remarks>
|
||||
/// You can provide your own implementation of <see cref="IField{T}"/> or you can
|
||||
/// use the <see cref="Field{T}"/> class to provide fluent values,
|
||||
/// <see cref="FieldReflector{T}"/> to use reflection or Json.FieldJson to use JSON Schema.
|
||||
/// </remarks>
|
||||
IFormBuilder<T> Field(IField<T> field);
|
||||
|
||||
/// <summary>
|
||||
/// Define a step for filling in a particular value in the form state.
|
||||
/// </summary>
|
||||
/// <param name="name">Path in the form state to the value being filled in.</param>
|
||||
/// <param name="active">Delegate to test form state to see if step is active.</param>
|
||||
/// <param name="validate">Delegate to validate the field value.</param>
|
||||
/// <returns>Modified IFormBuilder.</returns>
|
||||
IFormBuilder<T> Field(string name, ActiveDelegate<T> active = null, ValidateAsyncDelegate<T> validate = null);
|
||||
|
||||
/// <summary>
|
||||
/// Define a step for filling in a particular value in the form state.
|
||||
/// </summary>
|
||||
/// <param name="name">Path in the form state to the value being filled in.</param>
|
||||
/// <param name="prompt">Simple \ref patterns to describe prompt for field.</param>
|
||||
/// <param name="active">Delegate to test form state to see if step is active.n</param>
|
||||
/// <param name="validate">Delegate to validate the field value.</param>
|
||||
/// <returns>Modified IFormBuilder.</returns>
|
||||
IFormBuilder<T> Field(string name, string prompt, ActiveDelegate<T> active = null, ValidateAsyncDelegate<T> validate = null);
|
||||
|
||||
/// <summary>
|
||||
/// Define a step for filling in a particular value in the form state.
|
||||
/// </summary>
|
||||
/// <param name="name">Path in the form state to the value being filled in.</param>
|
||||
/// <param name="prompt">Prompt pattern with more formatting control to describe prompt for field.</param>
|
||||
/// <param name="active">Delegate to test form state to see if step is active.n</param>
|
||||
/// <param name="validate">Delegate to validate the field value.</param>
|
||||
/// <returns>Modified IFormBuilder.</returns>
|
||||
IFormBuilder<T> Field(string name, PromptAttribute prompt, ActiveDelegate<T> active = null, ValidateAsyncDelegate<T> validate = null);
|
||||
|
||||
/// <summary>
|
||||
/// Add all fields not already added to the form.
|
||||
/// </summary>
|
||||
/// <param name="exclude">Fields not to include.</param>
|
||||
/// <returns>Modified IFormBuilder.</returns>
|
||||
/// <remarks>
|
||||
/// This will add all fields defined in your form that have not already been
|
||||
/// added if the fields are supported.
|
||||
/// </remarks>
|
||||
IFormBuilder<T> AddRemainingFields(IEnumerable<string> exclude = null);
|
||||
|
||||
/// <summary>
|
||||
/// Add a confirmation step.
|
||||
/// </summary>
|
||||
/// <param name="prompt">Prompt to use for confirmation.</param>
|
||||
/// <param name="condition">Delegate to test if confirmation applies to the current form state.</param>
|
||||
/// <param name="dependencies">What fields this confirmation depends on.</param>
|
||||
/// <returns>Modified IFormBuilder.</returns>
|
||||
/// <remarks>
|
||||
/// If prompt is not supplied the \ref patterns element {*} will be used to confirm.
|
||||
/// Dependencies will by default be all active steps defined before this confirmation.
|
||||
/// </remarks>
|
||||
IFormBuilder<T> Confirm(string prompt = null, ActiveDelegate<T> condition = null, IEnumerable<string> dependencies = null);
|
||||
|
||||
/// <summary>
|
||||
/// Add a confirmation step.
|
||||
/// </summary>
|
||||
/// <param name="prompt">Prompt to use for confirmation.</param>
|
||||
/// <param name="condition">Delegate to test if confirmation applies to the current form state.</param>
|
||||
/// <param name="dependencies">What fields this confirmation depends on.</param>
|
||||
/// <returns>Modified IFormBuilder.</returns>
|
||||
/// <remarks>
|
||||
/// Dependencies will by default be all active steps defined before this confirmation.
|
||||
/// </remarks>
|
||||
IFormBuilder<T> Confirm(PromptAttribute prompt, ActiveDelegate<T> condition = null, IEnumerable<string> dependencies = null);
|
||||
|
||||
#region Documentation
|
||||
/// <summary> Generate a confirmation using a delegate to dynamically build the message. </summary>
|
||||
/// <param name="generateMessage"> Delegate for building message. </param>
|
||||
/// <param name="condition"> Whether or not this step is active. </param>
|
||||
/// <param name="dependencies">What fields this confirmation depends on.</param>
|
||||
/// <returns>Modified IFormBuilder.</returns>
|
||||
#endregion
|
||||
IFormBuilder<T> Confirm(MessageDelegate<T> generateMessage, ActiveDelegate<T> condition = null, IEnumerable<string> dependencies = null);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Delegate to send prompt to user.
|
||||
/// </summary>
|
||||
/// <param name="prompter">Delegate.</param>
|
||||
/// <returns>Modified IFormBuilder.</returns>
|
||||
IFormBuilder<T> Prompter(PromptAsyncDelegate<T> prompter);
|
||||
|
||||
/// <summary>
|
||||
/// Delegate to call when form is completed.
|
||||
/// </summary>
|
||||
/// <param name="callback">Delegate to call on completion.</param>
|
||||
/// <returns>Modified IFormBuilder.</returns>
|
||||
/// <remarks>
|
||||
/// This should only be used for side effects such as calling your service with
|
||||
/// the form state results. In any case the completed form state will be passed
|
||||
/// to the parent dialog.
|
||||
/// </remarks>
|
||||
IFormBuilder<T> OnCompletion(OnCompletionAsyncDelegate<T> callback);
|
||||
|
||||
/// <summary>
|
||||
/// Test to see if there is already a field with <paramref name="name"/>.
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <returns>True if field is already present.</returns>
|
||||
bool HasField(string name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default values for the form.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// These defaults can all be overridden when you create a form and before you add steps.
|
||||
/// </remarks>
|
||||
public class FormConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Construct configuration.
|
||||
/// </summary>
|
||||
public FormConfiguration()
|
||||
{
|
||||
DefaultPrompt.IsLocalizable = false;
|
||||
foreach (var template in Templates)
|
||||
{
|
||||
template.IsLocalizable = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default prompt and template format settings.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When you specify a <see cref="PromptAttribute"/> or <see cref="TemplateAttribute"/>, any format
|
||||
/// value you do not specify will come from this default.
|
||||
/// </remarks>
|
||||
public PromptAttribute DefaultPrompt = new PromptAttribute("")
|
||||
{
|
||||
AllowDefault = BoolDefault.True,
|
||||
ChoiceCase = CaseNormalization.None,
|
||||
ChoiceFormat = Resources.DefaultChoiceFormat,
|
||||
ChoiceLastSeparator = Resources.DefaultChoiceLastSeparator,
|
||||
ChoiceParens = BoolDefault.True,
|
||||
ChoiceSeparator = Resources.DefaultChoiceSeparator,
|
||||
ChoiceStyle = ChoiceStyleOptions.Auto,
|
||||
FieldCase = CaseNormalization.Lower,
|
||||
Feedback = FeedbackOptions.Auto,
|
||||
LastSeparator = Resources.DefaultLastSeparator,
|
||||
Separator = Resources.DefaultSeparator,
|
||||
ValueCase = CaseNormalization.InitialUpper
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Enumeration of strings for interpreting a user response as setting an optional field to be unspecified.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The first string is also used to describe not having a preference for an optional field.
|
||||
/// </remarks>
|
||||
public string[] NoPreference = Resources.MatchNoPreference.SplitList();
|
||||
|
||||
/// <summary>
|
||||
/// Enumeration of strings for interpreting a user response as asking for the current value.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The first value is also used to describe the option of keeping the current value.
|
||||
/// </remarks>
|
||||
public string[] CurrentChoice = Resources.MatchCurrentChoice.SplitList();
|
||||
|
||||
/// <summary>
|
||||
/// Enumeration of values for a "yes" response for boolean fields or confirmations.
|
||||
/// </summary>
|
||||
public string[] Yes = Resources.MatchYes.SplitList();
|
||||
|
||||
/// <summary>
|
||||
/// Enumeration of values for a "no" response for boolean fields or confirmations.
|
||||
/// </summary>
|
||||
public string[] No = Resources.MatchNo.SplitList();
|
||||
|
||||
/// <summary>
|
||||
/// String for naming the "navigation" field.
|
||||
/// </summary>
|
||||
public string Navigation = Resources.Navigation;
|
||||
|
||||
/// <summary>
|
||||
/// String for naming "Confirmation" fields.
|
||||
/// </summary>
|
||||
public string Confirmation = Resources.Confirmation;
|
||||
|
||||
/// <summary>
|
||||
/// Default templates to use if not override on the class or field level.
|
||||
/// </summary>
|
||||
public List<TemplateAttribute> Templates = new List<TemplateAttribute>
|
||||
{
|
||||
new TemplateAttribute(TemplateUsage.Bool, Resources.TemplateBool),
|
||||
// {0} is current choice, {1} is no preference
|
||||
new TemplateAttribute(TemplateUsage.BoolHelp, Resources.TemplateBoolHelp),
|
||||
|
||||
// {0} is term being clarified
|
||||
new TemplateAttribute(TemplateUsage.Clarify, Resources.TemplateClarify),
|
||||
|
||||
new TemplateAttribute(TemplateUsage.Confirmation, Resources.TemplateConfirmation),
|
||||
|
||||
new TemplateAttribute(TemplateUsage.CurrentChoice, Resources.TemplateCurrentChoice),
|
||||
|
||||
new TemplateAttribute(TemplateUsage.DateTime, Resources.TemplateDateTime),
|
||||
// {0} is current choice, {1} is no preference
|
||||
// new TemplateAttribute(TemplateUsage.DateTimeHelp, "Please enter a date or time expression like 'Monday' or 'July 3rd'{?, {0}}{?, {1}}."),
|
||||
new TemplateAttribute(TemplateUsage.DateTimeHelp, Resources.TemplateDateTimeHelp),
|
||||
|
||||
// {0} is min and {1} is max.
|
||||
new TemplateAttribute(TemplateUsage.Double, Resources.TemplateDouble) { ChoiceFormat = Resources.TemplateDoubleChoiceFormat },
|
||||
// {0} is current choice, {1} is no preference
|
||||
// {2} is min and {3} is max
|
||||
new TemplateAttribute(TemplateUsage.DoubleHelp, Resources.TemplateDoubleHelp),
|
||||
|
||||
// {0} is min, {1} is max and {2} are enumerated descriptions
|
||||
new TemplateAttribute(TemplateUsage.EnumManyNumberHelp, Resources.TemplateEnumManyNumberHelp),
|
||||
new TemplateAttribute(TemplateUsage.EnumOneNumberHelp, Resources.TemplateEnumOneNumberHelp),
|
||||
|
||||
// {2} are the words people can type
|
||||
new TemplateAttribute(TemplateUsage.EnumManyWordHelp, Resources.TemplateEnumManyWordHelp),
|
||||
new TemplateAttribute(TemplateUsage.EnumOneWordHelp, Resources.TemplateEnumOneWordHelp),
|
||||
|
||||
new TemplateAttribute(TemplateUsage.EnumSelectOne, Resources.TemplateEnumSelectOne),
|
||||
new TemplateAttribute(TemplateUsage.EnumSelectMany, Resources.TemplateEnumSelectMany),
|
||||
|
||||
// {0} is the not understood term
|
||||
new TemplateAttribute(TemplateUsage.Feedback, Resources.TemplateFeedback),
|
||||
|
||||
// For {0} is recognizer help and {1} is command help.
|
||||
new TemplateAttribute(TemplateUsage.Help, Resources.TemplateHelp),
|
||||
new TemplateAttribute(TemplateUsage.HelpClarify, Resources.TemplateHelpClarify),
|
||||
new TemplateAttribute(TemplateUsage.HelpConfirm, Resources.TemplateHelpConfirm),
|
||||
new TemplateAttribute(TemplateUsage.HelpNavigation, Resources.TemplateHelpNavigation),
|
||||
|
||||
// {0} is min and {1} is max if present
|
||||
new TemplateAttribute(TemplateUsage.Integer, Resources.TemplateInteger) { ChoiceFormat = Resources.TemplateIntegerChoiceFormat },
|
||||
// {0} is current choice, {1} is no preference
|
||||
// {2} is min and {3} is max
|
||||
new TemplateAttribute(TemplateUsage.IntegerHelp, Resources.TemplateIntegerHelp),
|
||||
|
||||
new TemplateAttribute(TemplateUsage.Navigation, Resources.TemplateNavigation) { FieldCase = CaseNormalization.None },
|
||||
// {0} is list of field names.
|
||||
new TemplateAttribute(TemplateUsage.NavigationCommandHelp, Resources.TemplateNavigationCommandHelp),
|
||||
new TemplateAttribute(TemplateUsage.NavigationFormat, Resources.TemplateNavigationFormat) {FieldCase = CaseNormalization.None },
|
||||
// {0} is min, {1} is max
|
||||
new TemplateAttribute(TemplateUsage.NavigationHelp, Resources.TemplateNavigationHelp),
|
||||
|
||||
new TemplateAttribute(TemplateUsage.NoPreference, Resources.TemplateNoPreference),
|
||||
|
||||
// {0} is the term that is not understood
|
||||
new TemplateAttribute(TemplateUsage.NotUnderstood, Resources.TemplateNotUnderstood),
|
||||
|
||||
new TemplateAttribute(TemplateUsage.StatusFormat, Resources.TemplateStatusFormat) {FieldCase = CaseNormalization.None },
|
||||
|
||||
new TemplateAttribute(TemplateUsage.String, Resources.TemplateString) { ChoiceFormat = Resources.TemplateStringChoiceFormat },
|
||||
// {0} is current choice, {1} is no preference
|
||||
new TemplateAttribute(TemplateUsage.StringHelp, Resources.TemplateStringHelp),
|
||||
|
||||
new TemplateAttribute(TemplateUsage.Unspecified, Resources.TemplateUnspecified)
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Definitions of the built-in commands.
|
||||
/// </summary>
|
||||
public Dictionary<FormCommand, CommandDescription> Commands = new Dictionary<FormCommand, CommandDescription>()
|
||||
{
|
||||
{FormCommand.Backup, new CommandDescription(
|
||||
Resources.CommandBack,
|
||||
Resources.CommandBackTerms.SplitList(),
|
||||
Resources.CommandBackHelp) },
|
||||
{FormCommand.Help, new CommandDescription(
|
||||
Resources.CommandHelp,
|
||||
Resources.CommandHelpTerms.SplitList(),
|
||||
Resources.CommandHelpHelp) },
|
||||
{FormCommand.Quit, new CommandDescription(
|
||||
Resources.CommandQuit,
|
||||
Resources.CommandQuitTerms.SplitList(),
|
||||
Resources.CommandQuitHelp) },
|
||||
{FormCommand.Reset, new CommandDescription(
|
||||
Resources.CommandReset,
|
||||
Resources.CommandResetTerms.SplitList(),
|
||||
Resources.CommandResetHelp)},
|
||||
{FormCommand.Status, new CommandDescription(
|
||||
Resources.CommandStatus,
|
||||
Resources.CommandStatusTerms.SplitList(),
|
||||
Resources.CommandStatusHelp) }
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Look up a particular template.
|
||||
/// </summary>
|
||||
/// <param name="usage">Desired template.</param>
|
||||
/// <returns>Matching template.</returns>
|
||||
public TemplateAttribute Template(TemplateUsage usage)
|
||||
{
|
||||
TemplateAttribute result = null;
|
||||
foreach (var template in Templates)
|
||||
{
|
||||
if (template.Usage == usage)
|
||||
{
|
||||
result = template;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Debug.Assert(result != null);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
}
|
|
@ -0,0 +1,255 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using Microsoft.Bot.Builder.V3Bridge.Dialogs;
|
||||
using Microsoft.Bot.Builder.V3Bridge.FormFlow.Advanced;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.FormFlow
|
||||
{
|
||||
/// <summary>
|
||||
/// A delegate for testing a form state to see if a particular step is active.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Form state type.</typeparam>
|
||||
/// <param name="state">Form state to test.</param>
|
||||
/// <returns>True if step is active given the current form state.</returns>
|
||||
public delegate bool ActiveDelegate<T>(T state);
|
||||
|
||||
/// <summary>
|
||||
/// Choice for clarifying an ambiguous value in <see cref="ValidateResult"/>.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class Choice
|
||||
{
|
||||
/// <summary>
|
||||
/// Value to return if choice is selected.
|
||||
/// </summary>
|
||||
public object Value;
|
||||
|
||||
/// <summary>
|
||||
/// Description of value.
|
||||
/// </summary>
|
||||
public DescribeAttribute Description;
|
||||
|
||||
/// <summary>
|
||||
/// Terms to match value.
|
||||
/// </summary>
|
||||
public TermsAttribute Terms;
|
||||
}
|
||||
|
||||
/// <summary> Encapsulates the result of a <see cref="ValidateAsyncDelegate{T}"/> </summary>
|
||||
/// <remarks>
|
||||
/// If <see cref="IsValid"/> is true, then the field will be set to <see cref="Value"/>.
|
||||
/// Otherwise if <see cref="Choices"/> is non-null they will be used to select a clarifying value.
|
||||
/// if <see cref="FeedbackCard"/> is non-null the resulting card will be displayed.
|
||||
/// Otherwise the <see cref="Feedback"/> string will be shown to provide feedback on the value.
|
||||
/// </remarks>
|
||||
public class ValidateResult
|
||||
{
|
||||
/// <summary> Feedback to provide back to the user on the input. </summary>
|
||||
public string Feedback;
|
||||
|
||||
/// <summary>
|
||||
/// Fully specified feedback card.
|
||||
/// </summary>
|
||||
public FormPrompt FeedbackCard;
|
||||
|
||||
/// <summary> True if value is a valid response. </summary>
|
||||
public bool IsValid;
|
||||
|
||||
/// <summary>
|
||||
/// Value to put in the field if result is valid.
|
||||
/// </summary>
|
||||
/// <remarks>This provides an opportunity for validation to compute the final value.</remarks>
|
||||
public object Value;
|
||||
|
||||
/// <summary>
|
||||
/// Choices for clarifying response.
|
||||
/// </summary>
|
||||
public IEnumerable<Choice> Choices;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A delegate for validating a particular response to a prompt.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Form state type.</typeparam>
|
||||
/// <param name="state">Form state to test.</param>
|
||||
/// <param name="value">Response value to validate.</param>
|
||||
/// <returns><see cref="ValidateResult"/> describing validity, transformed value, feedback or choices for clarification.</returns>
|
||||
public delegate Task<ValidateResult> ValidateAsyncDelegate<T>(T state, object value);
|
||||
|
||||
/// <summary>
|
||||
/// A delegate called when a form is completed.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Form state type.</typeparam>
|
||||
/// <param name="context">Session where form dialog is taking place.</param>
|
||||
/// <param name="state">Completed form state.</param>
|
||||
/// <remarks>
|
||||
/// This delegate gives an opportunity to take an action on a completed form
|
||||
/// such as sending it to your service. It cannot be used to create a new
|
||||
/// dialog or return a value to the parent dialog.
|
||||
/// </remarks>
|
||||
public delegate Task OnCompletionAsyncDelegate<T>(IDialogContext context, T state);
|
||||
|
||||
/// <summary>
|
||||
/// Interface for controlling a FormFlow dialog.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Form state type.</typeparam>
|
||||
/// <remarks>
|
||||
/// <see cref="FormDialog{T}"/> is an implementation of this interface.
|
||||
/// </remarks>
|
||||
/// <exception cref="FormCanceledException{T}">Thrown when the user quits while filling in a form, or there is an underlying exception in the code.</exception>
|
||||
public interface IFormDialog<T> : IDialog<T>
|
||||
where T : class
|
||||
{
|
||||
/// <summary>
|
||||
/// The form specification.
|
||||
/// </summary>
|
||||
IForm<T> Form { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Commands supported in form dialogs.
|
||||
/// </summary>
|
||||
public enum FormCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Move back to the previous step.
|
||||
/// </summary>
|
||||
Backup,
|
||||
|
||||
/// <summary>
|
||||
/// Ask for help on responding to the current field.
|
||||
/// </summary>
|
||||
Help,
|
||||
|
||||
/// <summary>
|
||||
/// Quit filling in the current form and return failure to parent dialog.
|
||||
/// </summary>
|
||||
Quit,
|
||||
|
||||
/// <summary>
|
||||
/// Reset the status of the form dialog.
|
||||
/// </summary>
|
||||
Reset,
|
||||
|
||||
/// <summary>
|
||||
/// Provide feedback to the user on the current form state.
|
||||
/// </summary>
|
||||
Status
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Description of all the information needed for a built-in command.
|
||||
/// </summary>
|
||||
public class CommandDescription
|
||||
{
|
||||
/// <summary>
|
||||
/// Description of the command.
|
||||
/// </summary>
|
||||
public string Description;
|
||||
|
||||
/// <summary>
|
||||
/// Regexs for matching the command.
|
||||
/// </summary>
|
||||
public string[] Terms;
|
||||
|
||||
/// <summary>
|
||||
/// Help string for the command.
|
||||
/// </summary>
|
||||
public string Help;
|
||||
|
||||
/// <summary>
|
||||
/// Construct the description of a built-in command.
|
||||
/// </summary>
|
||||
/// <param name="description">Description of the command.</param>
|
||||
/// <param name="terms">Terms that match the command.</param>
|
||||
/// <param name="help">Help on what the command does.</param>
|
||||
public CommandDescription(string description, string[] terms, string help)
|
||||
{
|
||||
Description = description;
|
||||
Terms = terms;
|
||||
Help = help;
|
||||
}
|
||||
}
|
||||
|
||||
#region Documentation
|
||||
/// <summary> Exception generated when form filling is canceled by user quit or exception. </summary>
|
||||
/// <remarks>In the case of user quit or an exception the strongly typed exception <see cref="FormCanceledException{T}"/>
|
||||
/// is actually thrown, but this provides simple access to the Last step.</remarks>
|
||||
#endregion
|
||||
[Serializable]
|
||||
public class FormCanceledException : OperationCanceledException
|
||||
{
|
||||
#region Documentation
|
||||
/// <summary> Constructor with message and inner exception. </summary>
|
||||
/// <param name="message">Exception message.</param>
|
||||
/// <param name="inner">Inner exception.</param>
|
||||
/// <remarks>In the case of quit by the user, the inner exception will be null.</remarks>
|
||||
#endregion
|
||||
public FormCanceledException(string message, Exception inner)
|
||||
: base(message, inner)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary> The names of completed steps. </summary>
|
||||
public IEnumerable<string> Completed { get; internal set; }
|
||||
|
||||
/// <summary> Name of the step that quit or threw an exception. </summary>
|
||||
public string Last { get; internal set; }
|
||||
}
|
||||
|
||||
#region Documentation
|
||||
/// <summary> Exception generated when form filling is canceled by user quit or exception. </summary>
|
||||
/// <typeparam name="T"> Underlying form type. </typeparam>
|
||||
#endregion
|
||||
[Serializable]
|
||||
public class FormCanceledException<T> : FormCanceledException
|
||||
{
|
||||
/// <summary> Constructor with message and inner exception. </summary>
|
||||
/// <param name="message">Exception message.</param>
|
||||
/// <param name="inner">Inner exception.</param>
|
||||
/// <remarks>In the case of user quit, the inner exception will be null.</remarks>
|
||||
public FormCanceledException(string message, Exception inner = null)
|
||||
: base(message, inner)
|
||||
{
|
||||
LastForm = default(T);
|
||||
}
|
||||
|
||||
/// <summary> Gets the partial form when the user quits or there is an exception. </summary>
|
||||
public T LastForm { get; internal set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,163 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Resources;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.FormFlow.Advanced
|
||||
{
|
||||
#region Documentation
|
||||
/// <summary> Interface for localizing string resources. </summary>
|
||||
#endregion
|
||||
public interface ILocalizer
|
||||
{
|
||||
/// <summary>
|
||||
/// Return the localizer culture.
|
||||
/// </summary>
|
||||
/// <returns>Current culture.</returns>
|
||||
CultureInfo Culture { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Add a key and its translation.
|
||||
/// </summary>
|
||||
/// <param name="key">Key for indexing translation.</param>
|
||||
/// <param name="translation">Translation for key.</param>
|
||||
void Add(string key, string translation);
|
||||
|
||||
/// <summary>
|
||||
/// Add a key and a list of translations separated by semi-colon.
|
||||
/// </summary>
|
||||
/// <param name="key">Key for indexing translation list.</param>
|
||||
/// <param name="list">List of translated terms.</param>
|
||||
void Add(string key, IEnumerable<string> list);
|
||||
|
||||
#region Documentation
|
||||
/// <summary> Adds value from dictionary under object if enumeration and prefix;object otherwise. </summary>
|
||||
/// <param name="prefix"> The resource prefix. </param>
|
||||
/// <param name="dictionary"> The dictionary to add. </param>
|
||||
#endregion
|
||||
void Add(string prefix, IReadOnlyDictionary<object, DescribeAttribute> dictionary);
|
||||
|
||||
#region Documentation
|
||||
/// <summary> Adds values from dictionary separated by semi-colons under object if enumeration and prefix;object otherwise.</summary>
|
||||
/// <param name="prefix"> The resource prefix. </param>
|
||||
/// <param name="dictionary"> The dictionary to add. </param>
|
||||
#endregion
|
||||
void Add(string prefix, IReadOnlyDictionary<object, TermsAttribute> dictionary);
|
||||
|
||||
#region Documentation
|
||||
/// <summary> Adds patterns from template separated by semi-colons under prefix;usage. </summary>
|
||||
/// <param name="prefix"> The resource prefix. </param>
|
||||
/// <param name="templates"> The template dictionary to add. </param>
|
||||
#endregion
|
||||
void Add(string prefix, IReadOnlyDictionary<TemplateUsage, TemplateAttribute> templates);
|
||||
|
||||
#region Documentation
|
||||
/// <summary> Adds patterns from template separated by semi-colons under prefix;usage.</summary>
|
||||
/// <param name="prefix"> The resource prefix. </param>
|
||||
/// <param name="template"> The template to add. </param>
|
||||
#endregion
|
||||
void Add(string prefix, TemplateAttribute template);
|
||||
|
||||
/// <summary>
|
||||
/// Translate a key to a translation.
|
||||
/// </summary>
|
||||
/// <param name="key">Key to lookup.</param>
|
||||
/// <param name="value">Value to set if present.</param>
|
||||
/// <returns>True if value is found. </returns>
|
||||
bool Lookup(string key, out string value);
|
||||
|
||||
/// <summary>
|
||||
/// Translate a key to an array of values.
|
||||
/// </summary>
|
||||
/// <param name="key">Key to lookup.</param>
|
||||
/// <param name="values">Array value to set if present.</param>
|
||||
/// <returns>True if value is found. </returns>
|
||||
bool LookupValues(string key, out string[] values);
|
||||
|
||||
#region Documentation
|
||||
/// <summary> Look up prefix;object from dictionary and replace value from localizer. </summary>
|
||||
/// <param name="prefix"> The prefix. </param>
|
||||
/// <param name="dictionary"> Dictionary with existing values. </param>
|
||||
#endregion
|
||||
void LookupDictionary(string prefix, IDictionary<object, DescribeAttribute> dictionary);
|
||||
|
||||
#region Documentation
|
||||
/// <summary> Look up prefix;object from dictionary and replace values from localizer. </summary>
|
||||
/// <param name="prefix"> The prefix. </param>
|
||||
/// <param name="dictionary"> Dictionary with existing values. </param>
|
||||
#endregion
|
||||
void LookupDictionary(string prefix, IDictionary<object, TermsAttribute> dictionary);
|
||||
|
||||
#region Documentation
|
||||
/// <summary> Looks up prefix;usage and replace patterns in template from localizer. </summary>
|
||||
/// <param name="prefix"> The prefix. </param>
|
||||
/// <param name="templates"> Template dictionary with existing values. </param>
|
||||
#endregion
|
||||
void LookupTemplates(string prefix, IDictionary<TemplateUsage, TemplateAttribute> templates);
|
||||
|
||||
/// <summary>
|
||||
/// Remove a key from the localizer.
|
||||
/// </summary>
|
||||
/// <param name="key">Key to remove.</param>
|
||||
void Remove(string key);
|
||||
|
||||
/// <summary>
|
||||
/// Save localizer resources to stream.
|
||||
/// </summary>
|
||||
/// <param name="writer">Where to write resources.</param>
|
||||
/// <remarks>
|
||||
/// Resource values are all strings. The key and value can have different parts separated by semi-colons.
|
||||
/// Key | Value | Description
|
||||
/// ----|-------|------------
|
||||
/// key;VALUE | string | Simple value.
|
||||
/// key;LIST | string[;string]* | List of values.
|
||||
/// usage;field[;field]*;TEMPLATE | pattern[;pattern]* | List of template patterns. Key includes fields that use template.
|
||||
/// </remarks>
|
||||
void Save(IResourceWriter writer);
|
||||
|
||||
/// <summary>
|
||||
/// Load the localizer from a stream.
|
||||
/// </summary>
|
||||
/// <param name="reader">Dictionary with resources.</param>
|
||||
/// <param name="missing">Keys found in current localizer that are not in loaded localizer.</param>
|
||||
/// <param name="extra">Keys found in loaded localizer that were not in current localizer.</param>
|
||||
/// <returns>New localizer from reader.</returns>
|
||||
/// <remarks>
|
||||
/// <see cref="Save(IResourceWriter)"/> to see resource format.
|
||||
/// </remarks>
|
||||
ILocalizer Load(IDictionaryEnumerator reader, out IEnumerable<string> missing, out IEnumerable<string> extra);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,718 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using Microsoft.Bot.Builder.V3Bridge.Resource;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Dialogs;
|
||||
using Microsoft.Bot.Builder.V3Bridge.Resource;
|
||||
using Microsoft.Bot.Schema;
|
||||
using Microsoft.Bot.Schema;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization.Formatters.Binary;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.FormFlow.Advanced
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Interface for a prompt and its associated recognizer.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Form state.</typeparam>
|
||||
/// <remarks>
|
||||
/// This interface allows taking a \ref patterns expression and making it into a string with the template parts filled in.
|
||||
/// </remarks>
|
||||
public interface IPrompt<T>
|
||||
where T : class
|
||||
{
|
||||
/// <summary>
|
||||
/// Description of the prompt and how to generate it.
|
||||
/// </summary>
|
||||
/// <returns>Attribute describing how to generate prompt.</returns>
|
||||
TemplateBaseAttribute Annotation { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Return prompt to send to user.
|
||||
/// </summary>
|
||||
/// <param name="state">Current form state.</param>
|
||||
/// <param name="field">Current field being processed.</param>
|
||||
/// <param name="args">Optional arguments.</param>
|
||||
/// <returns>Message to user.</returns>
|
||||
FormPrompt Prompt(T state, IField<T> field, params object[] args);
|
||||
|
||||
/// <summary>
|
||||
/// Associated recognizer if any.
|
||||
/// </summary>
|
||||
/// <returns>Recognizer for matching user input.</returns>
|
||||
IRecognize<T> Recognizer { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The prompt that is returned by form prompter.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public sealed class FormPrompt : ICloneable
|
||||
{
|
||||
/// <summary>
|
||||
/// The text prompt that corresponds to Message.Text.
|
||||
/// </summary>
|
||||
/// <remarks>When generating cards this will be the card title.</remarks>
|
||||
public string Prompt { set; get; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Description information for generating cards.
|
||||
/// </summary>
|
||||
public DescribeAttribute Description { set; get; }
|
||||
|
||||
/// <summary>
|
||||
/// The buttons that will be mapped to Message.Attachments.
|
||||
/// </summary>
|
||||
public IList<DescribeAttribute> Buttons { set; get; } = new List<DescribeAttribute>();
|
||||
|
||||
/// <summary>
|
||||
/// Desired prompt style.
|
||||
/// </summary>
|
||||
public ChoiceStyleOptions Style;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Prompt} {Language.BuildList(Buttons.Select(button => button.ToString()), Resources.DefaultChoiceSeparator, Resources.DefaultChoiceLastSeparator)}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deep clone the FormPrompt.
|
||||
/// </summary>
|
||||
/// <returns> A deep cloned instance of FormPrompt.</returns>
|
||||
public object Clone()
|
||||
{
|
||||
var newPrompt = new FormPrompt();
|
||||
newPrompt.Prompt = this.Prompt;
|
||||
newPrompt.Description = this.Description;
|
||||
newPrompt.Buttons = new List<DescribeAttribute>(this.Buttons);
|
||||
newPrompt.Style = this.Style;
|
||||
return newPrompt;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A Form button that will be mapped to Connector.Action.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public sealed class FormButton : ICloneable
|
||||
{
|
||||
/// <summary>
|
||||
/// Picture which will appear on the button.
|
||||
/// </summary>
|
||||
public string Image { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Message that will be sent to bot when this button is clicked.
|
||||
/// </summary>
|
||||
public string Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Label of the button.
|
||||
/// </summary>
|
||||
public string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// URL which will be opened in the browser built-into Client application.
|
||||
/// </summary>
|
||||
public string Url { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Clone the FormButton
|
||||
/// </summary>
|
||||
/// <returns> A new cloned instance of object.</returns>
|
||||
public object Clone()
|
||||
{
|
||||
return new FormButton
|
||||
{
|
||||
Image = this.Image,
|
||||
Message = this.Message,
|
||||
Title = this.Title,
|
||||
Url = this.Url
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ToString() override.
|
||||
/// </summary>
|
||||
/// <returns> Title of the button.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return Title;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A delegate for styling and posting a prompt.
|
||||
/// </summary>
|
||||
/// <param name="context">Message context.</param>
|
||||
/// <param name="prompt">Prompt to be posted.</param>
|
||||
/// <param name="state">State of the form.</param>
|
||||
/// <param name="field">Field being prompted or null if not a field.</param>
|
||||
/// <returns>Prompt that was posted.</returns>
|
||||
public delegate Task<FormPrompt> PromptAsyncDelegate<T>(IDialogContext context, FormPrompt prompt, T state, IField<T> field)
|
||||
where T : class;
|
||||
|
||||
public static partial class Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Generate a hero card from a FormPrompt.
|
||||
/// </summary>
|
||||
/// <param name="prompt">Prompt definition.</param>
|
||||
/// <returns>Either an empty list if no buttons or a list with one hero card.</returns>
|
||||
public static IList<Attachment> GenerateHeroCard(this FormPrompt prompt)
|
||||
{
|
||||
var actions = new List<CardAction>();
|
||||
foreach (var button in prompt.Buttons)
|
||||
{
|
||||
actions.Add(new CardAction(ActionTypes.ImBack, button.Description, button.Image, button.Message ?? button.Description));
|
||||
}
|
||||
|
||||
var attachments = new List<Attachment>();
|
||||
if (actions.Count > 0)
|
||||
{
|
||||
var description = prompt.Description;
|
||||
// Facebook requires a title https://github.com/Microsoft/BotBuilder/issues/1678
|
||||
attachments.Add(new HeroCard(text: prompt.Prompt, title: description.Title ?? string.Empty, subtitle: description.SubTitle,
|
||||
buttons: actions,
|
||||
images: prompt.Description?.Image == null ? null : new List<CardImage>() { new CardImage() { Url = description.Image } })
|
||||
.ToAttachment());
|
||||
}
|
||||
return attachments;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a list of hero cards from a prompt definition.
|
||||
/// </summary>
|
||||
/// <param name="prompt">Prompt definition.</param>
|
||||
/// <returns>List of hero cards.</returns>
|
||||
public static IList<Attachment> GenerateHeroCards(this FormPrompt prompt)
|
||||
{
|
||||
var attachments = new List<Attachment>();
|
||||
var description = prompt.Description;
|
||||
foreach (var button in prompt.Buttons)
|
||||
{
|
||||
string image = button.Image ?? description.Image;
|
||||
attachments.Add(new HeroCard(
|
||||
title: button.Title ?? description.Title ?? string.Empty,
|
||||
subtitle: button.SubTitle ?? description.SubTitle,
|
||||
text: prompt.Prompt,
|
||||
images: (image == null ? null : (new List<CardImage>() { new CardImage() { Url = image } })),
|
||||
buttons: new List<CardAction>() { new CardAction(ActionTypes.ImBack, button.Description, null, button.Message ?? button.Description) })
|
||||
.ToAttachment());
|
||||
}
|
||||
return attachments;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a prompt definition generate messages to send back.
|
||||
/// </summary>
|
||||
/// <param name="prompt">Prompt definition.</param>
|
||||
/// <param name="preamble">Simple text message with all except last line of prompt to allow markdown in prompts.</param>
|
||||
/// <param name="promptMessage">Message with prompt definition including cards.</param>
|
||||
/// <returns>True if preamble should be sent.</returns>
|
||||
public static bool GenerateMessages(this FormPrompt prompt, IMessageActivity preamble, IMessageActivity promptMessage)
|
||||
{
|
||||
var promptCopy = (FormPrompt) prompt.Clone();
|
||||
bool hasPreamble = false;
|
||||
if (promptCopy.Buttons?.Count > 0 || promptCopy.Description?.Image != null)
|
||||
{
|
||||
// If we are generating cards we do not support markdown so create a separate message
|
||||
// for all lines except the last one.
|
||||
var lines = promptCopy.Prompt.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
|
||||
if (lines.Length > 1)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
for (var i = 0; i < lines.Length - 1; ++i)
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
builder.AppendLine();
|
||||
}
|
||||
builder.Append(lines[i]);
|
||||
}
|
||||
preamble.Text = builder.ToString();
|
||||
promptCopy.Prompt = lines.Last();
|
||||
hasPreamble = true;
|
||||
}
|
||||
if (promptCopy.Buttons?.Count > 0)
|
||||
{
|
||||
var style = promptCopy.Style;
|
||||
if (style == ChoiceStyleOptions.Auto)
|
||||
{
|
||||
foreach (var button in promptCopy.Buttons)
|
||||
{
|
||||
// Images require carousel
|
||||
if (button.Image != null)
|
||||
{
|
||||
style = ChoiceStyleOptions.Carousel;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (style == ChoiceStyleOptions.Carousel)
|
||||
{
|
||||
promptMessage.AttachmentLayout = AttachmentLayoutTypes.Carousel;
|
||||
promptMessage.Attachments = promptCopy.GenerateHeroCards();
|
||||
}
|
||||
else
|
||||
{
|
||||
promptMessage.AttachmentLayout = AttachmentLayoutTypes.List;
|
||||
promptMessage.Attachments = promptCopy.GenerateHeroCard();
|
||||
}
|
||||
}
|
||||
else if (promptCopy.Description?.Image != null)
|
||||
{
|
||||
promptMessage.AttachmentLayout = AttachmentLayoutTypes.List;
|
||||
var card = new HeroCard() { Title = promptCopy.Prompt, Images = new List<CardImage> { new CardImage(promptCopy.Description.Image) } };
|
||||
promptMessage.Attachments = new List<Attachment> { card.ToAttachment() };
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
promptMessage.Text = promptCopy.Prompt;
|
||||
}
|
||||
return hasPreamble;
|
||||
}
|
||||
|
||||
internal static void AddRange<T>(this ICollection<T> collection, IEnumerable<T> enumerable)
|
||||
{
|
||||
foreach (var cur in enumerable)
|
||||
{
|
||||
collection.Add(cur);
|
||||
}
|
||||
}
|
||||
|
||||
internal static IList<T> Clone<T>(this IList<T> listToClone) where T : ICloneable
|
||||
{
|
||||
return listToClone.Select(item => (T)item.Clone()).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
#region Documentation
|
||||
/// <summary> A prompt and recognizer packaged together. </summary>
|
||||
/// <typeparam name="T"> UNderlying form type. </typeparam>
|
||||
#endregion
|
||||
public sealed class Prompter<T> : IPrompt<T>
|
||||
where T : class
|
||||
{
|
||||
/// <summary>
|
||||
/// Construct a prompter.
|
||||
/// </summary>
|
||||
/// <param name="annotation">Annotation describing the \ref patterns and formatting for prompt.</param>
|
||||
/// <param name="form">Current form.</param>
|
||||
/// <param name="recognizer">Recognizer if any.</param>
|
||||
/// <param name="fields">Fields name lookup. (Defaults to forms.)</param>
|
||||
public Prompter(TemplateBaseAttribute annotation, IForm<T> form, IRecognize<T> recognizer, IFields<T> fields = null)
|
||||
{
|
||||
annotation.ApplyDefaults(form.Configuration.DefaultPrompt);
|
||||
_annotation = annotation;
|
||||
_form = form;
|
||||
_fields = fields ?? form.Fields;
|
||||
_recognizer = recognizer;
|
||||
}
|
||||
|
||||
public TemplateBaseAttribute Annotation
|
||||
{
|
||||
get
|
||||
{
|
||||
return _annotation;
|
||||
}
|
||||
}
|
||||
|
||||
public FormPrompt Prompt(T state, IField<T> field, params object[] args)
|
||||
{
|
||||
string currentChoice = null;
|
||||
string noValue = null;
|
||||
if (field != null)
|
||||
{
|
||||
currentChoice = field.Template(TemplateUsage.CurrentChoice).Pattern();
|
||||
if (field.Optional)
|
||||
{
|
||||
noValue = field.Template(TemplateUsage.NoPreference).Pattern();
|
||||
}
|
||||
else
|
||||
{
|
||||
noValue = field.Template(TemplateUsage.Unspecified).Pattern();
|
||||
}
|
||||
}
|
||||
IList<DescribeAttribute> buttons = new List<DescribeAttribute>();
|
||||
var response = ExpandTemplate(_annotation.Pattern(), currentChoice, noValue, state, field, args, ref buttons);
|
||||
return new FormPrompt
|
||||
{
|
||||
Prompt = (response == null ? string.Empty : _spacesPunc.Replace(_spaces.Replace(Language.ANormalization(response), "$1 "), "$1")),
|
||||
Description = field?.FieldDescription,
|
||||
Buttons = buttons,
|
||||
Style = _annotation.ChoiceStyle
|
||||
};
|
||||
}
|
||||
|
||||
public IRecognize<T> Recognizer
|
||||
{
|
||||
get { return _recognizer; }
|
||||
}
|
||||
|
||||
#region Documentation
|
||||
/// <summary> Validate pattern by ensuring they refer to real fields. </summary>
|
||||
/// <param name="form"> The form. </param>
|
||||
/// <param name="pattern"> Specifies the pattern. </param>
|
||||
/// <param name="field"> Base field for pattern. </param>
|
||||
/// <param name="argLimit"> The number of arguments passed to the pattern. </param>
|
||||
/// <returns> true if it succeeds, false if it fails. </returns>
|
||||
#endregion
|
||||
public static bool ValidatePattern(IForm<T> form, string pattern, IField<T> field, int argLimit = 0)
|
||||
{
|
||||
bool ok = true;
|
||||
var fields = form.Fields;
|
||||
foreach (Match match in _args.Matches(pattern))
|
||||
{
|
||||
var expr = match.Groups[1].Value.Trim();
|
||||
int numeric;
|
||||
if (expr == "||")
|
||||
{
|
||||
ok = true;
|
||||
}
|
||||
else if (expr.StartsWith("&"))
|
||||
{
|
||||
var name = expr.Substring(1);
|
||||
if (name == string.Empty && field != null) name = field.Name;
|
||||
ok = (name == string.Empty || fields.Field(name) != null);
|
||||
}
|
||||
else if (expr.StartsWith("?"))
|
||||
{
|
||||
ok = ValidatePattern(form, expr.Substring(1), field, argLimit);
|
||||
}
|
||||
else if (expr.StartsWith("["))
|
||||
{
|
||||
if (expr.EndsWith("]"))
|
||||
{
|
||||
ok = ValidatePattern(form, expr.Substring(1, expr.Length - 2), field, argLimit);
|
||||
}
|
||||
else
|
||||
{
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
else if (expr.StartsWith("*"))
|
||||
{
|
||||
ok = (expr == "*" || expr == "*filled");
|
||||
}
|
||||
else if (TryParseFormat(expr, out numeric))
|
||||
{
|
||||
ok = numeric <= argLimit - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
var formatArgs = expr.Split(':');
|
||||
var name = formatArgs[0];
|
||||
if (name == string.Empty && field != null) name = field.Name;
|
||||
ok = (name == string.Empty || fields.Field(name) != null);
|
||||
}
|
||||
if (!ok)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
private string ExpandTemplate(string template, string currentChoice, string noValue, T state, IField<T> field, object[] args, ref IList<DescribeAttribute> buttons)
|
||||
{
|
||||
bool foundUnspecified = false;
|
||||
int last = 0;
|
||||
int numeric;
|
||||
var response = new StringBuilder();
|
||||
|
||||
foreach (Match match in _args.Matches(template))
|
||||
{
|
||||
var expr = match.Groups[1].Value.Trim();
|
||||
var substitute = string.Empty;
|
||||
if (expr.StartsWith("&"))
|
||||
{
|
||||
var name = expr.Substring(1);
|
||||
if (name == string.Empty && field != null) name = field.Name;
|
||||
var pathField = _fields.Field(name);
|
||||
substitute = Language.Normalize(pathField == null ? field.Name : pathField.FieldDescription.Description, _annotation.FieldCase);
|
||||
}
|
||||
else if (expr == "||")
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
var values = _recognizer.ValueDescriptions();
|
||||
var useButtons = !field.AllowsMultiple
|
||||
&& (_annotation.ChoiceStyle == ChoiceStyleOptions.Auto
|
||||
|| _annotation.ChoiceStyle == ChoiceStyleOptions.Buttons
|
||||
|| _annotation.ChoiceStyle == ChoiceStyleOptions.Carousel);
|
||||
if (values.Any() && _annotation.AllowDefault != BoolDefault.False && field.Optional)
|
||||
{
|
||||
values = values.Concat(new DescribeAttribute[] { new DescribeAttribute(Language.Normalize(noValue, _annotation.ChoiceCase)) });
|
||||
}
|
||||
string current = null;
|
||||
if (_annotation.AllowDefault != BoolDefault.False)
|
||||
{
|
||||
if (!field.Optional)
|
||||
{
|
||||
if (!field.IsUnknown(state))
|
||||
{
|
||||
current = ExpandTemplate(currentChoice, null, noValue, state, field, args, ref buttons);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
current = ExpandTemplate(currentChoice, null, noValue, state, field, args, ref buttons);
|
||||
}
|
||||
}
|
||||
if (values.Any())
|
||||
{
|
||||
if (useButtons)
|
||||
{
|
||||
foreach (var value in values)
|
||||
{
|
||||
buttons.Add(value);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Buttons do not support multiple selection so we fall back to text
|
||||
if (((_annotation.ChoiceStyle == ChoiceStyleOptions.Auto || _annotation.ChoiceStyle == ChoiceStyleOptions.AutoText)
|
||||
&& values.Count() < 4)
|
||||
|| (_annotation.ChoiceStyle == ChoiceStyleOptions.Inline))
|
||||
{
|
||||
// Inline choices
|
||||
if (_annotation.ChoiceParens == BoolDefault.True) builder.Append('(');
|
||||
var choices = new List<string>();
|
||||
var i = 1;
|
||||
foreach (var value in values)
|
||||
{
|
||||
choices.Add(string.Format(_annotation.ChoiceFormat, i, Language.Normalize(value.Description, _annotation.ChoiceCase)));
|
||||
++i;
|
||||
}
|
||||
builder.Append(Language.BuildList(choices, _annotation.ChoiceSeparator, _annotation.ChoiceLastSeparator));
|
||||
if (_annotation.ChoiceParens == BoolDefault.True) builder.Append(')');
|
||||
if (current != null)
|
||||
{
|
||||
builder.Append(" ");
|
||||
builder.Append(current);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Separate line choices
|
||||
if (current != null)
|
||||
{
|
||||
builder.Append(current);
|
||||
builder.Append(" ");
|
||||
}
|
||||
var i = 1;
|
||||
foreach (var value in values)
|
||||
{
|
||||
builder.AppendLine();
|
||||
builder.Append(" ");
|
||||
if (!_annotation.AllowNumbers)
|
||||
{
|
||||
builder.Append("* ");
|
||||
}
|
||||
builder.AppendFormat(_annotation.ChoiceFormat, i, Language.Normalize(value.Description, _annotation.ChoiceCase));
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (current != null)
|
||||
{
|
||||
builder.Append(" ");
|
||||
builder.Append(current);
|
||||
}
|
||||
substitute = builder.ToString();
|
||||
}
|
||||
else if (expr.StartsWith("*"))
|
||||
{
|
||||
// Status display of active results
|
||||
var filled = expr.ToLower().Trim().EndsWith("filled");
|
||||
var builder = new StringBuilder();
|
||||
if (match.Index > 0)
|
||||
{
|
||||
builder.AppendLine();
|
||||
}
|
||||
foreach (var entry in (from step in _fields where (!filled || !step.IsUnknown(state)) && step.Role == FieldRole.Value && step.Active(state) select step))
|
||||
{
|
||||
var format = new Prompter<T>(Template(entry, TemplateUsage.StatusFormat), _form, null);
|
||||
builder.Append("* ").AppendLine(format.Prompt(state, entry).Prompt);
|
||||
}
|
||||
substitute = builder.ToString();
|
||||
}
|
||||
else if (expr.StartsWith("[") && expr.EndsWith("]"))
|
||||
{
|
||||
// Generate a list from multiple fields
|
||||
var paths = expr.Substring(1, expr.Length - 2).Split(' ');
|
||||
var values = new List<Tuple<IField<T>, object, string>>();
|
||||
foreach (var spec in paths)
|
||||
{
|
||||
if (!spec.StartsWith("{") || !spec.EndsWith("}"))
|
||||
{
|
||||
throw new ArgumentException("Only {<field>} references are allowed in lists.");
|
||||
}
|
||||
var formatArgs = spec.Substring(1, spec.Length - 2).Trim().Split(':');
|
||||
var name = formatArgs[0];
|
||||
if (name == string.Empty && field != null) name = field.Name;
|
||||
var format = (formatArgs.Length > 1 ? "0:" + formatArgs[1] : "0");
|
||||
var eltDesc = _fields.Field(name);
|
||||
if (!eltDesc.IsUnknown(state))
|
||||
{
|
||||
var value = eltDesc.GetValue(state);
|
||||
if (value.GetType() != typeof(string) && value.GetType().IsIEnumerable())
|
||||
{
|
||||
var eltValues = (value as System.Collections.IEnumerable);
|
||||
foreach (var elt in eltValues)
|
||||
{
|
||||
values.Add(Tuple.Create(eltDesc, elt, format));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
values.Add(Tuple.Create(eltDesc, eltDesc.GetValue(state), format));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (values.Count() > 0)
|
||||
{
|
||||
var elements = (from elt in values
|
||||
select Language.Normalize(ValueDescription(elt.Item1, elt.Item2, elt.Item3), _annotation.ValueCase)).ToArray();
|
||||
substitute = Language.BuildList(elements, _annotation.Separator, _annotation.LastSeparator);
|
||||
}
|
||||
}
|
||||
else if (expr.StartsWith("?"))
|
||||
{
|
||||
// Conditional template
|
||||
var subValue = ExpandTemplate(expr.Substring(1), currentChoice, null, state, field, args, ref buttons);
|
||||
if (subValue == null)
|
||||
{
|
||||
substitute = string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
substitute = subValue;
|
||||
}
|
||||
}
|
||||
else if (TryParseFormat(expr, out numeric))
|
||||
{
|
||||
// Process ad hoc arg
|
||||
if (numeric < args.Length && args[numeric] != null)
|
||||
{
|
||||
substitute = string.Format("{" + expr + "}", args);
|
||||
}
|
||||
else
|
||||
{
|
||||
foundUnspecified = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var formatArgs = expr.Split(':');
|
||||
var name = formatArgs[0];
|
||||
if (name == string.Empty && field != null) name = field.Name;
|
||||
var pathDesc = _fields.Field(name);
|
||||
if (pathDesc.IsUnknown(state))
|
||||
{
|
||||
if (noValue == null)
|
||||
{
|
||||
foundUnspecified = true;
|
||||
break;
|
||||
}
|
||||
substitute = noValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
var value = pathDesc.GetValue(state);
|
||||
if (value.GetType() != typeof(string) && value.GetType().IsIEnumerable())
|
||||
{
|
||||
var values = (value as System.Collections.IEnumerable);
|
||||
substitute = Language.BuildList(from elt in values.Cast<object>()
|
||||
select Language.Normalize(ValueDescription(pathDesc, elt, "0"), _annotation.ValueCase),
|
||||
_annotation.Separator, _annotation.LastSeparator);
|
||||
}
|
||||
else
|
||||
{
|
||||
var format = (formatArgs.Length > 1 ? "0:" + formatArgs[1] : "0");
|
||||
substitute = ValueDescription(pathDesc, value, format);
|
||||
}
|
||||
}
|
||||
}
|
||||
response.Append(template.Substring(last, match.Index - last)).Append(substitute);
|
||||
last = match.Index + match.Length;
|
||||
}
|
||||
return (foundUnspecified ? null : response.Append(template.Substring(last, template.Length - last)).ToString());
|
||||
}
|
||||
|
||||
private static bool TryParseFormat(string format, out int number)
|
||||
{
|
||||
var args = format.Split(':');
|
||||
return int.TryParse(args[0], out number);
|
||||
}
|
||||
|
||||
private string ValueDescription(IField<T> field, object value, string format)
|
||||
{
|
||||
string result;
|
||||
if (format != "0")
|
||||
{
|
||||
result = string.Format("{" + format + "}", value);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = field.Prompt.Recognizer.ValueDescription(value).Description;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private TemplateAttribute Template(IField<T> field, TemplateUsage usage)
|
||||
{
|
||||
return field == null
|
||||
? _form.Configuration.Template(usage)
|
||||
: field.Template(usage);
|
||||
}
|
||||
|
||||
private static readonly Regex _args = new Regex(@"{((?>[^{}]+|{(?<number>)|}(?<-number>))*(?(number)(?!)))}", RegexOptions.Compiled);
|
||||
private static readonly Regex _spaces = new Regex(@"(\S)( {2,})", RegexOptions.Compiled);
|
||||
private static readonly Regex _spacesPunc = new Regex(@"(?:\s+)(\.|\?)", RegexOptions.Compiled);
|
||||
private IForm<T> _form;
|
||||
private IFields<T> _fields;
|
||||
private TemplateBaseAttribute _annotation;
|
||||
private IRecognize<T> _recognizer;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,208 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Microsoft Bot Framework: http://botframework.com
|
||||
//
|
||||
// Bot Builder SDK GitHub:
|
||||
// https://github.com/Microsoft/BotBuilder
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// All rights reserved.
|
||||
//
|
||||
// MIT License:
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Bot.Schema;
|
||||
|
||||
namespace Microsoft.Bot.Builder.V3Bridge.FormFlow.Advanced
|
||||
{
|
||||
/// <summary>
|
||||
/// Enumeration of special kinds of matches.
|
||||
/// </summary>
|
||||
public enum SpecialValues
|
||||
{
|
||||
/// <summary>
|
||||
/// Match corresponds to a field, not a specific value in the field.
|
||||
/// </summary>
|
||||
Field
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Describe a possible match in the user input.
|
||||
/// </summary>
|
||||
public class TermMatch
|
||||
{
|
||||
/// <summary>
|
||||
/// Construct a match.
|
||||
/// </summary>
|
||||
/// <param name="start">Start of match in input string.</param>
|
||||
/// <param name="length">Length of match in input string.</param>
|
||||
/// <param name="confidence">Confidence of match, 0-1.0.</param>
|
||||
/// <param name="value">The underlying C# value for the match.</param>
|
||||
public TermMatch(int start, int length, double confidence, object value)
|
||||
{
|
||||
Start = start;
|
||||
Length = length;
|
||||
Confidence = confidence;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start of match in input string.
|
||||
/// </summary>
|
||||
public readonly int Start;
|
||||
|
||||
/// <summary>
|
||||
/// End of match in input string.
|
||||
/// </summary>
|
||||
public int End { get { return Start + Length; } }
|
||||
|
||||
/// <summary>
|
||||
/// Length of match in input string.
|
||||
/// </summary>
|
||||
public readonly int Length;
|
||||
|
||||
/// <summary>
|
||||
/// Confidence of match, 0-1.0.
|
||||
/// </summary>
|
||||
public readonly double Confidence;
|
||||
|
||||
/// <summary>
|
||||
/// Underlying C# value.
|
||||
/// </summary>
|
||||
public readonly object Value;
|
||||
|
||||
/// <summary>
|
||||
/// Check to see if this covers the same span as match.
|
||||
/// </summary>
|
||||
/// <param name="match">TermMatch to compare.</param>
|
||||
/// <returns>True if both cover the same span.</returns>
|
||||
public bool Same(TermMatch match)
|
||||
{
|
||||
return Start == match.Start && End == match.End;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check to see if this completely covers match.
|
||||
/// </summary>
|
||||
/// <param name="match">TermMatch to compare.</param>
|
||||
/// <returns>True if this covers all of match.</returns>
|
||||
public bool Covers(TermMatch match)
|
||||
{
|
||||
return Start <= match.Start && End >= match.End && (Start != match.Start || End != match.End);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check to see if this overlaps with match in input.
|
||||
/// </summary>
|
||||
/// <param name="match">TermMatch to compare.</param>
|
||||
/// <returns>True if the matches overlap in the input.</returns>
|
||||
public bool Overlaps(TermMatch match)
|
||||
{
|
||||
return (match.Start <= End && Start <= match.Start && End <= match.End) // tmtm
|
||||
|| (Start <= match.End && match.Start <= Start && match.End <= End) // mtmt
|
||||
|| (Start <= match.Start && End >= match.End) // tmmt
|
||||
|| (match.Start <= Start && match.End >= End) // mttm
|
||||
;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("TermMatch({0}, {1}, {2}-{3})", Value, Confidence, Start, Start + Length);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is TermMatch && this == (TermMatch)obj;
|
||||
}
|
||||
|
||||
public static bool operator ==(TermMatch m1, TermMatch m2)
|
||||
{
|
||||
return ReferenceEquals(m1, m2) || (!ReferenceEquals(m1, null) && !ReferenceEquals(m2, null) && m1.Start == m2.Start && m1.Length == m2.Length && m1.Confidence == m2.Confidence && m1.Value == m2.Value);
|
||||
}
|
||||
|
||||
public static bool operator !=(TermMatch m1, TermMatch m2)
|
||||
{
|
||||
return !(m1 == m2);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Start.GetHashCode() ^ Length.GetHashCode() ^ Confidence.GetHashCode() ^ Value.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface for recognizers that look for matches in user input.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Underlying form state.</typeparam>
|
||||
public interface IRecognize<T>
|
||||
{
|
||||
#region Documentation
|
||||
/// <summary> Return the arguments to pass to the prompt. </summary>
|
||||
///<remarks>For example a numeric recognizer might pass min and max values.</remarks>
|
||||
/// <returns> An array of arguments.</returns>
|
||||
#endregion
|
||||
object[] PromptArgs();
|
||||
|
||||
/// <summary>
|
||||
/// Return all possible values or null if a primitive type.
|
||||
/// </summary>
|
||||
/// <returns>All possible values.</returns>
|
||||
IEnumerable<object> Values();
|
||||
|
||||
/// <summary>
|
||||
/// Return all possible value descriptions in order to support enumeration.
|
||||
/// </summary>
|
||||
/// <returns>All possible value descriptions.</returns>
|
||||
IEnumerable<DescribeAttribute> ValueDescriptions();
|
||||
|
||||
/// <summary>
|
||||
/// Return the description of a specific value.
|
||||
/// </summary>
|
||||
/// <param name="value">Value to get description of.</param>
|
||||
/// <returns>Description of the value.</returns>
|
||||
DescribeAttribute ValueDescription(object value);
|
||||
|
||||
/// <summary>
|
||||
/// Return valid inputs to describe a particular value.
|
||||
/// </summary>
|
||||
/// <param name="value">Value being checked.</param>
|
||||
/// <returns>Valid inputs for describing value.</returns>
|
||||
IEnumerable<string> ValidInputs(object value);
|
||||
|
||||
/// <summary>
|
||||
/// Return the help string describing what are valid inputs to the recognizer.
|
||||
/// </summary>
|
||||
/// <returns>Help on what the recognizer accepts.</returns>
|
||||
string Help(T state, object defaultValue = null);
|
||||
|
||||
/// <summary>
|
||||
/// Return the matches found in the input.
|
||||
/// </summary>
|
||||
/// <param name="input">The input activity being matched.</param>
|
||||
/// <param name="defaultValue">The default value or null if none.</param>
|
||||
/// <returns>Match records.</returns>
|
||||
IEnumerable<TermMatch> Matches(IMessageActivity input, object defaultValue = null);
|
||||
}
|
||||
}
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче