This commit is contained in:
Tom Laird-McConnell 2018-03-08 21:33:10 -08:00
Родитель 3fc3e38288
Коммит 28ad01b4e3
243 изменённых файлов: 52594 добавлений и 9 удалений

Просмотреть файл

@ -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, ...], &lt;args&gt; }, ...}` -- Define templates.
/// * `Prompt: { Patterns:[string, ...] &lt;args&gt;}` -- 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&lt;JObject&gt; 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&lt;TraceActivityLogger&gt;()
/// .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 {&amp;} 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 {&amp;}, 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);
}
}

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше