remove classic, classic test and form test projects

This commit is contained in:
John Taylor 2018-09-12 18:27:14 -07:00
Родитель 8ff8744437
Коммит 93a1898d36
259 изменённых файлов: 0 добавлений и 59443 удалений

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

@ -34,16 +34,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.Templ
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.TemplateManager.Tests", "tests\Microsoft.Bot.Builder.TemplateManager\Microsoft.Bot.Builder.TemplateManager.Tests.csproj", "{C93F6192-0123-4121-AD92-374A71E4B0F3}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Classic", "Classic", "{2A46FEEF-4FD9-4DD5-8650-39C36CFDF2BB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.Classic.Tests", "tests\Microsoft.Bot.Builder.Classic.Tests\Microsoft.Bot.Builder.Classic.Tests.csproj", "{508661A7-F47A-4D60-8ED6-69E378ED0E74}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RView", "libraries\Microsoft.Bot.Builder.Classic\RView\RView.csproj", "{12CAE029-7B8D-4928-8504-28746C36ADB3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FormTest", "tests\FormTest\FormTest.csproj", "{77BD8C61-FB06-4BF7-A346-D2C87284390A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Bot.Builder.Classic", "libraries\Microsoft.Bot.Builder.Classic\Microsoft.Bot.Builder.Classic\Microsoft.Bot.Builder.Classic.csproj", "{CDFEC7D6-847E-4C13-956B-0A960AE3EB60}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Schema.Tests", "tests\Microsoft.Bot.Schema.Tests\Microsoft.Bot.Schema.Tests.csproj", "{A4184239-F13F-4A09-B2D3-0B9532609248}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.AI.QnA", "libraries\Microsoft.Bot.Builder.AI.QnA\Microsoft.Bot.Builder.AI.QnA.csproj", "{D9B2EF5D-0515-460F-948A-2BB70C8DCF62}"
@ -177,36 +167,6 @@ Global
{C93F6192-0123-4121-AD92-374A71E4B0F3}.Documentation|Any CPU.ActiveCfg = Documentation|Any CPU
{C93F6192-0123-4121-AD92-374A71E4B0F3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C93F6192-0123-4121-AD92-374A71E4B0F3}.Release|Any CPU.Build.0 = Release|Any CPU
{508661A7-F47A-4D60-8ED6-69E378ED0E74}.Debug - NuGet Packages|Any CPU.ActiveCfg = Debug|Any CPU
{508661A7-F47A-4D60-8ED6-69E378ED0E74}.Debug - NuGet Packages|Any CPU.Build.0 = Debug|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}.Documentation|Any CPU.ActiveCfg = Documentation|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
{12CAE029-7B8D-4928-8504-28746C36ADB3}.Debug - NuGet Packages|Any CPU.ActiveCfg = Debug|Any CPU
{12CAE029-7B8D-4928-8504-28746C36ADB3}.Debug - NuGet Packages|Any CPU.Build.0 = Debug|Any CPU
{12CAE029-7B8D-4928-8504-28746C36ADB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{12CAE029-7B8D-4928-8504-28746C36ADB3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{12CAE029-7B8D-4928-8504-28746C36ADB3}.Documentation|Any CPU.ActiveCfg = Documentation|Any CPU
{12CAE029-7B8D-4928-8504-28746C36ADB3}.Documentation|Any CPU.Build.0 = Documentation|Any CPU
{12CAE029-7B8D-4928-8504-28746C36ADB3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{12CAE029-7B8D-4928-8504-28746C36ADB3}.Release|Any CPU.Build.0 = Release|Any CPU
{77BD8C61-FB06-4BF7-A346-D2C87284390A}.Debug - NuGet Packages|Any CPU.ActiveCfg = Debug|Any CPU
{77BD8C61-FB06-4BF7-A346-D2C87284390A}.Debug - NuGet Packages|Any CPU.Build.0 = Debug|Any CPU
{77BD8C61-FB06-4BF7-A346-D2C87284390A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{77BD8C61-FB06-4BF7-A346-D2C87284390A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{77BD8C61-FB06-4BF7-A346-D2C87284390A}.Documentation|Any CPU.ActiveCfg = Documentation|Any CPU
{77BD8C61-FB06-4BF7-A346-D2C87284390A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{77BD8C61-FB06-4BF7-A346-D2C87284390A}.Release|Any CPU.Build.0 = Release|Any CPU
{CDFEC7D6-847E-4C13-956B-0A960AE3EB60}.Debug - NuGet Packages|Any CPU.ActiveCfg = Debug|Any CPU
{CDFEC7D6-847E-4C13-956B-0A960AE3EB60}.Debug - NuGet Packages|Any CPU.Build.0 = Debug|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}.Documentation|Any CPU.ActiveCfg = Documentation|Any CPU
{CDFEC7D6-847E-4C13-956B-0A960AE3EB60}.Documentation|Any CPU.Build.0 = Documentation|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
{A4184239-F13F-4A09-B2D3-0B9532609248}.Debug - NuGet Packages|Any CPU.ActiveCfg = Debug|Any CPU
{A4184239-F13F-4A09-B2D3-0B9532609248}.Debug - NuGet Packages|Any CPU.Build.0 = Debug|Any CPU
{A4184239-F13F-4A09-B2D3-0B9532609248}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
@ -312,11 +272,6 @@ Global
{053BD8B8-B5C1-4C45-81D4-C9BA8D5B3CE2} = {276EBE79-A13A-46BD-A566-B01DC0477A9B}
{EED5F0D3-6F00-4ED1-9108-5ADCB10A3734} = {4269F3C3-6B42-419B-B64A-3E6DC0F1574A}
{C93F6192-0123-4121-AD92-374A71E4B0F3} = {AD743B78-D61F-4FBF-B620-FA83CE599A50}
{2A46FEEF-4FD9-4DD5-8650-39C36CFDF2BB} = {4269F3C3-6B42-419B-B64A-3E6DC0F1574A}
{508661A7-F47A-4D60-8ED6-69E378ED0E74} = {AD743B78-D61F-4FBF-B620-FA83CE599A50}
{12CAE029-7B8D-4928-8504-28746C36ADB3} = {2A46FEEF-4FD9-4DD5-8650-39C36CFDF2BB}
{77BD8C61-FB06-4BF7-A346-D2C87284390A} = {AD743B78-D61F-4FBF-B620-FA83CE599A50}
{CDFEC7D6-847E-4C13-956B-0A960AE3EB60} = {2A46FEEF-4FD9-4DD5-8650-39C36CFDF2BB}
{A4184239-F13F-4A09-B2D3-0B9532609248} = {AD743B78-D61F-4FBF-B620-FA83CE599A50}
{D9B2EF5D-0515-460F-948A-2BB70C8DCF62} = {763168FA-A590-482C-84D8-2922F7ADB1A2}
{763168FA-A590-482C-84D8-2922F7ADB1A2} = {4269F3C3-6B42-419B-B64A-3E6DC0F1574A}

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

@ -1,5 +0,0 @@
@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

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

@ -1,5 +0,0 @@
@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

Двоичный файл не отображается.

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

@ -1,99 +0,0 @@
//
// 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.Classic.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>();
}
}
}

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

@ -1,184 +0,0 @@
//
// 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.Classic.ConnectorEx;
using Microsoft.Bot.Builder.Classic.Dialogs.Internals;
using Microsoft.Bot.Schema;
namespace Microsoft.Bot.Builder.Classic.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="v4Context"/> <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="v4Context">The turn context containing 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(Microsoft.Bot.Builder.ITurnContext turnContext, Func<IDialog<object>> MakeRoot, CancellationToken token = default(CancellationToken))
{
using (var scope = DialogModule.BeginLifetimeScope(Container, turnContext))
{
DialogModule_MakeRoot.Register(scope, MakeRoot);
await SendAsync(scope, turnContext, token);
}
}
/// <summary>
/// Resume a conversation and post the data to the dialog waiting.
/// </summary>
/// <param name="resumptionCookie"> The resumption cookie.</param>
/// <param name="turnContext">The turn context containing the message sent to the 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, Microsoft.Bot.Builder.ITurnContext turnContext, CancellationToken token = default(CancellationToken))
{
var conversationRef = resumptionCookie.ToConversationReference();
await ResumeAsync(conversationRef, turnContext, token);
}
/// <summary>
/// Resume a conversation and post the data to the dialog waiting.
/// </summary>
/// <param name="conversationReference"> The resumption cookie.</param>
/// <param name="turnContext">The turn context containing the message sent to the 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, Microsoft.Bot.Builder.ITurnContext turnContext, CancellationToken token = default(CancellationToken))
{
using (var scope = DialogModule.BeginLifetimeScope(Container, turnContext))
{
Func<IDialog<object>> MakeRoot = () => { throw new InvalidOperationException(); };
DialogModule_MakeRoot.Register(scope, MakeRoot);
await SendAsync(scope, turnContext, 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, Microsoft.Bot.Builder.ITurnContext turnContext, CancellationToken token = default(CancellationToken))
{
var task = scope.Resolve<IPostToBot>();
await task.PostAsync(turnContext.Activity.AsMessageActivity(), token);
}
}
}

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

@ -1,464 +0,0 @@
//
// 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.Classic.Autofac.Base;
using Microsoft.Bot.Builder.Classic.Base;
using Microsoft.Bot.Builder.Classic;
using Microsoft.Bot.Builder.Classic.Resource;
using Microsoft.Bot.Builder.Classic.ConnectorEx;
using Microsoft.Bot.Builder.Classic.History;
using Microsoft.Bot.Builder.Classic.Internals.Fibers;
using Microsoft.Bot.Builder.Classic.Scorables;
using Microsoft.Bot.Builder.Classic.Scorables.Internals;
using Microsoft.Bot.Connector;
using Microsoft.Bot.Connector.Authentication;
using Microsoft.Bot.Schema;
namespace Microsoft.Bot.Builder.Classic.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, Microsoft.Bot.Builder.ITurnContext turnContext)
{
var inner = scope.BeginLifetimeScope(LifetimeScopeTag);
inner.Resolve<Microsoft.Bot.Builder.ITurnContext>(TypedParameter.From(turnContext));
inner.Resolve<IActivity>(TypedParameter.From((IActivity)turnContext.Activity));
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.Classic.Resource.Resources", typeof(Resources).Assembly))
.As<ResourceManager>()
.SingleInstance();
// every lifetime scope is driven by a context
builder
.Register((c, p) => p.TypedAs<Microsoft.Bot.Builder.ITurnContext>())
.AsSelf()
.AsImplementedInterfaces()
.InstancePerMatchingLifetimeScope(LifetimeScopeTag);
builder
.Register((c, p) => p.TypedAs<IActivity>())
.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<V4Bridge_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="V4Bridge_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(V4Bridge_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));
}
}
}

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

@ -1,116 +0,0 @@
//
// 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.Classic.Scorables.Internals;
namespace Microsoft.Bot.Builder.Classic.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;
}
}
}

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

@ -1,182 +0,0 @@
//
// 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.Classic.Scorables.Internals;
namespace Microsoft.Bot.Builder.Classic.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();
}
}
}

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

@ -1,70 +0,0 @@
//
// 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.Classic.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.Classic.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);
}
}
}

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

@ -1,96 +0,0 @@
//
// 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.Classic.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;
}
}
}
}

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

@ -1,152 +0,0 @@
//
// 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.Classic.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.Classic.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;
}
}
}

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

@ -1,82 +0,0 @@
//
// 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.Classic.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;
}
}
}

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

@ -1,92 +0,0 @@
//
// 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.Classic.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();
}
}
}

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

@ -1,201 +0,0 @@
//
// 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.Classic.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();
}
}
}
}
}
}
}

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

@ -1,208 +0,0 @@
//
// 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.Classic.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.Classic.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);
}
}
}

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

@ -1,147 +0,0 @@
//
// 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.Classic.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();
}
}
}
}
}
}
}

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

@ -1,71 +0,0 @@
//
// 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.Classic.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;
}
}
}

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

@ -1,49 +0,0 @@
//
// 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.Classic.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());
}
}

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

@ -1,64 +0,0 @@
//
// 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.Classic.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; }
}
}
}

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

@ -1,53 +0,0 @@
//
// 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.Classic.Base
{
public static partial class Types
{
public static MethodInfo MethodOf(Expression<Action> action)
{
var call = (MethodCallExpression)action.Body;
return call.Method;
}
}
}

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

@ -1,162 +0,0 @@
//
// 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.Classic.Internals.Fibers;
using Microsoft.Bot.Schema;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
namespace Microsoft.Bot.Builder.Classic.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;
}
}
}

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

@ -1,97 +0,0 @@
// <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.Classic
{
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);
}
}
}

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

@ -1,677 +0,0 @@
//
// 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.Classic.Base;
using Microsoft.Bot.Builder.Classic.ConnectorEx;
using Microsoft.Bot.Builder.Classic.Internals.Fibers;
using Microsoft.Bot.Builder.Classic;
using Microsoft.Bot.Schema;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Microsoft.Bot.Builder.Classic.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 Exception(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 Exception(HttpStatusCode.PreconditionFailed.ToString());
}
}
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();
}
}
}

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

@ -1,388 +0,0 @@
//
// 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.Classic.ConnectorEx;
using Microsoft.Bot.Builder.Classic.Internals.Fibers;
using Microsoft.Bot.Connector;
using Microsoft.Bot.Schema;
namespace Microsoft.Bot.Builder.Classic.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 sealed class V4Bridge_BotToUser : IBotToUser
{
private readonly Microsoft.Bot.Builder.ITurnContext turnContext;
public V4Bridge_BotToUser(Microsoft.Bot.Builder.ITurnContext turnContext)
{
SetField.NotNull(out this.turnContext, nameof(turnContext), turnContext);
}
IMessageActivity IBotToUser.MakeMessage()
{
var toBotActivity = (Activity)this.turnContext.Activity;
return toBotActivity.CreateReply();
}
Task IBotToUser.PostAsync(IMessageActivity message, CancellationToken cancellationToken)
{
// TODO, change this to context.SendActivity with M2 delta
return this.turnContext.Adapter.SendActivitiesAsync(this.turnContext, new Activity[] { (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 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();
}
}
}

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

@ -1,134 +0,0 @@
//
// 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.Classic.History;
using Microsoft.Bot.Builder.Classic.Internals.Fibers;
using Microsoft.Bot.Schema;
using Newtonsoft.Json;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Bot.Builder.Classic.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.Classic.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);
}
}
}

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

@ -1,71 +0,0 @@
//
// 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.Classic.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);
}
}

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

@ -1,143 +0,0 @@
//
// 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.Classic.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;
}
}
}

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

@ -1,129 +0,0 @@
//
// 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.Classic.Internals.Fibers;
using Microsoft.Bot.Schema;
namespace Microsoft.Bot.Builder.Classic.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="Schema.Activity.Speak"/>; false otherwise.</returns>
bool SupportsSpeak();
/// <summary>
/// Indicates if channel relies on <see cref="Schema.Activity.InputHint"/>.
/// </summary>
/// <returns>True if channel expect bot setting <see cref="Schema.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);
}
}
}

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

@ -1,79 +0,0 @@
//
// 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.Classic;
using Microsoft.Bot.Builder.Classic.Internals.Fibers;
using Microsoft.Bot.Connector;
using Microsoft.Bot.Connector.Authentication;
using Microsoft.Bot.Schema;
namespace Microsoft.Bot.Builder.Classic.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);
}
}
}

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

@ -1,171 +0,0 @@
//
// 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.Classic.Dialogs.Internals;
using Microsoft.Bot.Schema;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Linq;
namespace Microsoft.Bot.Builder.Classic.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);
}
}
}

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

@ -1,72 +0,0 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Classic.Base;
using Microsoft.Bot.Builder.Classic.Dialogs.Internals;
using Microsoft.Bot.Builder.Classic.Internals.Fibers;
using Microsoft.Bot.Connector.Authentication;
using Microsoft.Bot.Schema;
namespace Microsoft.Bot.Builder.Classic.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;
}
}
}

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

@ -1,173 +0,0 @@
//
// 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.Classic.Base;
using Microsoft.Bot.Builder.Classic.ConnectorEx;
using Microsoft.Bot.Builder.Classic.Internals.Fibers;
using Microsoft.Bot.Schema;
namespace Microsoft.Bot.Builder.Classic.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;
}
}
}
}

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

@ -1,167 +0,0 @@
//
// 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.Classic.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.Event, typeof(IEventActivity) },
{ ActivityTypes.Invoke, typeof(IInvokeActivity) },
{ ActivityTypes.Typing, typeof(ITypingActivity) },
{ ActivityTypes.EndOfConversation, typeof(IEndOfConversationActivity) },
{ ActivityTypes.MessageUpdate, typeof(IMessageUpdateActivity) },
{ ActivityTypes.MessageDelete, typeof(IMessageDeleteActivity) },
{ ActivityTypes.InstallationUpdate, typeof(IInstallationUpdateActivity) },
{ ActivityTypes.MessageReaction, typeof(IMessageReactionActivity) },
{ ActivityTypes.Suggestion, typeof(ISuggestionActivity) },
{ ActivityTypes.Trace, typeof(ITraceActivity) },
{ ActivityTypes.Handoff, typeof(IHandoffActivity) },
};
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;
}
}
}

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

@ -1,175 +0,0 @@
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.Classic.Dialogs;
using Microsoft.Bot.Builder.Classic.Internals.Fibers;
using Microsoft.Bot.Schema;
namespace Microsoft.Bot.Builder.Classic.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
{
ActivityId = activity.Id,
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());
}
}
}
}

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

@ -1,196 +0,0 @@
//
// 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.Classic.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 the trusted host names 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());
}
}
}
}

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

@ -1,97 +0,0 @@
////
//// 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.Classic.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;
// }
// }
// }
//}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

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

@ -1,195 +0,0 @@
//
// 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.Classic.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.Classic.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)));
}
}
}

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

@ -1,103 +0,0 @@
//
// 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.Classic.Dialogs.Internals;
using Microsoft.Bot.Builder.Classic.Internals.Fibers;
using Microsoft.Bot.Builder.Classic.Scorables.Internals;
using Microsoft.Bot.Builder.Classic.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;
namespace Microsoft.Bot.Builder.Classic.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;
}
}
}

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

@ -1,152 +0,0 @@
//
// 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.Classic.Internals.Fibers;
using Microsoft.Bot.Schema;
namespace Microsoft.Bot.Builder.Classic.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;
}
}

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

@ -1,111 +0,0 @@
//
// 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.Classic.Internals.Fibers;
using Microsoft.Bot.Builder.Classic.Scorables.Internals;
using Microsoft.Bot.Schema;
using Microsoft.Bot.Builder.Classic.Scorables;
namespace Microsoft.Bot.Builder.Classic.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))
{
}
}
}

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

@ -1,83 +0,0 @@
//
// 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.Classic.Base;
using Microsoft.Bot.Builder.Classic.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.Classic.Internals.Fibers;
namespace Microsoft.Bot.Builder.Classic.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);
}
}
}

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

@ -1,489 +0,0 @@
//
// 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.Classic.Base;
using Microsoft.Bot.Builder.Classic.Internals.Fibers;
using Microsoft.Bot.Schema;
namespace Microsoft.Bot.Builder.Classic.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);
}
}
}
}

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

@ -1,146 +0,0 @@
//
// 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.Classic.Internals.Fibers;
using Microsoft.Bot.Builder.Classic.Base;
using Microsoft.Bot.Schema;
namespace Microsoft.Bot.Builder.Classic.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;
}
}
}

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

@ -1,91 +0,0 @@
//
// 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.Classic.Scorables;
namespace Microsoft.Bot.Builder.Classic.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>
{
}
}

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

@ -1,103 +0,0 @@
//
// 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.Classic.Dialogs.Internals;
using Microsoft.Bot.Builder.Classic.Scorables;
using Microsoft.Bot.Builder.Classic.Scorables.Internals;
using Microsoft.Bot.Schema;
namespace Microsoft.Bot.Builder.Classic.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);
}
}
}

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

@ -1,143 +0,0 @@
//
// 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.Classic.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)
{
}
}
}

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

@ -1,63 +0,0 @@
//
// 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.Classic.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>
{
}
}

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

@ -1,216 +0,0 @@
//
// 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.Classic.Dialogs.Internals;
using Microsoft.Bot.Schema;
namespace Microsoft.Bot.Builder.Classic.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.GetContinuationActivity"/>.
/// </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<Microsoft.Bot.Schema.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<Microsoft.Bot.Schema.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);
}
}
}

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

@ -1,127 +0,0 @@
//
// 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.Classic.Base;
using Microsoft.Bot.Schema;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Bot.Builder.Classic.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);
}
}
}

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

@ -1,422 +0,0 @@
//
// 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.Classic.Dialogs.Internals;
using Microsoft.Bot.Builder.Classic.Internals.Fibers;
using Microsoft.Bot.Builder.Classic.Luis;
using Microsoft.Bot.Builder.Classic.Luis.Models;
using Microsoft.Bot.Builder.Classic.Scorables.Internals;
using Microsoft.Bot.Schema;
namespace Microsoft.Bot.Builder.Classic.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);
}
}
}
}
}
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

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

@ -1,532 +0,0 @@
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.Classic.Dialogs.Internals;
using System.Collections.Concurrent;
using Microsoft.Bot.Builder.Classic.Resource;
namespace Microsoft.Bot.Builder.Classic.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 });
}
}
}

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

@ -1,91 +0,0 @@
//
// 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.Classic.Base;
using Microsoft.Bot.Builder.Classic.Internals.Fibers;
using Microsoft.Bot.Schema;
using Microsoft.Bot.Builder.Classic.Scorables;
namespace Microsoft.Bot.Builder.Classic.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);
}
}
}
}

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

@ -1,110 +0,0 @@
//
// 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.Classic.Internals.Fibers
{
public interface IAwaiter<out T> : INotifyCompletion
{
bool IsCompleted { get; }
T GetResult();
}
}
namespace Microsoft.Bot.Builder.Classic.Dialogs
{
using Microsoft.Bot.Builder.Classic.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.Classic.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);
}
}
}

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

@ -1,142 +0,0 @@
//
// 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.Classic.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)
{
}
}
}

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

@ -1,154 +0,0 @@
//
// 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.Classic.Dialogs;
namespace Microsoft.Bot.Builder.Classic.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;
}
}
}

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

@ -1,253 +0,0 @@
//
// 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.Classic.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;
}
}
}

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

@ -1,50 +0,0 @@
//
// 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.Classic.Internals.Fibers
{
public interface IStore<T>
{
void Reset();
bool TryLoad(out T item);
void Save(T item);
void Flush();
}
}

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

@ -1,137 +0,0 @@
//
// 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.Classic.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;
}
}
}
}

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

@ -1,298 +0,0 @@
//
// 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.Classic.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.Classic.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;
}
}
}
}
}

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

@ -1,59 +0,0 @@
//
// 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.Classic.Dialogs;
namespace Microsoft.Bot.Builder.Classic.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);
}
}
}

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

@ -1,166 +0,0 @@
//
// 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.Classic.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();
}
}
}

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

@ -1,418 +0,0 @@
//
// 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.Classic.Base;
using Microsoft.Bot.Builder.Classic.Dialogs;
namespace Microsoft.Bot.Builder.Classic.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>();
}
}
}

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

@ -1,859 +0,0 @@
//
// 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.Classic.FormFlow.Advanced;
namespace Microsoft.Bot.Builder.Classic.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.Classic.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;
}
}
}

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

@ -1,126 +0,0 @@
//
// 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.Classic.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
}
}

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

@ -1,213 +0,0 @@
//
// 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.Classic.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(string.Empty));
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();
}
}
}

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

@ -1,813 +0,0 @@
//
// 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.Classic.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>>();
}
}

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

@ -1,577 +0,0 @@
////
//// 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.Classic.Dialogs;
//using Microsoft.Bot.Builder.Classic.FormFlow.Advanced;
//using Newtonsoft.Json.Linq;
//using System;
//using System.Collections.Generic;
//using System.Linq;
//namespace Microsoft.Bot.Builder.Classic.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;
// }
//}

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

@ -1,445 +0,0 @@
//
// 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.Classic.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
}
}

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

@ -1,522 +0,0 @@
//
// 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.Classic.FormFlow.Advanced;
using Microsoft.Bot.Builder.Classic.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.Classic.Dialogs;
using System.Threading.Tasks;
using System.Text;
namespace Microsoft.Bot.Builder.Classic.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 : string.Empty;
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), string.Empty, 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), string.Empty, 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 == string.Empty ? 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);
}
}
}
}

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

@ -1,543 +0,0 @@
#if FORMFLOW_JSON
//
// 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.Classic.Dialogs;
using Microsoft.Bot.Builder.Classic.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.Classic.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.Classic.dll is included.
/// * `Imports: [import, ...]` -- Define imports to include in scripts with usings. By default these namespaces are included: Microsoft.Bot.Builder.Classic, Microsoft.Bot.Builder.Classic.Dialogs, Microsoft.Bot.Builder.Classic.FormFlow, Microsoft.Bot.Builder.Classic.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.Classic.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.Classic", "Microsoft.Bot.Builder.Classic.Dialogs",
"Microsoft.Bot.Builder.Classic.FormFlow", "Microsoft.Bot.Builder.Classic.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 == string.Empty ? 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.Classic.FormFlow.Advanced
{
/// <summary>
/// Global values to pass into scripts defined using <see cref="Microsoft.Bot.Builder.Classic.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;
}
}
#endif

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

@ -1,851 +0,0 @@
//
// 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.Classic.Dialogs;
using Microsoft.Bot.Builder.Classic.FormFlow.Advanced;
using Microsoft.Bot.Builder.Classic.Internals.Fibers;
using Microsoft.Bot.Builder.Classic.Luis.Models;
namespace Microsoft.Bot.Builder.Classic.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<EntityModel> _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;
public 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<EntityModel> entities = null, CultureInfo cultureInfo = null)
{
buildForm = buildForm ?? BuildDefaultForm;
entities = entities ?? Enumerable.Empty<EntityModel>();
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.Classic.Luis.Models
{
[Serializable]
public partial class EntityModel
{
}
[Serializable]
public partial class IntentRecommendation
{
}
}

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

@ -1,106 +0,0 @@
//
// 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.Classic.FormFlow.Advanced;
namespace Microsoft.Bot.Builder.Classic.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;
}
}
}

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

@ -1,437 +0,0 @@
//
// 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.Classic.FormFlow;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.Bot.Builder.Classic.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);
}
}

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

@ -1,79 +0,0 @@
//
// 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.Classic.Dialogs;
using Microsoft.Bot.Builder.Classic.FormFlow.Advanced;
using System.Collections;
using System.Collections.Generic;
using System.Resources;
using System.Threading.Tasks;
namespace Microsoft.Bot.Builder.Classic.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);
}
}

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

@ -1,439 +0,0 @@
//
// 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.Classic.Resource;
using Microsoft.Bot.Builder.Classic.FormFlow.Advanced;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Threading.Tasks;
namespace Microsoft.Bot.Builder.Classic.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.Classic 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(string.Empty)
{
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;
}
};
}

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

@ -1,255 +0,0 @@
//
// 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.Classic.Dialogs;
using Microsoft.Bot.Builder.Classic.FormFlow.Advanced;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Microsoft.Bot.Builder.Classic.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; }
}
}

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

@ -1,163 +0,0 @@
//
// 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.Classic.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);
}
}

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

@ -1,716 +0,0 @@
//
// 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.Classic.Resource;
using Microsoft.Bot.Builder.Classic.Dialogs;
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.Classic.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;
}
}

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

@ -1,208 +0,0 @@
//
// 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.Classic.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);
}
}

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

@ -1,97 +0,0 @@
//
// 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.Tasks;
using Microsoft.Bot.Builder.Classic.Dialogs;
using Microsoft.Bot.Schema;
namespace Microsoft.Bot.Builder.Classic.FormFlow.Advanced
{
internal enum StepPhase { Ready, Responding, Completed };
internal enum StepType { Field, Confirm, Navigation, Message };
internal struct StepResult
{
internal StepResult(bool success, NextStep next, FormPrompt feedback, FormPrompt prompt)
{
this.Success = success;
this.Next = next;
this.Feedback = feedback;
this.Prompt = prompt;
}
internal NextStep Next { get; set; }
internal FormPrompt Feedback { get; set; }
internal FormPrompt Prompt { get; set; }
internal bool Success { get; set; }
}
internal interface IStep<T>
where T : class
{
string Name { get; }
StepType Type { get; }
TemplateBaseAttribute Annotation { get; }
IField<T> Field { get; }
void SaveResources();
void Localize();
bool Active(T state);
Task<bool> DefineAsync(T state);
FormPrompt Start(IDialogContext context, T state, FormState form);
bool InClarify(FormState form);
IEnumerable<TermMatch> Match(IDialogContext context, T state, FormState form, IMessageActivity input);
Task<StepResult> ProcessAsync(IDialogContext context, T state, FormState form, IMessageActivity input, IEnumerable<TermMatch> matches);
FormPrompt NotUnderstood(IDialogContext context, T state, FormState form, IMessageActivity input);
FormPrompt Help(T state, FormState form, string commandHelp);
bool Back(IDialogContext context, T state, FormState form);
IEnumerable<string> Dependencies { get; }
}
}

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

@ -1,318 +0,0 @@
//
// 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.Classic.Resource;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace Microsoft.Bot.Builder.Classic.FormFlow.Advanced
{
/// <summary>
/// Language related utilities.
/// </summary>
public class Language
{
/// <summary>
/// Language stop words.
/// </summary>
public static HashSet<string> StopWords = new HashSet<string>(Resources.LanguageStopWords.SplitList());
/// <summary>
/// Language articles.
/// </summary>
public static HashSet<string> Articles = new HashSet<string>(Resources.LanguageArticles.SplitList());
/// <summary>
/// Test to see if word is all punctuation or white space.
/// </summary>
/// <param name="word">Word to check.</param>
/// <returns>True if word is all punctuation or white space.</returns>
public static bool NonWord(string word)
{
bool nonWord = true;
foreach (var ch in word)
{
if (!(char.IsControl(ch) || char.IsPunctuation(ch) || char.IsWhiteSpace(ch)))
{
nonWord = false;
break;
}
}
return nonWord;
}
/// <summary>
/// Test to see if a word is all noise.
/// </summary>
/// <param name="word">Word to test.</param>
/// <returns>True if word is a number, a <see cref="NonWord(string)"/> or a <see cref="StopWords"/>.</returns>
public static bool NoiseWord(string word)
{
double number;
bool noiseWord = double.TryParse(word, out number);
if (!noiseWord) noiseWord = NonWord(word);
if (!noiseWord) noiseWord = StopWords.Contains(word.ToLower());
return noiseWord;
}
/// <summary>
/// Test to see if a word can be ignored in a resposne.
/// </summary>
/// <param name="word">Word to test.</param>
/// <returns>True if word is a <see cref="NonWord(string)"/> or a <see cref="StopWords"/>.</returns>
public static bool NoiseResponse(string word)
{
bool noiseWord = NonWord(word);
if (!noiseWord) noiseWord = StopWords.Contains(word.ToLower());
return noiseWord;
}
/// <summary>
/// Test a word for articles or noise.
/// </summary>
/// <param name="word">Word to test.</param>
/// <returns>True if word is <see cref="NonWord(string)"/> or <see cref="Articles"/>.</returns>
public static bool ArticleOrNone(string word)
{
return NonWord(word) || Articles.Contains(word);
}
/// <summary>
/// Test words to see if they are all ignorable in a response.
/// </summary>
/// <param name="words"></param>
/// <returns></returns>
public static IEnumerable<string> NonNoiseWords(IEnumerable<string> words)
{
return from word in words where !NoiseResponse(word) select word;
}
/// <summary>
/// Regular expression to break a string into words.
/// </summary>
public static Regex WordBreaker = new Regex(@"\w+", RegexOptions.Compiled);
/// <summary>
/// Break input into words.
/// </summary>
/// <param name="input">String to be broken.</param>
/// <returns>Enumeration of words.</returns>
public static IEnumerable<string> WordBreak(string input)
{
foreach (Match match in WordBreaker.Matches(input))
{
yield return match.Value;
}
}
/// <summary>
/// Break a string into words based on _ and case changes.
/// </summary>
/// <param name="original">Original string.</param>
/// <returns>String with words on case change or _ boundaries.</returns>
public static string CamelCase(string original)
{
var builder = new StringBuilder();
var name = original.Trim();
var previousUpper = Char.IsUpper(name[0]);
var previousLetter = Char.IsLetter(name[0]);
bool first = true;
for (int i = 0; i < name.Length; ++i)
{
var ch = name[i];
if (!first && (ch == '_' || ch == ' '))
{
// Non begin _ as space
builder.Append(' ');
}
else
{
var isUpper = Char.IsUpper(ch);
var isLetter = Char.IsLetter(ch);
if ((!previousUpper && isUpper)
|| (isLetter != previousLetter)
|| (!first && isUpper && (i + 1) < name.Length && Char.IsLower(name[i + 1])))
{
// Break on lower to upper, number boundaries and Upper to lower
builder.Append(' ');
}
previousUpper = isUpper;
previousLetter = isLetter;
builder.Append(ch);
if (first)
{
first = false;
}
}
}
return builder.ToString();
}
/// <summary>
/// Make sure all words end with an optional s.
/// </summary>
/// <param name="words">Words to pluralize.</param>
/// <returns>Enumeration of plural word regex.</returns>
public static IEnumerable<string> OptionalPlurals(IEnumerable<string> words)
{
bool addS = System.Threading.Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName == "en";
foreach (var original in words)
{
var word = original.ToLower();
var newWord = word;
if (addS && !NoiseWord(word) && word.Length > 1)
{
newWord = (word.EndsWith("s") ? word + "?" : word + "s?");
}
yield return newWord;
}
}
/// <summary>
/// Generate regular expressions to match word sequences in original string.
/// </summary>
/// <param name="phrase">Original string to be processed.</param>
/// <param name="maxLength">Maximum phrase length to support.</param>
/// <returns>Array of regular expressions to match subsequences in input.</returns>
/// <remarks>
/// This function will call <see cref="CamelCase(string)"/> and then will generate sub-phrases up to maxLength.
/// For example an enumeration of AngusBeefAndGarlicPizza would generate: 'angus?', 'beefs?', 'garlics?', 'pizzas?', 'angus? beefs?', 'garlics? pizzas?' and 'angus beef and garlic pizza'.
/// You can call it directly, or it is used when <see cref="FieldReflector{T}"/> generates terms or when <see cref="TermsAttribute"/> is used with a <see cref="TermsAttribute.MaxPhrase"/> argument.
/// </remarks>
public static string[] GenerateTerms(string phrase, int maxLength)
{
var words = (from word in phrase.Split(' ') where word.Length > 0 select word.ToLower()).ToArray();
var terms = new List<string>();
for (var length = 1; length <= Math.Min(words.Length, maxLength); ++length)
{
for (var start = 0; start <= words.Length - length; ++start)
{
var ngram = new ArraySegment<string>(words, start, length);
if (!ArticleOrNone(ngram.First()) && !ArticleOrNone(ngram.Last()))
{
terms.Add(string.Join(" ", OptionalPlurals(ngram)));
}
}
}
if (words.Length > maxLength)
{
terms.Add(string.Join(" ", words));
}
return terms.ToArray();
}
private static Regex _aOrAn = new Regex(@"\b(a|an)(?:\s+)([aeiou])?", RegexOptions.Compiled | RegexOptions.IgnoreCase);
/// <summary>
/// Switch 'a' before consonants and 'an' before vowels.
/// </summary>
/// <param name="input">String to fix.</param>
/// <returns>String with 'a' and 'an' normalized.</returns>
/// <remarks>
/// This is not perfect because English is complex, but does a reasonable job.
/// </remarks>
public static string ANormalization(string input)
{
if (System.Threading.Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName == "en")
{
var builder = new StringBuilder();
var last = 0;
foreach (Match match in _aOrAn.Matches(input))
{
var currentWord = match.Groups[1];
builder.Append(input.Substring(last, currentWord.Index - last));
if (match.Groups[2].Success)
{
builder.Append("an");
}
else
{
builder.Append("a");
}
last = currentWord.Index + currentWord.Length;
}
builder.Append(input.Substring(last));
return builder.ToString();
}
else
{
return input;
}
}
/// <summary>
/// Given a list of string values generate a proper English list.
/// </summary>
/// <param name="values">Value in list.</param>
/// <param name="separator">Separator between all elements except last.</param>
/// <param name="lastSeparator">Last element separator.</param>
/// <returns>Value in a proper English list.</returns>
public static string BuildList(IEnumerable<string> values, string separator, string lastSeparator)
{
var builder = new StringBuilder();
var pos = 0;
var end = values.Count() - 1;
foreach (var elt in values)
{
if (pos > 0)
{
builder.Append(pos == end ? lastSeparator : separator);
}
builder.Append(elt);
++pos;
}
return builder.ToString();
}
/// <summary> Normalize a string. </summary>
/// <param name="value"> The value to normalize. </param>
/// <param name="normalization"> The normalization to apply. </param>
/// <returns> A normalized string. </returns>
public static string Normalize(string value, CaseNormalization normalization)
{
switch (normalization)
{
case CaseNormalization.InitialUpper:
value = string.Join(" ", (from word in Language.WordBreak(value)
select char.ToUpper(word[0]) + word.Substring(1).ToLower()));
break;
case CaseNormalization.Lower: value = value.ToLower(); break;
case CaseNormalization.Upper: value = value.ToUpper(); break;
}
return value;
}
}
}

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

@ -1,319 +0,0 @@
//
// 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.Globalization;
using System.Linq;
using System.Resources;
using static Microsoft.Bot.Builder.Classic.Resource.Extensions;
namespace Microsoft.Bot.Builder.Classic.FormFlow.Advanced
{
#region Documentation
/// <summary> A resource localizer. </summary>
#endregion
public class Localizer : ILocalizer
{
public CultureInfo Culture { get; set; }
public void Add(string key, string translation)
{
if (translation != null)
{
_translations.Add(key, translation);
}
}
public void Add(string key, IEnumerable<string> list)
{
if (list.Any())
{
_arrayTranslations.Add(key, list.ToArray());
}
}
public void Add(string prefix, IReadOnlyDictionary<object, DescribeAttribute> dictionary)
{
foreach (var entry in dictionary)
{
if (entry.Value.IsLocalizable)
{
if (entry.Key.GetType().IsEnum)
{
var key = entry.Key.GetType().Name + "." + entry.Key;
if (!_translations.ContainsKey(key))
{
Add(key, entry.Value.Description);
Add(key, entry.Value.Image);
Add(key + nameof(entry.Value.Title), entry.Value.Title);
Add(key + nameof(entry.Value.SubTitle), entry.Value.SubTitle);
Add(key + nameof(entry.Value.Message), entry.Value.Message);
}
}
else
{
Add(prefix + SEPARATOR + entry.Key, entry.Value.Description);
}
}
}
}
public void Add(string prefix, IReadOnlyDictionary<object, TermsAttribute> dictionary)
{
foreach (var entry in dictionary)
{
if (entry.Value.IsLocalizable)
{
if (entry.Key.GetType().IsEnum)
{
var key = entry.Key.GetType().Name + "." + entry.Key;
if (!_arrayTranslations.ContainsKey(key))
{
_arrayTranslations.Add(key, entry.Value.Alternatives);
}
}
else
{
_arrayTranslations.Add(prefix + SEPARATOR + entry.Key, entry.Value.Alternatives);
}
}
}
}
public void Add(string prefix, IReadOnlyDictionary<TemplateUsage, TemplateAttribute> templates)
{
foreach (var template in templates.Values)
{
Add(prefix, template);
}
}
public void Add(string prefix, TemplateAttribute template)
{
if (template.IsLocalizable)
{
_templateTranslations.Add(MakeList(prefix, template.Usage.ToString()), template.Patterns);
}
}
public bool Lookup(string key, out string value)
{
return _translations.TryGetValue(key, out value);
}
public bool LookupValues(string key, out string[] values)
{
return _arrayTranslations.TryGetValue(key, out values);
}
public void LookupDictionary(string prefix, IDictionary<object, DescribeAttribute> dictionary)
{
foreach (var entry in dictionary)
{
var key = entry.Key;
var desc = entry.Value;
string skey;
if (key.GetType().IsEnum)
{
skey = key.GetType().Name + "." + key;
}
else
{
skey = prefix + SEPARATOR + key;
}
string value;
if (_translations.TryGetValue(skey, out value))
{
desc.Description = value;
}
if (_translations.TryGetValue(skey + nameof(desc.Image), out value))
{
desc.Image = value;
}
if (_translations.TryGetValue(skey + nameof(desc.Title), out value))
{
desc.Title = value;
}
if (_translations.TryGetValue(skey + nameof(desc.SubTitle), out value))
{
desc.SubTitle = value;
}
if (_translations.TryGetValue(skey + nameof(desc.Message), out value))
{
desc.Message = value;
}
}
}
public void LookupDictionary(string prefix, IDictionary<object, TermsAttribute> dictionary)
{
foreach (var key in dictionary.Keys.ToArray())
{
string skey;
if (key.GetType().IsEnum)
{
skey = key.GetType().Name + "." + key;
}
else
{
skey = prefix + SEPARATOR + key;
}
string[] values;
if (_arrayTranslations.TryGetValue(skey, out values))
{
dictionary[key] = new TermsAttribute(values);
}
}
}
public void LookupTemplates(string prefix, IDictionary<TemplateUsage, TemplateAttribute> templates)
{
foreach (var template in templates.Values)
{
string[] patterns;
if (_templateTranslations.TryGetValue(prefix + SEPARATOR + template.Usage, out patterns))
{
template.Patterns = patterns;
}
}
}
public void Remove(string key)
{
_translations.Remove(key);
_arrayTranslations.Remove(key);
_templateTranslations.Remove(key);
}
public ILocalizer Load(IDictionaryEnumerator reader, out IEnumerable<string> missing, out IEnumerable<string> extra)
{
var lmissing = new List<string>();
var lextra = new List<string>();
var newLocalizer = new Localizer();
while (reader.MoveNext())
{
var entry = (DictionaryEntry)reader.Current;
var fullKey = (string)entry.Key;
var semi = fullKey.LastIndexOf(SEPARATOR[0]);
var key = fullKey.Substring(0, semi);
var type = fullKey.Substring(semi + 1);
var val = (string)entry.Value;
if (type == "VALUE")
{
newLocalizer.Add(key, val);
}
else if (type == "LIST")
{
newLocalizer.Add(key, val.SplitList().ToArray());
}
else if (type == "TEMPLATE")
{
var elements = key.SplitList();
var usage = elements.First();
var fields = elements.Skip(1);
var patterns = val.SplitList();
var template = new TemplateAttribute((TemplateUsage)Enum.Parse(typeof(TemplateUsage), usage), patterns.ToArray());
foreach (var field in fields)
{
newLocalizer.Add(field, template);
}
}
}
// Find missing and extra keys
lmissing.AddRange(_translations.Keys.Except(newLocalizer._translations.Keys));
lmissing.AddRange(_arrayTranslations.Keys.Except(newLocalizer._arrayTranslations.Keys));
lmissing.AddRange(_templateTranslations.Keys.Except(newLocalizer._templateTranslations.Keys));
lextra.AddRange(newLocalizer._translations.Keys.Except(_translations.Keys));
lextra.AddRange(newLocalizer._arrayTranslations.Keys.Except(_arrayTranslations.Keys));
lextra.AddRange(newLocalizer._templateTranslations.Keys.Except(_templateTranslations.Keys));
missing = lmissing;
extra = lextra;
return newLocalizer;
}
public void Save(IResourceWriter writer)
{
foreach (var entry in _translations)
{
writer.AddResource(entry.Key + SEPARATOR + "VALUE", entry.Value);
}
foreach (var entry in _arrayTranslations)
{
writer.AddResource(entry.Key + SEPARATOR + "LIST", MakeList(entry.Value));
}
// Switch from field;usage -> patterns
// to usage;pattern* -> [fields]
var byPattern = new Dictionary<string, List<string>>();
foreach (var entry in _templateTranslations)
{
var names = entry.Key.SplitList().ToArray();
var field = names[0];
var usage = names[1];
var key = MakeList(AddPrefix(usage, entry.Value));
List<string> fields;
if (byPattern.TryGetValue(key, out fields))
{
fields.Add(field);
}
else
{
byPattern.Add(key, new List<string> { field });
}
}
// WriteAsync out TEMPLATE;usage;field* -> pattern*
foreach (var entry in byPattern)
{
var elements = entry.Key.SplitList().ToArray();
var usage = elements[0];
var patterns = elements.Skip(1);
var key = usage + SEPARATOR + MakeList(entry.Value) + SEPARATOR + "TEMPLATE";
writer.AddResource(key, MakeList(patterns));
}
}
protected IEnumerable<string> AddPrefix(string prefix, IEnumerable<string> suffix)
{
return new string[] { prefix }.Union(suffix);
}
protected Dictionary<string, string> _translations = new Dictionary<string, string>();
protected Dictionary<string, string[]> _arrayTranslations = new Dictionary<string, string[]>();
protected Dictionary<string, string[]> _templateTranslations = new Dictionary<string, string[]>();
}
}

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

@ -1,260 +0,0 @@
//
// 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.Classic.FormFlow.Advanced
{
internal class MatchAnalyzer
{
internal static void PrintMatches(IEnumerable<TermMatch> matches, int offset = 0)
{
foreach (var match in matches)
{
string message = string.Empty;
message = message.PadRight(match.Start + offset, ' ');
message = message.PadRight(match.End + offset, '_');
Console.WriteLine("{0} {1}", message, match.Value);
}
}
internal static bool IsIgnorable(string input, int start, int end)
{
return Language.NonWord(input.Substring(start, end - start));
}
internal static bool IsSpecial(object value)
{
return value is SpecialValues && (SpecialValues)value == SpecialValues.Field;
}
// Collapse together subsequent matches for same value or value has same range as no preference
internal static IEnumerable<TermMatch> Coalesce(IEnumerable<TermMatch> matches, string input)
{
var sorted = (from match in matches
orderby match.Start ascending
, match.End ascending
, match.Value == null ascending
select match).ToList();
while (sorted.Count() > 0)
{
var current = sorted.First();
sorted.Remove(current);
bool emit = true;
foreach (var next in sorted.ToList())
{
if (next.Covers(current))
{
// Current is completely covered by a subsequent match
emit = false;
break;
}
else if (current.End < next.Start)
{
var gap = next.Start - current.End;
if (gap > 1 && !Language.NonWord(input.Substring(current.End, gap)))
{
// Unmatched word means we can't merge any more
emit = true;
break;
}
else if (current.Value == next.Value || IsSpecial(current.Value) || IsSpecial(next.Value))
{
// Compatible, extend current match
current = new TermMatch(current.Start, next.End - current.Start, Math.Max(current.Confidence, next.Confidence),
IsSpecial(current.Value) ? next.Value : current.Value);
sorted.Remove(next);
}
}
else if (next.Value == null && current.Overlaps(next))
{
// Remove no preference if there is any overlapping meaning
sorted.Remove(next);
}
}
if (emit && !IsSpecial(current.Value))
{
sorted = (from match in sorted where !current.Covers(match) select match).ToList();
yield return current;
}
}
}
internal static IEnumerable<TermMatch> HighestConfidence(IEnumerable<TermMatch> matches)
{
var sorted = (from match in matches orderby match.Start ascending, match.End ascending, match.Confidence descending select match);
TermMatch last = null;
foreach (var match in sorted)
{
if (last == null || !last.Same(match) || last.Confidence == match.Confidence)
{
last = match;
yield return match;
}
}
}
// Full match if everything left is white space or punctuation
internal static bool IsFullMatch(string input, IEnumerable<TermMatch> matches, double threshold = 1.0)
{
bool fullMatch = matches.Count() > 0;
var sorted = from match in matches orderby match.Start ascending select match;
var current = 0;
var minConfidence = 1.0;
foreach (var match in sorted)
{
if (match.Start > current)
{
if (!IsIgnorable(input, current, match.Start))
{
fullMatch = false;
break;
}
}
if (match.Confidence < minConfidence)
{
minConfidence = match.Confidence;
}
current = match.End;
}
if (fullMatch && current < input.Length)
{
fullMatch = IsIgnorable(input, current, input.Length);
}
return fullMatch && minConfidence >= threshold;
}
internal static IEnumerable<string> Unmatched(string input, IEnumerable<TermMatch> matches)
{
var unmatched = new List<string>();
var sorted = from match in matches orderby match.Start ascending select match;
var current = 0;
foreach (var match in sorted)
{
if (match.Start > current)
{
if (!IsIgnorable(input, current, match.Start))
{
yield return input.Substring(current, match.Start - current).Trim();
}
}
current = match.End;
}
if (input.Length > current)
{
yield return input.Substring(current).Trim();
}
}
internal static double MinConfidence(IEnumerable<TermMatch> matches)
{
return matches.Count() == 0 ? 0.0 : (from match in matches select match.Confidence).Min();
}
internal static int Coverage(IEnumerable<TermMatch> matches)
{
// TODO: This does not handle partial overlaps
return matches.Count() == 0 ? 0 : (from match in GroupedMatches(matches) select match.First().Length).Sum();
}
internal static int BestMatches(params IEnumerable<TermMatch>[] allMatches)
{
int bestMatch = 0;
var confidences = (from matches in allMatches select MinConfidence(matches)).ToArray();
int bestCoverage = 0;
double bestConfidence = 0;
for (var i = 0; i < allMatches.Length; ++i)
{
var confidence = confidences[i];
var coverage = Coverage(allMatches[i]);
if (coverage > bestCoverage)
{
bestConfidence = confidence;
bestCoverage = coverage;
bestMatch = i;
}
else if (coverage == bestCoverage && confidence > bestConfidence)
{
bestConfidence = confidence;
bestCoverage = coverage;
bestMatch = i;
}
}
return bestMatch;
}
internal static List<List<TermMatch>> GroupedMatches(IEnumerable<TermMatch> matches)
{
var groups = new List<List<TermMatch>>();
var sorted = from match in matches orderby match.Start ascending, match.End descending select match;
var current = sorted.FirstOrDefault();
var currentGroup = new List<TermMatch>();
foreach (var match in sorted)
{
if (match != current)
{
if (current.Same(match))
{
// No preference loses to everything
if (current.Value == null)
{
current = match;
}
else if (match != null)
{
// Ambiguous match
currentGroup.Add(match);
}
}
else if (!current.Overlaps(match))
// TODO: We are not really handling partial overlap. To do so we need a lattice.
{
// New group
currentGroup.Add(current);
groups.Add(currentGroup);
current = match;
currentGroup = new List<TermMatch>();
}
}
}
if (current != null)
{
currentGroup.Add(current);
groups.Add(currentGroup);
}
return groups;
}
}
}

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

@ -1,33 +0,0 @@
using Microsoft.Bot.Schema;
namespace Microsoft.Bot.Builder.Classic.FormFlow.Advanced
{
internal class MessageActivityHelper
{
internal static string GetSanitizedTextInput(IMessageActivity activity)
{
var text = (activity != null ? activity.Text : null);
var result = text == null ? string.Empty : text.Trim();
if (result.StartsWith("\""))
{
result = result.Substring(1);
}
if (result.EndsWith("\""))
{
result = result.Substring(0, result.Length - 1);
}
return result;
}
internal static IMessageActivity BuildMessageWithText(string text)
{
return new Activity
{
Type = ActivityTypes.Message,
Text = text
};
}
}
}

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

@ -1,763 +0,0 @@
//
// 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.Text.RegularExpressions;
using Chronic;
using System.Threading;
using Microsoft.Bot.Schema;
namespace Microsoft.Bot.Builder.Classic.FormFlow.Advanced
{
/// <summary>
/// Recognizer for enumerated values.
/// </summary>
public sealed class RecognizeEnumeration<T> : IRecognize<T>
where T : class
{
/// <summary>
/// Delegate for mapping from a C# value to it's description.
/// </summary>
/// <param name="value">C# value to get description for.</param>
/// <returns>Description of C# value.</returns>
public delegate DescribeAttribute DescriptionDelegate(object value);
/// <summary>
/// Delegate to return the terms to match on for a C# value.
/// </summary>
/// <param name="value">C# value to get terms for.</param>
/// <returns>Enumeration of regular expressions to match on for value.</returns>
public delegate IEnumerable<string> TermsDelegate(object value);
/// <summary>
/// Constructor based on <see cref="IField{T}"/>.
/// </summary>
/// <param name="field">Field with enumerated values.</param>
public RecognizeEnumeration(IField<T> field)
{
_form = field.Form;
_allowNumbers = field.AllowNumbers;
_description = field.FieldDescription.Description;
_terms = field.FieldTerms.ToArray();
_values = field.Values.ToArray();
_valueDescriptions = field.ValueDescriptions.ToArray();
_descriptionDelegate = (value) => field.ValueDescription(value);
_termsDelegate = (value) => field.Terms(value);
_helpFormat = field.Template(field.AllowNumbers
? (field.AllowsMultiple ? TemplateUsage.EnumManyNumberHelp : TemplateUsage.EnumOneNumberHelp)
: (field.AllowsMultiple ? TemplateUsage.EnumManyWordHelp : TemplateUsage.EnumOneWordHelp));
_noPreference = field.Optional ? field.Form.Configuration.NoPreference : null;
_currentChoice = field.Form.Configuration.CurrentChoice.FirstOrDefault();
BuildPerValueMatcher(from term in field.Form.Configuration.CurrentChoice select Regex.Escape(term.Trim()).Replace(" ", @"\s+"));
}
public object[] PromptArgs()
{
return new object[0];
}
public IEnumerable<object> Values()
{
return _values;
}
public IEnumerable<DescribeAttribute> ValueDescriptions()
{
return _valueDescriptions;
}
public DescribeAttribute ValueDescription(object value)
{
return _descriptionDelegate(value);
}
public IEnumerable<string> ValidInputs(object value)
{
return _termsDelegate(value);
}
public string Help(T state, object defaultValue)
{
var values = (from val in _valueDescriptions select val.Description);
var max = _max;
if (_noPreference != null)
{
values = values.Union(new string[] { _noPreference.First() });
if (defaultValue == null)
{
--max;
}
}
if ((defaultValue != null || _noPreference != null) && _currentChoice != null)
{
values = values.Union(new string[] { _currentChoice });
}
var args = new List<object>();
if (_allowNumbers)
{
args.Add(1);
args.Add(max);
}
else
{
args.Add(null);
args.Add(null);
}
args.Add(Language.BuildList(from val in values select Language.Normalize(val, _helpFormat.ChoiceCase), _helpFormat.ChoiceSeparator, _helpFormat.ChoiceLastSeparator));
return new Prompter<T>(_helpFormat, _form, this).Prompt(state, null, args.ToArray()).Prompt;
}
public IEnumerable<TermMatch> Matches(IMessageActivity input, object defaultValue)
{
var inputText = MessageActivityHelper.GetSanitizedTextInput(input);
// if the user hit enter on an optional prompt, then consider taking the current choice as a low confidence option
bool userSkippedPrompt = string.IsNullOrWhiteSpace(inputText) && (defaultValue != null || _noPreference != null);
if (userSkippedPrompt)
{
yield return new TermMatch(0, inputText.Length, 1.0, defaultValue);
}
foreach (var expression in _expressions)
{
double maxWords = expression.MaxWords;
foreach (Match match in expression.Expression.Matches(inputText))
{
var group1 = match.Groups[1];
var group2 = match.Groups[2];
object newValue;
if (group1.Success)
{
if (ConvertSpecial(expression.Value, defaultValue, out newValue))
{
yield return new TermMatch(group1.Index, group1.Length, 1.0, newValue);
}
}
if (group2.Success)
{
var words = group2.Value.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).Length;
var confidence = System.Math.Min(words / maxWords, 1.0);
if (ConvertSpecial(expression.Value, defaultValue, out newValue))
{
yield return new TermMatch(group2.Index, group2.Length, confidence, newValue);
}
}
}
}
}
public override string ToString()
{
var builder = new StringBuilder();
builder.AppendFormat("RecognizeEnumeration({0}", _description);
builder.Append(" [");
foreach (var description in _valueDescriptions)
{
builder.Append(" ");
builder.Append(description);
}
builder.Append("])");
return builder.ToString();
}
private enum Special { CurrentChoice, NoPreference };
// Word character, any word character, any digit, any positive group over word characters
private const string WORD = @"(\w|\\w|\\d|(\[(?>(\w|-)+|\[(?<number>)|\](?<-number>))*(?(number)(?!))\]))";
// Starts with matching word character
private static Regex _wordStart = new Regex($@"^{WORD}", RegexOptions.Compiled);
// Ends with matching word character with optional repetitions
private static Regex _wordEnd = new Regex($@"{WORD}(\?|\*|\+|\{{\d+\}}|\{{,\d+\}}|\{{\d+,\d+\}})?$", RegexOptions.Compiled);
private void BuildPerValueMatcher(IEnumerable<string> currentChoice)
{
if (currentChoice != null)
{
// 0 is reserved for current default if any
AddExpression(0, Special.CurrentChoice, currentChoice, _allowNumbers);
}
var n = 1;
foreach (var value in _values)
{
n = AddExpression(n, value, _termsDelegate(value), _allowNumbers);
}
if (_noPreference != null)
{
// Add recognizer for no preference
n = AddExpression(n, Special.NoPreference, _noPreference, _allowNumbers);
}
if (_terms != null && _terms.Count() > 0)
{
// Add field terms to help disambiguate
AddExpression(n, SpecialValues.Field, _terms, false);
}
_max = n - 1;
}
private int NumberOfWords(string regex)
{
return regex.Split(new string[] { @"\s", " " }, StringSplitOptions.RemoveEmptyEntries).Length;
}
// Generate a regex with 3 parts:
// Group 1: startWord <number> endWord
// Group 2: (all word terms) with optional start/end words
// If a group is empty, match on NOMATCH constant.
private const string NOMATCH = "__qqqq__";
private int AddExpression(int n, object value, IEnumerable<string> terms, bool allowNumbers)
{
var orderedTerms = (from term in terms orderby term.Length descending select term).ToArray();
var words = new StringBuilder();
var first = true;
int maxWords = 0;
if (orderedTerms.Length > 0)
{
maxWords = terms.Max(NumberOfWords);
foreach (var term in orderedTerms)
{
var nterm = term.Trim().Replace(" ", @"\s+");
if (nterm != string.Empty)
{
if (first)
{
first = false;
words.Append('(');
}
else
{
words.Append('|');
}
words.Append("(?:");
if (_wordStart.Match(nterm).Success && _wordEnd.Match(nterm).Success)
{
words.Append($@"\b{nterm}\b");
}
else
{
words.Append(nterm);
}
words.Append(')');
}
}
}
if (first)
{
words.Append($@"(\b(?:{NOMATCH})");
}
words.Append(')');
var numbers = allowNumbers ? $"(\\b{n}\\b)" : NOMATCH;
var expr = $"{numbers}|{words}";
_expressions.Add(new ValueAndExpression(value, new Regex(expr.ToString(), RegexOptions.IgnoreCase), maxWords));
++n;
return n;
}
private bool ConvertSpecial(object value, object defaultValue, out object newValue)
{
bool ok = true;
newValue = value;
if (value is Special)
{
var special = (Special)value;
if (special == Special.CurrentChoice && (_noPreference != null || defaultValue != null))
{
newValue = defaultValue;
}
else if (special == Special.NoPreference)
{
newValue = null;
}
else
{
ok = false;
}
}
return ok;
}
private class ValueAndExpression
{
public ValueAndExpression(object value, Regex expression, int maxWords)
{
Value = value;
Expression = expression;
MaxWords = maxWords;
}
public readonly object Value;
public readonly Regex Expression;
public readonly int MaxWords;
}
private readonly IForm<T> _form;
private readonly string _description;
private readonly IEnumerable<string> _noPreference;
private readonly string _currentChoice;
private readonly bool _allowNumbers;
private readonly IEnumerable<string> _terms;
private readonly IEnumerable<object> _values;
private readonly IEnumerable<DescribeAttribute> _valueDescriptions;
private readonly DescriptionDelegate _descriptionDelegate;
private readonly TermsDelegate _termsDelegate;
private readonly TemplateAttribute _helpFormat;
private int _max;
private readonly List<ValueAndExpression> _expressions = new List<ValueAndExpression>();
}
/// <summary>
/// Abstract class for constructing primitive value recognizers.
/// </summary>
/// <typeparam name="T">Form state.</typeparam>
public abstract class RecognizePrimitive<T> : IRecognize<T>
where T : class
{
/// <summary>
/// Constructor using <see cref="IField{T}"/>.
/// </summary>
/// <param name="field">Field to build recognizer for.</param>
public RecognizePrimitive(IField<T> field)
{
_field = field;
_currentChoices = new HashSet<string>(from choice in field.Form.Configuration.CurrentChoice
select choice.Trim().ToLower());
if (field.Optional)
{
if (field.IsNullable)
{
_noPreference = new HashSet<string>(from choice in field.Form.Configuration.NoPreference
select choice.Trim().ToLower());
}
else
{
throw new ArgumentException("Primitive values must be nullable to be optional.");
}
}
}
public virtual object[] PromptArgs()
{
return new object[0];
}
/// <summary>
/// Abstract method for parsing input.
/// </summary>
/// <param name="input">Input to match.</param>
/// <returns>TermMatch if input is a match.</returns>
public abstract TermMatch Parse(string input);
public virtual IEnumerable<TermMatch> Matches(IMessageActivity input, object defaultValue = null)
{
var inputText = MessageActivityHelper.GetSanitizedTextInput(input);
var matchValue = inputText.Trim().ToLower();
if (_noPreference != null && _noPreference.Contains(matchValue))
{
yield return new TermMatch(0, inputText.Length, 1.0, null);
}
else if ((defaultValue != null || _noPreference != null) && (matchValue == string.Empty || _currentChoices.Contains(matchValue)))
{
yield return new TermMatch(0, inputText.Length, 1.0, defaultValue);
}
else
{
var result = Parse(inputText);
if (result != null)
{
yield return result;
}
}
}
public abstract IEnumerable<string> ValidInputs(object value);
public abstract DescribeAttribute ValueDescription(object value);
public virtual IEnumerable<DescribeAttribute> ValueDescriptions()
{
return new DescribeAttribute[0];
}
public virtual IEnumerable<object> Values()
{
return null;
}
public abstract string Help(T state, object defaultValue);
/// <summary>
/// Return the help template args for current choice and no preference.
/// </summary>
/// <param name="state">Form state.</param>
/// <param name="defaultValue">Current value of field.</param>
/// <returns></returns>
protected List<object> HelpArgs(T state, object defaultValue)
{
var args = new List<object>();
if (defaultValue != null || _field.Optional)
{
args.Add(_field.Form.Configuration.CurrentChoice.First());
if (_field.Optional)
{
args.Add(_field.Form.Configuration.NoPreference.First());
}
else
{
args.Add(null);
}
}
else
{
args.Add(null);
args.Add(null);
}
return args;
}
/// <summary>
/// Field being filled information.
/// </summary>
protected IField<T> _field;
private HashSet<string> _currentChoices;
private HashSet<string> _noPreference;
}
/// <summary>
/// Recognize a boolean value.
/// </summary>
/// <typeparam name="T">Form state.</typeparam>
public sealed class RecognizeBool<T> : RecognizePrimitive<T>
where T : class
{
/// <summary>
/// Construct a boolean recognizer for a field.
/// </summary>
/// <param name="field">Boolean field.</param>
public RecognizeBool(IField<T> field)
: base(field)
{
_yes = new HashSet<string>(from term in field.Form.Configuration.Yes
select term.Trim().ToLower());
_no = new HashSet<string>(from term in field.Form.Configuration.No
select term.Trim().ToLower());
}
public override TermMatch Parse(string input)
{
TermMatch result = null;
var matchValue = input.Trim().ToLower();
if (_yes.Contains(matchValue))
{
result = new TermMatch(0, input.Length, 1.0, true);
}
else if (_no.Contains(matchValue))
{
result = new TermMatch(0, input.Length, 1.0, false);
}
return result;
}
public override string Help(T state, object defaultValue)
{
var prompt = new Prompter<T>(_field.Template(TemplateUsage.BoolHelp), _field.Form, null);
var args = HelpArgs(state, defaultValue);
return prompt.Prompt(state, _field, args.ToArray()).Prompt;
}
public override IEnumerable<string> ValidInputs(object value)
{
return (bool)value
? _field.Form.Configuration.Yes
: _field.Form.Configuration.No;
}
public override DescribeAttribute ValueDescription(object value)
{
return new DescribeAttribute(((bool)value
? _field.Form.Configuration.Yes
: _field.Form.Configuration.No).First());
}
public override IEnumerable<DescribeAttribute> ValueDescriptions()
{
return new DescribeAttribute[] { ValueDescription(true), ValueDescription(false) };
}
private HashSet<string> _yes;
private HashSet<string> _no;
}
/// <summary>
/// Recognize a string field.
/// </summary>
/// <typeparam name="T">Form state.</typeparam>
public sealed class RecognizeString<T> : RecognizePrimitive<T>
where T : class
{
/// <summary>
/// Construct a string recognizer for a field.
/// </summary>
/// <param name="field">string field.</param>
public RecognizeString(IField<T> field)
: base(field)
{
}
public override IEnumerable<string> ValidInputs(object value)
{
yield return (string)value;
}
public override DescribeAttribute ValueDescription(object value)
{
return new DescribeAttribute((string)value);
}
public override TermMatch Parse(string input)
{
TermMatch result = null;
if (!string.IsNullOrWhiteSpace(input))
{
// Confidence is 0.0 so commands get a crack
result = new TermMatch(0, input.Length, 0.0, input);
}
return result;
}
public override string Help(T state, object defaultValue)
{
var prompt = new Prompter<T>(_field.Template(TemplateUsage.StringHelp), _field.Form, null);
var args = HelpArgs(state, defaultValue);
return prompt.Prompt(state, _field, args.ToArray()).Prompt;
}
}
/// <summary>
/// Recognize a numeric field.
/// </summary>
/// <typeparam name="T">Form state.</typeparam>
public sealed class RecognizeNumber<T> : RecognizePrimitive<T>
where T : class
{
/// <summary>
/// Construct a numeric recognizer for a field.
/// </summary>
/// <param name="field">Numeric field.</param>
public RecognizeNumber(IField<T> field)
: base(field)
{
double min, max;
_showLimits = field.Limits(out min, out max);
_min = (long)min;
_max = (long)max;
}
public override object[] PromptArgs()
{
return _showLimits ? new object[] { _min, _max } : new object[] { null, null };
}
public override DescribeAttribute ValueDescription(object value)
{
return new DescribeAttribute(((long)Convert.ChangeType(value, typeof(long))).ToString(Thread.CurrentThread.CurrentUICulture.NumberFormat));
}
public override IEnumerable<string> ValidInputs(object value)
{
yield return ((long)value).ToString(Thread.CurrentThread.CurrentUICulture.NumberFormat);
}
public override TermMatch Parse(string input)
{
TermMatch result = null;
long number;
if (long.TryParse(input, NumberStyles.Integer, Thread.CurrentThread.CurrentUICulture.NumberFormat, out number))
{
if (number >= _min && number <= _max)
{
result = new TermMatch(0, input.Length, 1.0, number);
}
}
return result;
}
public override string Help(T state, object defaultValue)
{
var prompt = new Prompter<T>(_field.Template(TemplateUsage.IntegerHelp), _field.Form, null);
var args = HelpArgs(state, defaultValue);
if (_showLimits)
{
args.Add(_min);
args.Add(_max);
}
return prompt.Prompt(state, _field, args.ToArray()).Prompt;
}
private long _min;
private long _max;
private bool _showLimits;
}
/// <summary>
/// Recognize a double or float field.
/// </summary>
/// <typeparam name="T"></typeparam>
public sealed class RecognizeDouble<T> : RecognizePrimitive<T>
where T : class
{
/// <summary>
/// Construct a double or float recognizer for a field.
/// </summary>
/// <param name="field">Float or double field.</param>
public RecognizeDouble(IField<T> field)
: base(field)
{
_showLimits = field.Limits(out _min, out _max);
}
public override object[] PromptArgs()
{
return _showLimits ? new object[] { _min, _max } : new object[] { null, null };
}
public override DescribeAttribute ValueDescription(object value)
{
return new DescribeAttribute(((double)Convert.ChangeType(value, typeof(double))).ToString(Thread.CurrentThread.CurrentUICulture.NumberFormat));
}
public override IEnumerable<string> ValidInputs(object value)
{
yield return ((double)value).ToString(Thread.CurrentThread.CurrentUICulture.NumberFormat);
}
public override TermMatch Parse(string input)
{
TermMatch result = null;
double number;
if (double.TryParse(input, NumberStyles.Float, Thread.CurrentThread.CurrentUICulture.NumberFormat, out number))
{
if (number >= _min && number <= _max)
{
result = new TermMatch(0, input.Length, 1.0, number);
}
}
return result;
}
public override string Help(T state, object defaultValue)
{
var prompt = new Prompter<T>(_field.Template(TemplateUsage.DoubleHelp), _field.Form, null);
var args = HelpArgs(state, defaultValue);
if (_showLimits)
{
args.Add(_min);
args.Add(_max);
}
return prompt.Prompt(state, _field, args.ToArray()).Prompt;
}
private double _min;
private double _max;
private bool _showLimits;
}
/// <summary>
/// Recognize a date/time expression.
/// </summary>
/// <typeparam name="T">Form state.</typeparam>
/// <remarks>
/// Expressions recognized are based the C# Chronic parser for English and
/// the C# DateTime parser otherwise.
/// </remarks>
public sealed class RecognizeDateTime<T> : RecognizePrimitive<T>
where T : class
{
/// <summary>
/// Construct a date/time recognizer.
/// </summary>
/// <param name="field">DateTime field.</param>
public RecognizeDateTime(IField<T> field)
: base(field)
{
_parser = new Chronic.Parser();
}
public override string Help(T state, object defaultValue)
{
var prompt = new Prompter<T>(_field.Template(TemplateUsage.DateTimeHelp), _field.Form, null);
var args = HelpArgs(state, defaultValue);
return prompt.Prompt(state, _field, args.ToArray()).Prompt;
}
public override TermMatch Parse(string input)
{
TermMatch match = null;
if (Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName != "en")
{
DateTime dt;
if (DateTime.TryParse(input, Thread.CurrentThread.CurrentUICulture.DateTimeFormat, DateTimeStyles.None, out dt))
{
match = new TermMatch(0, input.Length, 1.0, dt);
}
}
else
{
var parse = _parser.Parse(input);
if (parse != null && parse.Start.HasValue)
{
match = new TermMatch(0, input.Length, 1.0, parse.Start.Value);
}
}
return match;
}
public override IEnumerable<string> ValidInputs(object value)
{
yield return ValueDescription(value).Description;
}
public override DescribeAttribute ValueDescription(object value)
{
return new DescribeAttribute(((DateTime)value).ToString(Thread.CurrentThread.CurrentUICulture.DateTimeFormat));
}
private Parser _parser;
}
}

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

@ -1,937 +0,0 @@
//
// 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.Linq;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Classic.Dialogs;
using Microsoft.Bot.Schema;
namespace Microsoft.Bot.Builder.Classic.FormFlow.Advanced
{
internal class FieldStep<T> : IStep<T>
where T : class
{
public FieldStep(string name, IForm<T> form)
{
_name = name;
_field = form.Fields.Field(name);
}
public string Name
{
get
{
return _name;
}
}
public StepType Type
{
get
{
return StepType.Field;
}
}
public TemplateBaseAttribute Annotation
{
get
{
return _field.Prompt?.Annotation;
}
}
public IField<T> Field
{
get
{
return _field;
}
}
public void SaveResources()
{
_field.SaveResources();
}
public void Localize()
{
_field.Localize();
}
public bool Active(T state)
{
return _field.Active(state);
}
public async Task<bool> DefineAsync(T state)
{
return await _field.DefineAsync(state);
}
public FormPrompt Start(IDialogContext context, T state, FormState form)
{
form.SetPhase(StepPhase.Responding);
form.StepState = new FieldStepState(FieldStepStates.SentPrompt);
return _field.Prompt.Prompt(state, _field, _field.Prompt.Recognizer.PromptArgs());
}
public IEnumerable<TermMatch> Match(IDialogContext context, T state, FormState form, IMessageActivity input)
{
IEnumerable<TermMatch> matches = null;
Debug.Assert(form.Phase() == StepPhase.Responding);
var stepState = (FieldStepState)form.StepState;
if (stepState.State == FieldStepStates.SentPrompt)
{
matches = _field.Prompt.Recognizer.Matches(input, _field.GetValue(state));
}
else if (stepState.State == FieldStepStates.SentClarify)
{
var fieldState = (FieldStepState)form.StepState;
var iprompt = _field.Prompt;
var choiceRecognizer = ClarifyRecognizer(fieldState, iprompt.Recognizer);
matches = MatchAnalyzer.Coalesce(MatchAnalyzer.HighestConfidence(choiceRecognizer.Matches(input)), MessageActivityHelper.GetSanitizedTextInput(input)).ToArray();
if (matches.Count() > 1)
{
matches = new TermMatch[0];
}
}
#if DEBUG
if (FormDialog.DebugRecognizers)
{
MatchAnalyzer.PrintMatches(matches, 2);
}
#endif
return matches;
}
public async Task<StepResult> ProcessAsync(IDialogContext context, T state, FormState form, IMessageActivity input, IEnumerable<TermMatch> matches)
{
var inputText = MessageActivityHelper.GetSanitizedTextInput(input);
ValidateResult feedback = new ValidateResult();
feedback.IsValid = true;
feedback.Feedback = null;
feedback.FeedbackCard = null;
feedback.Choices = null;
FormPrompt prompt = null;
FormPrompt feedbackPrompt = null;
var iprompt = _field.Prompt;
var fieldState = (FieldStepState)form.StepState;
object response = null;
if (fieldState.State == FieldStepStates.SentPrompt)
{
// Response to prompt
var firstMatch = matches.FirstOrDefault();
if (matches.Count() == 1)
{
response = firstMatch.Value;
if (_field.AllowsMultiple && response != null
&& (response.GetType() == typeof(string) || !response.GetType().IsIEnumerable()))
{
response = new List<object>() { response };
}
feedback = await SetValueAsync(state, response, form);
if (!feedback.IsValid && feedback.Choices != null)
{
var choices = new Ambiguous(inputText.Substring(firstMatch.Start, firstMatch.Length), feedback.Choices);
fieldState.State = FieldStepStates.SentClarify;
fieldState.Settled = new List<object>();
fieldState.Clarifications = new List<Ambiguous>() { choices };
response = SetValue(state, null);
prompt = ClarifyPrompt((FieldStepState)form.StepState, iprompt.Recognizer, state);
}
}
else if (matches.Count() > 1)
{
// Check multiple matches for ambiguity
var groups = MatchAnalyzer.GroupedMatches(matches);
// 1) Could be multiple match groups like for ingredients.
// 2) Could be overlapping matches like "onion".
// 3) Could be multiple matches where only one is expected.
if (!_field.AllowsMultiple)
{
// Create a single group of all possibilities if only want one value
var mergedGroup = groups.SelectMany((group) => group).ToList();
groups = new List<List<TermMatch>>() { mergedGroup };
}
var ambiguous = new List<Ambiguous>();
var settled = new List<object>();
foreach (var choices in groups)
{
if (choices.Count > 1)
{
var unclearResponses = string.Join(" ", (from choice in choices select inputText.Substring(choice.Start, choice.Length)).Distinct());
var values = from match in choices select match.Value;
ambiguous.Add(new Ambiguous(unclearResponses, values));
}
else
{
var matchValue = choices.First().Value;
if (matchValue != null && matchValue.GetType() != typeof(string) && matchValue.GetType().IsIEnumerable())
{
foreach (var value in (System.Collections.IEnumerable)matchValue)
{
settled.Add(value);
}
}
else
{
settled.Add(choices.First().Value);
}
}
}
if (settled.Count > 1)
{
// Remove no preference if present
settled.Remove(null);
}
if (ambiguous.Count > 0)
{
// Need 1 or more clarifications
fieldState.State = FieldStepStates.SentClarify;
fieldState.Settled = settled;
fieldState.Clarifications = ambiguous;
response = SetValue(state, null);
prompt = ClarifyPrompt((FieldStepState)form.StepState, iprompt.Recognizer, state);
}
else
{
if (_field.AllowsMultiple)
{
response = settled;
feedback = await SetValueAsync(state, response, form);
}
else
{
Debug.Assert(settled.Count == 1);
response = settled.First();
feedback = await SetValueAsync(state, response, form);
}
}
}
var unmatched = MatchAnalyzer.Unmatched(inputText, matches);
var unmatchedWords = string.Join(" ", unmatched);
var nonNoise = Language.NonNoiseWords(Language.WordBreak(unmatchedWords)).ToArray();
fieldState.Unmatched = null;
if (_field.Prompt.Annotation.Feedback == FeedbackOptions.Always)
{
fieldState.Unmatched = string.Join(" ", nonNoise);
}
else if (_field.Prompt.Annotation.Feedback == FeedbackOptions.Auto
&& nonNoise.Any()
&& unmatched.Any())
{
fieldState.Unmatched = string.Join(" ", nonNoise);
}
}
else if (fieldState.State == FieldStepStates.SentClarify)
{
if (matches.Count() == 1)
{
// Clarified ambiguity
var clarify = NeedsClarification(fieldState);
fieldState.Settled.Add(matches.First().Value);
fieldState.Clarifications.Remove(clarify);
if (prompt == null)
{
// No clarification left, so set the field
if (_field.AllowsMultiple)
{
response = fieldState.Settled;
feedback = await SetValueAsync(state, response, form);
}
else
{
Debug.Assert(fieldState.Settled.Count == 1);
response = fieldState.Settled.First();
feedback = await SetValueAsync(state, response, form);
}
form.SetPhase(StepPhase.Completed);
}
}
}
if (form.Phase() == StepPhase.Completed)
{
form.StepState = null;
if (fieldState.Unmatched != null)
{
if (feedback.FeedbackCard != null)
{
feedbackPrompt = feedback.FeedbackCard;
}
else if (feedback.Feedback != null)
{
feedbackPrompt = new FormPrompt { Prompt = feedback.Feedback };
}
else
{
if (fieldState.Unmatched != string.Empty)
{
feedbackPrompt = new Prompter<T>(_field.Template(TemplateUsage.Feedback), _field.Form, null).Prompt(state, _field, fieldState.Unmatched);
}
else
{
feedbackPrompt = new Prompter<T>(_field.Template(TemplateUsage.Feedback), _field.Form, null).Prompt(state, _field);
}
}
}
}
var next = _field.Next(response, state);
return new StepResult(feedback.IsValid, next, feedbackPrompt ?? (feedback.FeedbackCard ?? new FormPrompt { Prompt = feedback.Feedback }), prompt);
}
public bool InClarify(FormState form)
{
var state = (FieldStepState)form.StepState;
return state.State == FieldStepStates.SentClarify;
}
public FormPrompt NotUnderstood(IDialogContext context, T state, FormState form, IMessageActivity input)
{
var inputText = MessageActivityHelper.GetSanitizedTextInput(input);
FormPrompt feedback = null;
var iprompt = _field.Prompt;
var fieldState = (FieldStepState)form.StepState;
if (fieldState.State == FieldStepStates.SentPrompt)
{
feedback = Template(TemplateUsage.NotUnderstood).Prompt(state, _field, inputText);
}
else if (fieldState.State == FieldStepStates.SentClarify)
{
feedback = Template(TemplateUsage.NotUnderstood, ClarifyRecognizer(fieldState, _field.Prompt.Recognizer)).Prompt(state, _field, inputText);
}
return feedback;
}
public bool Back(IDialogContext context, T state, FormState form)
{
bool backedUp = false;
var fieldState = (FieldStepState)form.StepState;
if (fieldState.State == FieldStepStates.SentClarify)
{
var desc = _field.Form.Fields.Field(_name);
if (desc.AllowsMultiple)
{
desc.SetValue(state, fieldState.Settled);
}
else if (fieldState.Settled.Any())
{
desc.SetValue(state, fieldState.Settled.First());
}
form.SetPhase(StepPhase.Ready);
backedUp = true;
}
return backedUp;
}
public FormPrompt Help(T state, FormState form, string commandHelp)
{
var fieldState = (FieldStepState)form.StepState;
IPrompt<T> template;
if (fieldState.State == FieldStepStates.SentClarify)
{
template = Template(TemplateUsage.HelpClarify, ClarifyRecognizer(fieldState, _field.Prompt.Recognizer));
}
else
{
template = Template(TemplateUsage.Help, _field.Prompt.Recognizer);
}
var help = template.Prompt(state, _field, "* " + template.Recognizer.Help(state, _field.GetValue(state)), commandHelp);
return new FormPrompt { Prompt = "* " + help.Prompt, Buttons = help.Buttons };
}
public IEnumerable<string> Dependencies
{
get
{
return Array.Empty<string>();
}
}
private IPrompt<T> Template(TemplateUsage usage, IRecognize<T> recognizer = null)
{
var template = _field.Template(usage);
return new Prompter<T>(template, _field.Form, recognizer == null ? _field.Prompt.Recognizer : recognizer);
}
private object SetValue(T state, object value)
{
var desc = _field.Form.Fields.Field(_name);
if (value == null)
{
desc.SetUnknown(state);
}
else
{
desc.SetValue(state, value);
}
return value;
}
private async Task<ValidateResult> SetValueAsync(T state, object value, FormState form)
{
var desc = _field.Form.Fields.Field(_name);
var feedback = await desc.ValidateAsync(state, value);
if (feedback.IsValid)
{
SetValue(state, feedback.Value);
form.SetPhase(StepPhase.Completed);
}
else if (feedback.Feedback == null)
{
feedback.Feedback = string.Empty;
}
return feedback;
}
private Ambiguous NeedsClarification(FieldStepState stepState)
{
Ambiguous clarify = null;
foreach (var clarification in stepState.Clarifications)
{
if (clarification.Values.Length > 1)
{
clarify = clarification;
break;
}
}
return clarify;
}
private class FieldClarify : Field<T>
{
public FieldClarify(IField<T> root)
: base(root.Name, FieldRole.Value)
{
Form = root.Form;
var template = root.Template(TemplateUsage.Clarify);
SetPrompt(new PromptAttribute(template));
ReplaceTemplate(template);
ReplaceTemplate(root.Template(template.AllowNumbers ? TemplateUsage.EnumOneNumberHelp : TemplateUsage.EnumManyNumberHelp));
}
public override bool IsUnknown(T state)
{
return true;
}
}
private IField<T> ClarifyField(Ambiguous clarify, IRecognize<T> recognizer)
{
var field = new FieldClarify(_field);
foreach (var value in clarify.Values)
{
var choice = value as Choice;
if (choice != null)
{
field.AddDescription(choice.Value,
choice.Description.Description,
choice.Description.Image,
choice.Description.Message);
field.AddTerms(choice.Value, choice.Terms.Alternatives);
}
else
{
var desc = recognizer.ValueDescription(value);
field.AddDescription(value, desc.Description, desc.Image);
field.AddTerms(value, recognizer.ValidInputs(value).ToArray());
}
}
return field;
}
private IRecognize<T> ClarifyRecognizer(FieldStepState stepState, IRecognize<T> recognizer)
{
var clarify = NeedsClarification(stepState);
return (clarify != null ? new RecognizeEnumeration<T>(ClarifyField(clarify, recognizer)) : null);
}
private FormPrompt ClarifyPrompt(FieldStepState stepState, IRecognize<T> recognizer, T state)
{
var clarify = NeedsClarification(stepState);
FormPrompt prompt = null;
if (clarify != null)
{
var field = ClarifyField(clarify, recognizer);
var prompter = new Prompter<T>(field.Template(TemplateUsage.Clarify), field.Form, new RecognizeEnumeration<T>(field));
prompt = prompter.Prompt(state, field, clarify.Response);
}
return prompt;
}
internal enum FieldStepStates { Unknown, SentPrompt, SentClarify };
[Serializable]
internal class Ambiguous
{
public readonly string Response;
public object[] Values;
public Ambiguous(string response, IEnumerable<object> values)
{
Response = response;
Values = values.ToArray<object>();
}
}
[Serializable]
internal class FieldStepState
{
internal FieldStepStates State;
internal string Unmatched;
internal List<object> Settled;
internal List<Ambiguous> Clarifications;
public FieldStepState(FieldStepStates state)
{
State = state;
}
}
private readonly string _name;
private readonly IField<T> _field;
}
internal class ConfirmStep<T> : IStep<T>
where T : class
{
public ConfirmStep(IField<T> field)
{
_field = field;
}
public bool Back(IDialogContext context, T state, FormState form)
{
return false;
}
public IField<T> Field
{
get
{
return _field;
}
}
public void SaveResources()
{
_field.SaveResources();
}
public void Localize()
{
_field.Localize();
}
public bool Active(T state)
{
return _field.Active(state);
}
public IEnumerable<TermMatch> Match(IDialogContext context, T state, FormState form, IMessageActivity input)
{
return _field.Prompt.Recognizer.Matches(input);
}
public string Name
{
get
{
return _field.Name;
}
}
public TemplateBaseAttribute Annotation
{
get
{
return _field.Prompt?.Annotation;
}
}
public FormPrompt NotUnderstood(IDialogContext context, T state, FormState form, IMessageActivity input)
{
var template = _field.Template(TemplateUsage.NotUnderstood);
var prompter = new Prompter<T>(template, _field.Form, null);
return prompter.Prompt(state, _field, MessageActivityHelper.GetSanitizedTextInput(input));
}
public async Task<StepResult> ProcessAsync(IDialogContext context, T state, FormState form, IMessageActivity input, IEnumerable<TermMatch> matches)
{
var value = matches.First().Value;
form.StepState = null;
form.SetPhase((bool)value ? StepPhase.Completed : StepPhase.Ready);
var next = _field.Next(value, state);
return new StepResult(true, next, feedback: null, prompt: null);
}
public async Task<bool> DefineAsync(T state)
{
return await _field.DefineAsync(state);
}
public FormPrompt Start(IDialogContext context, T state, FormState form)
{
form.SetPhase(StepPhase.Responding);
return _field.Prompt.Prompt(state, _field);
}
public FormPrompt Help(T state, FormState form, string commandHelp)
{
var template = _field.Template(TemplateUsage.HelpConfirm);
var prompt = new Prompter<T>(template, _field.Form, _field.Prompt.Recognizer);
var help = prompt.Prompt(state, _field, "* " + prompt.Recognizer.Help(state, null), commandHelp);
return new FormPrompt { Prompt = "* " + help.Prompt, Buttons = help.Buttons };
}
public bool InClarify(FormState form)
{
return false;
}
public StepType Type
{
get
{
return StepType.Confirm;
}
}
public IEnumerable<string> Dependencies
{
get
{
return _field.Dependencies;
}
}
private readonly IField<T> _field;
}
internal class NavigationField<T> : Field<T>
where T : class
{
public NavigationField(string name, string startField, IForm<T> form, T state, FormState formState, Fields<T> fields)
: base(name, FieldRole.Value)
{
Form = form;
var field = form.Fields.Field(startField);
SetFieldDescription(_form.Configuration.Navigation);
SetOptional();
foreach (var value in formState.Next.Names)
{
var svalue = (string)value;
var sfield = form.Fields.Field(svalue);
var fieldPrompt = sfield.Template(TemplateUsage.NavigationFormat);
var prompter = new Prompter<T>(fieldPrompt, form, sfield.Prompt.Recognizer);
AddDescription(value, prompter.Prompt(state, sfield).Prompt, null, sfield.FieldDescription.Message ?? sfield.FieldDescription.Description);
AddTerms(value, form.Fields.Field(svalue).FieldTerms.ToArray());
}
var template = field.Template(TemplateUsage.Navigation);
SetPrompt(new PromptAttribute(template));
SetRecognizer(new RecognizeEnumeration<T>(this));
_prompt = new Prompter<T>(template, form, _recognizer, fields);
}
public override bool IsUnknown(T state)
{
return true;
}
}
internal class NavigationStep<T> : IStep<T>
where T : class
{
private const string _name = "__navigate__";
public NavigationStep(string startField, IForm<T> form, T state, FormState formState)
{
var fields = new Fields<T>();
_field = new NavigationField<T>(_name, startField, form, state, formState, fields);
fields.Add(_field);
_fields = fields;
}
public bool Back(IDialogContext context, T state, FormState form)
{
form.Next = null;
return false;
}
public IField<T> Field
{
get
{
return _field;
}
}
public bool Active(T state)
{
return true;
}
public IEnumerable<TermMatch> Match(IDialogContext context, T state, FormState form, IMessageActivity input)
{
return _field.Prompt.Recognizer.Matches(input);
}
public string Name
{
get
{
return "Navigation";
}
}
public TemplateBaseAttribute Annotation
{
get
{
return _field.Prompt.Annotation;
}
}
public bool InClarify(FormState form)
{
return false;
}
public FormPrompt NotUnderstood(IDialogContext context, T state, FormState form, IMessageActivity input)
{
var template = _field.Template(TemplateUsage.NotUnderstood);
return new Prompter<T>(template, _field.Form, _field.Prompt.Recognizer, _fields).Prompt(state, _field, MessageActivityHelper.GetSanitizedTextInput(input));
}
public async Task<StepResult> ProcessAsync(IDialogContext context, T state, FormState form, IMessageActivity input, IEnumerable<TermMatch> matches)
{
NextStep next;
form.Next = null;
var val = matches.First().Value;
if (val == null)
{
next = new NextStep();
}
else
{
next = new NextStep(new string[] { (string)val });
}
return new StepResult(true, next, feedback: null, prompt: null);
}
public Task<bool> DefineAsync(T state)
{
throw new NotImplementedException();
}
public FormPrompt Start(IDialogContext context, T state, FormState form)
{
return _field.Prompt.Prompt(state, _field);
}
public StepType Type
{
get
{
return StepType.Navigation;
}
}
public FormPrompt Help(T state, FormState form, string commandHelp)
{
var recognizer = _field.Prompt.Recognizer;
var prompt = new Prompter<T>(_field.Template(TemplateUsage.HelpNavigation), _field.Form, recognizer, _fields);
var help = prompt.Prompt(state, _field, "* " + recognizer.Help(state, null), commandHelp);
return new FormPrompt { Prompt = "* " + help.Prompt, Buttons = help.Buttons };
}
public void SaveResources()
{
}
public void Localize()
{
}
public IEnumerable<string> Dependencies
{
get
{
return Array.Empty<string>();
}
}
private readonly IField<T> _field;
private readonly IFields<T> _fields;
}
internal class MessageStep<T> : IStep<T>
where T : class
{
public MessageStep(MessageDelegate<T> generateMessage, ActiveDelegate<T> condition, IEnumerable<string> dependencies, IForm<T> form)
{
_name = "message" + form.Steps.Count.ToString();
_form = form;
_message = generateMessage;
_condition = (condition == null ? (state) => true : condition);
_dependencies = dependencies ?? form.Dependencies(form.Steps.Count);
}
public MessageStep(PromptAttribute prompt, ActiveDelegate<T> condition, IEnumerable<string> dependencies, IForm<T> form)
{
_name = "message" + form.Steps.Count.ToString();
_form = form;
_promptDefinition = prompt;
_condition = (condition == null ? (state) => true : condition);
_dependencies = dependencies ?? form.Dependencies(form.Steps.Count);
}
public bool Active(T state)
{
return _condition(state);
}
public bool Back(IDialogContext context, T state, FormState form)
{
return false;
}
public FormPrompt Help(T state, FormState form, string commandHelp)
{
return null;
}
public IEnumerable<string> Dependencies
{
get
{
return _dependencies;
}
}
public IField<T> Field
{
get
{
return null;
}
}
public IEnumerable<TermMatch> Match(IDialogContext context, T state, FormState form, IMessageActivity input)
{
throw new NotImplementedException();
}
public string Name
{
get
{
return _name;
}
}
public TemplateBaseAttribute Annotation
{
get { return _promptDefinition; }
}
public bool InClarify(FormState form)
{
return false;
}
public FormPrompt NotUnderstood(IDialogContext context, T state, FormState form, IMessageActivity input)
{
throw new NotImplementedException();
}
public Task<StepResult> ProcessAsync(IDialogContext context, T state, FormState form, IMessageActivity input, IEnumerable<TermMatch> matches)
{
throw new NotImplementedException();
}
public async Task<bool> DefineAsync(T state)
{
if (_message != null)
{
_promptDefinition = await _message(state);
}
return true;
}
public FormPrompt Start(IDialogContext context, T state, FormState form)
{
form.SetPhase(StepPhase.Completed);
var prompt = new Prompter<T>(_promptDefinition, _form, null);
return prompt.Prompt(state, null);
}
public void SaveResources()
{
if (_message == null)
{
_form.Resources.Add(_name, _promptDefinition.Patterns);
}
}
public void Localize()
{
if (_message == null)
{
string[] patterns;
_form.Resources.LookupValues(_name, out patterns);
if (patterns != null) _promptDefinition.Patterns = patterns;
}
}
public StepType Type
{
get
{
return StepType.Message;
}
}
private readonly string _name;
private readonly IForm<T> _form;
private PromptAttribute _promptDefinition;
private readonly MessageDelegate<T> _message;
private readonly ActiveDelegate<T> _condition;
private readonly IEnumerable<string> _dependencies;
}
}

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

@ -1,79 +0,0 @@
//
// 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.Tasks;
using static Microsoft.Bot.Builder.Classic.Luis.BuiltIn.DateTime;
namespace Microsoft.Bot.Builder.Classic.Luis
{
/// <summary>
/// Policy for interpreting LUIS resolutions.
/// </summary>
public interface ICalendarPlus
{
Calendar Calendar { get; }
CalendarWeekRule WeekRule { get; }
DayOfWeek FirstDayOfWeek { get; }
int HourFor(DayPart dayPart);
}
/// <summary>
/// https://en.wikipedia.org/wiki/Gregorian_calendar
/// </summary>
public sealed class WesternCalendarPlus : ICalendarPlus
{
Calendar ICalendarPlus.Calendar => CultureInfo.InvariantCulture.Calendar;
DayOfWeek ICalendarPlus.FirstDayOfWeek => DayOfWeek.Sunday;
CalendarWeekRule ICalendarPlus.WeekRule => CalendarWeekRule.FirstDay;
int ICalendarPlus.HourFor(DayPart dayPart)
{
switch (dayPart)
{
case DayPart.MO: return 9;
case DayPart.MI: return 12;
case DayPart.AF: return 15;
case DayPart.EV: return 18;
case DayPart.NI: return 21;
default: throw new NotImplementedException();
}
}
}
}

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

@ -1,368 +0,0 @@
//
// 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 Microsoft.Bot.Builder.Classic.Internals.Fibers;
using Microsoft.Bot.Builder.Classic.Luis.Models;
using static Microsoft.Bot.Builder.Classic.Luis.BuiltIn.DateTime;
namespace Microsoft.Bot.Builder.Classic.Luis
{
/// <summary>
/// An abtraction to map from a LUIS <see cref="EntityModel"/> to specific CLR types.
/// </summary>
public interface IEntityToType
{
/// <summary>
/// Try to map LUIS <see cref="EntityModel"/> instances to a <see cref="TimeSpan"/>, relative to now.
/// </summary>
/// <param name="now">The now reference <see cref="DateTime"/>.</param>
/// <param name="entities">A list of possibly-relevant <see cref="EntityModel"/> instances.</param>
/// <param name="span">The output <see cref="TimeSpan"/>.</param>
/// <returns>True if the mapping may have been successful, false otherwise.</returns>
bool TryMapToTimeSpan(DateTime now, IEnumerable<EntityModel> entities, out TimeSpan span);
/// <summary>
/// Try to map LUIS <see cref="EntityModel"/> instances to a list of <see cref="DateTime"/> ranges, relative to now.
/// </summary>
/// <param name="now">The now reference <see cref="DateTime"/>.</param>
/// <param name="entities">A list of possibly-relevant <see cref="EntityModel"/> instances.</param>
/// <param name="ranges">The output <see cref="DateTime"/> ranges.</param>
/// <returns>True if the mapping may have been successful, false otherwise.</returns>
bool TryMapToDateRanges(DateTime now, IEnumerable<EntityModel> entities, out IEnumerable<Range<DateTime>> ranges);
}
public sealed class StrictEntityToType : IEntityToType
{
private readonly IResolutionParser parser;
private readonly ICalendarPlus calendar;
public StrictEntityToType(IResolutionParser parser, ICalendarPlus calendar)
{
SetField.NotNull(out this.parser, nameof(parser), parser);
SetField.NotNull(out this.calendar, nameof(calendar), calendar);
}
bool IEntityToType.TryMapToDateRanges(DateTime now, IEnumerable<EntityModel> entities, out IEnumerable<Range<DateTime>> ranges)
{
var resolutions = this.parser.ParseResolutions(entities);
var dateTimes = resolutions.OfType<DateTimeResolution>().ToArray();
if (dateTimes.Length > 0)
{
// possibly infinite
var merged = dateTimes
.Select(r => Interpret(r, now, this.calendar.Calendar, this.calendar.WeekRule, this.calendar.FirstDayOfWeek, this.calendar.HourFor))
.Aggregate((l, r) => l.SortedMerge(r));
ranges = merged;
return true;
}
else
{
ranges = null;
return false;
}
}
bool IEntityToType.TryMapToTimeSpan(DateTime now, IEnumerable<EntityModel> entities, out TimeSpan span)
{
span = default(TimeSpan);
return false;
}
/// <summary>
/// Interpret a parsed DateTimeResolution to provide a series of DateTime ranges
/// </summary>
/// <param name="resolution">The DateTimeResolution parsed from a LUIS response.</param>
/// <param name="now">The reference point of "now".</param>
/// <param name="calendar">The calendar to use for date math.</param>
/// <param name="rule">The calendar week rule to use for date math.</param>
/// <param name="firstDayOfWeek">The first day of the week to use for date math.</param>
/// <param name="HourFor">The hour that corresponds to the <see cref="DayPart"/> enumeration.</param>
/// <returns>A potentially infinite series of DateTime ranges.</returns>
public static IEnumerable<Range<DateTime>> Interpret(DateTimeResolution resolution, DateTime now, Calendar calendar, CalendarWeekRule rule, DayOfWeek firstDayOfWeek, Func<DayPart, int> HourFor)
{
// remove any millisecond components
now = new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, now.Kind);
switch (resolution.Reference)
{
case Reference.PAST_REF:
yield return Range.From(DateTime.MinValue, now);
yield break;
case Reference.PRESENT_REF:
yield return Range.From(now, now);
yield break;
case Reference.FUTURE_REF:
yield return Range.From(now, DateTime.MaxValue);
yield break;
case null:
break;
default:
throw new NotImplementedException();
}
var start = now;
// TODO: maybe clamp to prevent divergence
while (start < DateTime.MaxValue)
{
var after = start;
while (true)
{
// for each date component in decreasing order of significance:
// if it's not a variable (-1) or missing (null) component, then
// add a unit of that component to "start"
// round down to the component's granularity
// calculate the "after" based on the size of that component
if (resolution.Year >= 0)
{
bool need = start.Year != resolution.Year;
if (need)
{
start = start.AddYears(1);
start = new DateTime(start.Year, 1, 1, 0, 0, 0, 0, start.Kind);
}
if (start.Year > resolution.Year)
{
yield break;
}
after = start.AddYears(1);
if (need)
{
continue;
}
}
if (resolution.Month >= 0)
{
bool need = start.Month != resolution.Month;
if (need)
{
start = start.AddMonths(1);
start = new DateTime(start.Year, start.Month, 1, 0, 0, 0, 0, start.Kind);
}
after = start.AddMonths(1);
if (need)
{
continue;
}
}
var week = calendar.GetWeekOfYear(start, rule, firstDayOfWeek);
if (resolution.Week >= 0)
{
bool need = week != resolution.Week;
if (need)
{
start = start.AddDays(7);
start = new DateTime(start.Year, start.Month, start.Day, 0, 0, 0, 0, start.Kind);
while (start.DayOfWeek != firstDayOfWeek)
{
start = start.AddDays(-1);
}
}
after = start.AddDays(7);
if (need)
{
continue;
}
}
if (resolution.DayOfWeek != null)
{
bool need = start.DayOfWeek != resolution.DayOfWeek;
if (need)
{
start = start.AddDays(1);
start = new DateTime(start.Year, start.Month, start.Day, 0, 0, 0, 0, start.Kind);
}
after = start.AddDays(1);
if (need)
{
continue;
}
}
if (resolution.Day >= 0)
{
bool need = start.Day != resolution.Day;
if (need)
{
start = start.AddDays(1);
start = new DateTime(start.Year, start.Month, start.Day, 0, 0, 0, 0, start.Kind);
}
after = start.AddDays(1);
if (need)
{
continue;
}
}
if (resolution.DayPart != null)
{
var hourStart = HourFor(resolution.DayPart.Value);
var hourAfter = HourFor(resolution.DayPart.Value.Next());
var hourDelta = hourAfter - hourStart;
if (hourDelta < 0)
{
hourDelta += 24;
}
bool need = start.Hour != hourStart;
if (need)
{
start = start.AddHours(1);
start = new DateTime(start.Year, start.Month, start.Day, start.Hour, 0, 0, 0, start.Kind);
}
after = start.AddHours(hourDelta);
if (need)
{
continue;
}
}
if (resolution.Hour >= 0)
{
bool need = start.Hour != resolution.Hour;
if (need)
{
start = start.AddHours(1);
start = new DateTime(start.Year, start.Month, start.Day, start.Hour, 0, 0, 0, start.Kind);
}
after = start.AddHours(1);
if (need)
{
continue;
}
}
if (resolution.Minute >= 0)
{
bool need = start.Minute != resolution.Minute;
if (need)
{
start = start.AddMinutes(1);
start = new DateTime(start.Year, start.Month, start.Day, start.Hour, start.Minute, 0, 0, start.Kind);
}
after = start.AddMinutes(1);
if (need)
{
continue;
}
}
if (resolution.Second >= 0)
{
bool need = start.Second != resolution.Second;
if (need)
{
start = start.AddSeconds(1);
start = new DateTime(start.Year, start.Month, start.Day, start.Hour, start.Minute, start.Second, 0, start.Kind);
}
after = start.AddSeconds(1);
if (need)
{
continue;
}
}
// if all of the components were variable or missing,
// then in order of increasing component granularity,
// if the component is variable rather than missing, then increment by that granularity
if (start == after)
{
if (resolution.Second < 0)
{
after = start.AddSeconds(1);
}
else if (resolution.Minute < 0)
{
after = start.AddMinutes(1);
}
else if (resolution.Hour < 0)
{
after = start.AddHours(1);
}
else if (resolution.Day < 0)
{
after = start.AddDays(1);
}
else if (resolution.Week < 0)
{
after = start.AddDays(7);
}
else if (resolution.Month < 0)
{
after = start.AddMonths(1);
}
else if (resolution.Year < 0)
{
after = start.AddYears(1);
}
else
{
// a second is our minimum granularity
after = start.AddSeconds(1);
}
}
if (start >= now)
{
yield return new Range<DateTime>(start, after);
}
start = after;
}
}
}
}
}

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

@ -1,100 +0,0 @@
//
// 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.Classic.Luis.Models;
using static Microsoft.Bot.Builder.Classic.Luis.BuiltIn.DateTime;
namespace Microsoft.Bot.Builder.Classic.Luis
{
/// <summary>
/// LUIS extension methods.
/// </summary>
public static partial class Extensions
{
/// <summary>
/// Try to find an entity within the result.
/// </summary>
/// <param name="result">The LUIS result.</param>
/// <param name="type">The entity type.</param>
/// <param name="entity">The found entity.</param>
/// <returns>True if the entity was found, false otherwise.</returns>
public static bool TryFindEntity(this LuisResult result, string type, out EntityModel entity)
{
entity = result.Entities?.FirstOrDefault(e => e.Type == type);
return entity != null;
}
/// <summary>
/// Parse all resolutions from a LUIS result.
/// </summary>
/// <param name="parser">The resolution parser.</param>
/// <param name="entities">The LUIS entities.</param>
/// <returns>The parsed resolutions.</returns>
public static IEnumerable<Resolution> ParseResolutions(this IResolutionParser parser, IEnumerable<EntityModel> entities)
{
if (entities != null)
{
foreach (var entity in entities)
{
Resolution resolution;
if (parser.TryParse(entity.Resolution, out resolution))
{
yield return resolution;
}
}
}
}
/// <summary>
/// Return the next <see cref="DayPart"/>.
/// </summary>
/// <param name="part">The <see cref="DayPart"/> query.</param>
/// <returns>The next <see cref="DayPart"/> after the query.</returns>
public static DayPart Next(this DayPart part)
{
switch (part)
{
case DayPart.MO: return DayPart.MI;
case DayPart.MI: return DayPart.AF;
case DayPart.AF: return DayPart.EV;
case DayPart.EV: return DayPart.NI;
case DayPart.NI: return DayPart.MO;
default: throw new NotImplementedException();
}
}
}
}

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

@ -1,354 +0,0 @@
// The swagger file that is used to generate Luis models.
// Look at autorest, https://github.com/Azure/AutoRest, for more information.
{
"swagger": "2.0",
"info": {
"version": "v1",
"title": "Luis Query API"
},
"host": "westus.api.cognitive.microsoft.com",
"basePath": "/luis/",
"schemes": [
"https"
],
"paths": {
"/v1/application": {
"get": {
"operationId": "GetIntentsAndEntities",
"description": "Query Luis for intents and entities.",
"parameters": [
{
"name": "id",
"in": "query",
"type": "string",
"description": "Luis model Id.",
"format": "guid",
"required": true
},
{
"name": "subscription-key",
"in": "query",
"type": "string",
"description": "Luis subscription key.",
"format": "string",
"required": true
},
{
"name": "q",
"in": "query",
"type": "string",
"description": "Query submitted to LUIS service.",
"format": "string",
"required": true
}
],
"produces": [
"application/json"
],
"responses": {
"200": {
"description": "intents and entities extracted.",
"schema": {
"$ref": "#/definitions/LuisResult"
}
}
}
}
},
"/v2.0/apps/{id}": {
"get": {
"operationId": "GetIntentsAndEntitiesV2",
"description": "Query Luis for intents and entities.",
"parameters": [
{
"name": "id",
"in": "path",
"type": "string",
"description": "Luis model Id.",
"format": "guid",
"required": true
},
{
"name": "subscription-key",
"in": "query",
"type": "string",
"description": "Luis subscription key.",
"format": "string",
"required": true
},
{
"name": "q",
"in": "query",
"type": "string",
"description": "Query submitted to LUIS service.",
"format": "string",
"required": true
},
{
"name": "timezoneOffset",
"in": "query",
"type": "number",
"description": "The timezone offset for the location of the request.",
"required": false
},
{
"name": "contextId",
"in": "query",
"type": "string",
"description": "The conversation id for dialog.",
"required": false
},
{
"name": "verbose",
"in": "query",
"type": "boolean",
"description": "If true will return all intents instead of just the topscoring intent.",
"required": false
},
{
"name": "forceSet",
"in": "query",
"type": "string",
"description": "A parameter name to override in the action parameters with a new value.",
"required": false
},
{
"name": "allowSampling",
"in": "query",
"type": "string",
"description": "A parameter to allow logging the data so they can be accessed.",
"required": false
}
],
"produces": [
"application/json"
],
"responses": {
"200": {
"description": "intents and entities extracted.",
"schema": {
"$ref": "#/definitions/LuisResult"
}
}
}
}
}
},
"definitions": {
"LuisResult": {
"type": "object",
"required": [
"query",
"entities"
],
"properties": {
"query": {
"type": "string",
"description": "The query sent to LUIS."
},
"topScoringIntent": {
"$ref": "#/definitions/IntentRecommendation"
},
"intents": {
"type": "array",
"description": "The intents found in the query text.",
"items": {
"$ref": "#/definitions/IntentRecommendation"
}
},
"entities": {
"type": "array",
"description": "The entities found in the query text.",
"items": {
"$ref": "#/definitions/EntityModel"
}
},
"compositeEntities": {
"type": "array",
"description": "The composite entities found in the utterance.",
"items": {
"$ref": "#/definitions/CompositeEntityModel"
}
},
"dialog": {
"$ref": "#/definitions/DialogResponse"
},
"alteredQuery": {
"type": "string",
"description": "The altered query used by LUIS to extract intent and entities. For example, when Bing spell check is enabled for a model, this field will contain the spell checked utterance."
}
}
},
"IntentRecommendation": {
"description": "LUIS intent recommendation. Look at https://www.luis.ai/Help for more information.",
"properties": {
"intent": {
"type": "string",
"description": "The LUIS intent detected by LUIS service in response to a query."
},
"score": {
"type": "number",
"description": "The score for the detected intent."
},
"actions": {
"type": "array",
"description": "The action associated with this Luis intent.",
"items": {
"$ref": "#/definitions/Action"
}
}
}
},
"Action": {
"properties": {
"triggered": {
"type": "boolean",
"description": "True if the Luis action is triggered, false otherwise."
},
"name": {
"type": "string",
"description": "Name of the action."
},
"parameters": {
"type": "array",
"description": "The parameters for the action.",
"items": {
"$ref": "#/definitions/ActionParameter"
}
}
}
},
"ActionParameter": {
"properties": {
"name": {
"type": "string",
"description": "Name of the parameter."
},
"required": {
"type": "boolean",
"description": "True if the parameter is required, false otherwise."
},
"value": {
"type": "array",
"description": "Value of extracted entities for this parameter.",
"items": {
"$ref": "#/definitions/EntityModel"
}
}
}
},
"EntityModel": {
"description": "Luis entity recommendation. Look at https://www.luis.ai/Help for more information.",
"type": "object",
"properties": {
"role": {
"type": "string",
"description": "Role of the entity."
},
"entity": {
"type": "string",
"description": "Entity extracted by LUIS."
},
"type": {
"type": "string",
"description": "Type of the entity."
},
"startIndex": {
"type": "integer",
"description": "Start index of the entity in the LUIS query string."
},
"endIndex": {
"type": "integer",
"description": "End index of the entity in the LUIS query string."
},
"score": {
"type": "number",
"description": "Score assigned by LUIS to detected entity."
},
"resolution": {
"type": "object",
"description": "A machine interpretable resolution of the entity. For example the string \"one thousand\" would have the resolution \"1000\". The exact form of the resolution is defined by the entity type and is documented here: https://www.luis.ai/Help#PreBuiltEntities.",
"additionalProperties": {
"type": "object"
}
}
},
"required": [
"type"
]
},
"CompositeEntityModel": {
"description": "Luis composite entity. Look at https://www.luis.ai/Help for more information.",
"type": "object",
"properties": {
"parentType": {
"type": "string",
"description": "Type of parent entity."
},
"value": {
"type": "string",
"description": "Value for entity extracted by LUIS."
},
"children": {
"type": "array",
"items": {
"$ref": "#/definitions/CompositeChild"
}
}
},
"required": [
"parentType",
"value",
"children"
]
},
"CompositeChild": {
"description": "Child entity in Luis composite entity.",
"type": "object",
"properties": {
"type": {
"type": "string",
"description": "Type of child entity."
},
"value": {
"type": "string",
"description": "Value extracted by Luis."
}
},
"required": [
"type",
"value"
]
},
"DialogResponse": {
"type": "object",
"description": "The dialog response.",
"properties": {
"prompt": {
"type": "string",
"description": "Prompt that should be asked."
},
"parameterName": {
"type": "string",
"description": "Name of the parameter."
},
"parameterType": {
"type": "string",
"description": "Type of the parameter."
},
"contextId": {
"type": "string",
"description": "The context id for dialog."
},
"status": {
"type": "string",
"enum": [
"Question",
"Finished"
],
"description": "The dialog status."
}
}
}
}
}

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

@ -1,230 +0,0 @@
//
// 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.Classic.Internals.Fibers;
namespace Microsoft.Bot.Builder.Classic.Luis
{
/// <summary>
/// Luis api version.
/// </summary>
public enum LuisApiVersion
{
[Obsolete]
V1,
V2
}
/// <summary>
/// A mockable interface for the LUIS model.
/// </summary>
public interface ILuisModel
{
/// <summary>
/// The LUIS model ID.
/// </summary>
string ModelID { get; }
/// <summary>
/// The LUIS subscription key.
/// </summary>
string SubscriptionKey { get; }
/// <summary>
/// The base Uri for accessing LUIS.
/// </summary>
Uri UriBase { get; }
/// <summary>
/// Luis Api Version.
/// </summary>
LuisApiVersion ApiVersion { get; }
/// <summary>
/// Threshold for top scoring intent
/// </summary>
double Threshold { get; }
/// <summary>
/// Modify a Luis request to specify query parameters like spelling or logging.
/// </summary>
/// <param name="request">Request so far.</param>
/// <returns>Modified request.</returns>
LuisRequest ModifyRequest(LuisRequest request);
}
/// <summary>
/// The LUIS model information.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Method, AllowMultiple = true)]
[Serializable]
public class LuisModelAttribute : Attribute, ILuisModel, ILuisOptions, IEquatable<ILuisModel>
{
private readonly string modelID;
/// <summary>
/// The GUID for the LUIS model.
/// </summary>
public string ModelID => modelID;
private readonly string subscriptionKey;
/// <summary>
/// The subscription key for LUIS.
/// </summary>
public string SubscriptionKey => subscriptionKey;
private readonly string domain;
/// <summary>
/// Domain where LUIS application is located.
/// </summary>
/// <remarks>Null means default which is api.projectoxford.ai for V1 API and westus.api.cognitive.microsoft.com for V2 api.</remarks>
public string Domain => domain;
private readonly Uri uriBase;
/// <summary>
/// Base URI for LUIS calls.
/// </summary>
public Uri UriBase => uriBase;
private readonly LuisApiVersion apiVersion;
/// <summary>
/// Version of query API to call.
/// </summary>
public LuisApiVersion ApiVersion => apiVersion;
private readonly double threshold;
/// <summary>
/// Threshold for top scoring intent
/// </summary>
public double Threshold => threshold;
private ILuisOptions Options => (ILuisOptions)this;
/// <summary>
/// Indicates if logging of queries to LUIS is allowed.
/// </summary>
public bool Log { get { return Options.Log.Value; } set { Options.Log = value; } }
/// <summary>
/// Turn on spell checking.
/// </summary>
public bool SpellCheck { get { return Options.SpellCheck.Value; } set { Options.SpellCheck = value; } }
/// <summary>
/// Use the staging endpoint.
/// </summary>
public bool Staging { get { return Options.Staging.Value; } set { Options.Staging = value; } }
/// <summary>
/// The time zone offset.
/// </summary>
public double TimezoneOffset { get { return Options.TimezoneOffset.Value; } set { Options.TimezoneOffset = value; } }
/// <summary>
/// The verbose flag.
/// </summary>
public bool Verbose { get { return Options.Verbose.Value; } set { Options.Verbose = value; } }
/// <summary>
/// The Bing Spell Check subscription key.
/// </summary>
public string BingSpellCheckSubscriptionKey { get { return Options.BingSpellCheckSubscriptionKey; } set { Options.BingSpellCheckSubscriptionKey = value; } }
bool? ILuisOptions.Log { get; set; }
bool? ILuisOptions.SpellCheck { get; set; }
bool? ILuisOptions.Staging { get; set; }
double? ILuisOptions.TimezoneOffset { get; set; }
bool? ILuisOptions.Verbose { get; set; }
string ILuisOptions.BingSpellCheckSubscriptionKey { get; set; }
public static Uri UriFor(LuisApiVersion apiVersion, string domain = null)
{
if (domain == null)
{
domain = apiVersion == LuisApiVersion.V2 ? "westus.api.cognitive.microsoft.com" : "api.projectoxford.ai/luis/v1/application";
}
return new Uri(apiVersion == LuisApiVersion.V2 ? $"https://{domain}/luis/v2.0/apps/" : $"https://api.projectoxford.ai/luis/v1/application");
}
/// <summary>
/// Construct the LUIS model information.
/// </summary>
/// <param name="modelID">The LUIS model ID.</param>
/// <param name="subscriptionKey">The LUIS subscription key.</param>
/// <param name="apiVersion">The LUIS API version.</param>
/// <param name="domain">Domain where LUIS model is located.</param>
/// <param name="threshold">Threshold for the top scoring intent.</param>
public LuisModelAttribute(string modelID, string subscriptionKey,
LuisApiVersion apiVersion = LuisApiVersion.V2, string domain = null, double threshold = 0.0d)
{
SetField.NotNull(out this.modelID, nameof(modelID), modelID);
SetField.NotNull(out this.subscriptionKey, nameof(subscriptionKey), subscriptionKey);
this.apiVersion = apiVersion;
this.domain = domain;
this.uriBase = UriFor(apiVersion, domain);
this.threshold = threshold;
this.Log = true;
}
public bool Equals(ILuisModel other)
{
return other != null
&& object.Equals(this.ModelID, other.ModelID)
&& object.Equals(this.SubscriptionKey, other.SubscriptionKey)
&& object.Equals(this.ApiVersion, other.ApiVersion)
&& object.Equals(this.UriBase, other.UriBase)
;
}
public override bool Equals(object other)
{
return this.Equals(other as ILuisModel);
}
public override int GetHashCode()
{
return ModelID.GetHashCode()
^ SubscriptionKey.GetHashCode()
^ UriBase.GetHashCode()
^ ApiVersion.GetHashCode();
}
public LuisRequest ModifyRequest(LuisRequest request)
{
Options.Apply(request);
return request;
}
}
}

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

@ -1,86 +0,0 @@
//
// 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.
//
namespace Microsoft.Bot.Builder.Classic.Luis
{
/// <summary>
/// Interface containing optional parameters for a LUIS request.
/// </summary>
public interface ILuisOptions
{
/// <summary>
/// Indicates if logging of queries to LUIS is allowed.
/// </summary>
bool? Log { get; set; }
/// <summary>
/// Turn on spell checking.
/// </summary>
bool? SpellCheck { get; set; }
/// <summary>
/// Use the staging endpoint.
/// </summary>
bool? Staging { get; set; }
/// <summary>
/// The time zone offset.
/// </summary>
double? TimezoneOffset { get; set; }
/// <summary>
/// The verbose flag.
/// </summary>
bool? Verbose { get; set; }
/// <summary>
/// The Bing Spell Check subscription key.
/// </summary>
string BingSpellCheckSubscriptionKey { get; set; }
}
public static partial class Extensions
{
public static void Apply(this ILuisOptions source, ILuisOptions target)
{
if (source.Log.HasValue) target.Log = source.Log.Value;
if (source.SpellCheck.HasValue) target.SpellCheck = source.SpellCheck.Value;
if (source.Staging.HasValue) target.Staging = source.Staging.Value;
if (source.TimezoneOffset.HasValue) target.TimezoneOffset = source.TimezoneOffset.Value;
if (source.Verbose.HasValue) target.Verbose = source.Verbose.Value;
if (!string.IsNullOrWhiteSpace(source.BingSpellCheckSubscriptionKey)) target.BingSpellCheckSubscriptionKey = source.BingSpellCheckSubscriptionKey;
}
}
}

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

@ -1,345 +0,0 @@
//
// 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.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Classic.Internals.Fibers;
using Microsoft.Bot.Builder.Classic.Luis.Models;
using Microsoft.Rest;
using Newtonsoft.Json;
namespace Microsoft.Bot.Builder.Classic.Luis
{
/// <summary>
/// Object that contains all the possible parameters to build Luis request.
/// </summary>
public sealed class LuisRequest : ILuisOptions
{
/// <summary>
/// The text query.
/// </summary>
public string Query { get; set; }
/// <summary>
/// Indicates if logging of queries to LUIS is allowed.
/// </summary>
public bool? Log { get; set; }
/// <summary>
/// Turn on spell checking.
/// </summary>
public bool? SpellCheck { get; set; }
/// <summary>
/// Use the staging endpoint.
/// </summary>
public bool? Staging { get; set; }
/// <summary>
/// The time zone offset.
/// </summary>
public double? TimezoneOffset { get; set; }
/// <summary>
/// The verbose flag.
/// </summary>
public bool? Verbose { get; set; }
/// <summary>
/// The Bing Spell Check subscription key.
/// </summary>
public string BingSpellCheckSubscriptionKey { get; set; }
/// <summary>
/// Any extra query parameters for the URL.
/// </summary>
public string ExtraParameters { get; set; }
/// <summary>
/// The context id.
/// </summary>
[Obsolete("Action binding in LUIS should be replaced with code.")]
public string ContextId { get; set; }
/// <summary>
/// Force setting the parameter when using action binding.
/// </summary>
[Obsolete("Action binding in LUIS should be replaced with code.")]
public string ForceSet { get; set; }
/// <summary>
/// Constructs an instance of the LuisReqeuest.
/// </summary>
/// <param name="query"> The text query.</param>
public LuisRequest(string query)
{
this.Query = query;
this.Log = true;
}
/// <summary>
/// Build the Uri for issuing the request for the specified Luis model.
/// </summary>
/// <param name="model"> The Luis model.</param>
/// <returns> The request Uri.</returns>
public Uri BuildUri(ILuisModel model)
{
if (model.ModelID == null)
{
throw new ValidationException(ValidationRules.CannotBeNull, "id");
}
if (model.SubscriptionKey == null)
{
throw new ValidationException(ValidationRules.CannotBeNull, "subscriptionKey");
}
var queryParameters = new List<string>();
queryParameters.Add($"subscription-key={Uri.EscapeDataString(model.SubscriptionKey)}");
queryParameters.Add($"q={Uri.EscapeDataString(Query)}");
UriBuilder builder;
var id = Uri.EscapeDataString(model.ModelID);
switch (model.ApiVersion)
{
#pragma warning disable CS0612
case LuisApiVersion.V1:
builder = new UriBuilder(model.UriBase);
queryParameters.Add($"id={id}");
break;
#pragma warning restore CS0612
case LuisApiVersion.V2:
//v2.0 have the model as path parameter
builder = new UriBuilder(new Uri(model.UriBase, id));
break;
default:
throw new ArgumentException($"{model.ApiVersion} is not a valid Luis api version.");
}
if (Log != null)
{
queryParameters.Add($"log={Uri.EscapeDataString(Convert.ToString(Log))}");
}
if (SpellCheck != null)
{
queryParameters.Add($"spellCheck={Uri.EscapeDataString(Convert.ToString(SpellCheck))}");
}
if (Staging != null)
{
queryParameters.Add($"staging={Uri.EscapeDataString(Convert.ToString(Staging))}");
}
if (TimezoneOffset != null)
{
queryParameters.Add($"timezoneOffset={Uri.EscapeDataString(Convert.ToString(TimezoneOffset))}");
}
if (Verbose != null)
{
queryParameters.Add($"verbose={Uri.EscapeDataString(Convert.ToString(Verbose))}");
}
if (!string.IsNullOrWhiteSpace(BingSpellCheckSubscriptionKey))
{
queryParameters.Add($"bing-spell-check-subscription-key={Uri.EscapeDataString(BingSpellCheckSubscriptionKey)}");
}
#pragma warning disable CS0618
if (ContextId != null)
{
queryParameters.Add($"contextId={Uri.EscapeDataString(ContextId)}");
}
if (ForceSet != null)
{
queryParameters.Add($"forceSet={Uri.EscapeDataString(ForceSet)}");
}
#pragma warning restore CS0618
if (ExtraParameters != null)
{
queryParameters.Add(ExtraParameters);
}
builder.Query = string.Join("&", queryParameters);
return builder.Uri;
}
}
/// <summary>
/// A mockable interface for the LUIS service.
/// </summary>
public interface ILuisService
{
/// <summary>
/// Modify the incoming LUIS request.
/// </summary>
/// <param name="request">Request so far.</param>
/// <returns>Modified request.</returns>
LuisRequest ModifyRequest(LuisRequest request);
/// <summary>
/// Build the query uri for the <see cref="LuisRequest"/>.
/// </summary>
/// <param name="luisRequest">The luis request text.</param>
/// <returns>The query uri.</returns>
Uri BuildUri(LuisRequest luisRequest);
/// <summary>
/// Query the LUIS service using this uri.
/// </summary>
/// <param name="uri">The query uri.</param>
/// <param name="token">The cancellation token.</param>
/// <returns>The LUIS result.</returns>
Task<LuisResult> QueryAsync(Uri uri, CancellationToken token);
}
/// <summary>
/// Standard implementation of ILuisService against actual LUIS service.
/// </summary>
[Serializable]
public sealed class LuisService : ILuisService
{
private static readonly HttpClient DefaultHttpClient = new HttpClient() { Timeout = TimeSpan.FromSeconds(20) };
private HttpClient _httpClient;
private readonly ILuisModel model;
/// <summary>
/// Construct the LUIS service using the model information.
/// </summary>
/// <param name="model">The LUIS model information.</param>
/// <param name="customHttpClient">an optional custom http client</param>
public LuisService(ILuisModel model, HttpClient customHttpClient = null)
{
_httpClient = customHttpClient ?? DefaultHttpClient;
SetField.NotNull(out this.model, nameof(model), model);
}
public LuisRequest ModifyRequest(LuisRequest request)
{
return model.ModifyRequest(request);
}
Uri ILuisService.BuildUri(LuisRequest luisRequest)
{
return luisRequest.BuildUri(this.model);
}
public static void Fix(LuisResult result)
{
// fix up Luis result for backward compatibility
// v2 api is not returning list of intents if verbose query parameter
// is not set. This will move IntentRecommendation in TopScoringIntent
// to list of Intents.
if (result.Intents == null || result.Intents.Count == 0)
{
if (result.TopScoringIntent != null)
{
result.Intents = new List<IntentRecommendation> { result.TopScoringIntent };
}
}
}
public void ApplyThreshold(LuisResult result)
{
if (result.TopScoringIntent.Score > model.Threshold)
{
return;
}
result.TopScoringIntent.Intent = "None";
result.TopScoringIntent.Score = 1.0d;
}
async Task<LuisResult> ILuisService.QueryAsync(Uri uri, CancellationToken token)
{
string json;
using (var response = await _httpClient.GetAsync(uri, HttpCompletionOption.ResponseContentRead, token))
{
response.EnsureSuccessStatusCode();
json = await response.Content.ReadAsStringAsync();
}
try
{
var result = JsonConvert.DeserializeObject<LuisResult>(json);
Fix(result);
ApplyThreshold(result);
return result;
}
catch (JsonException ex)
{
throw new ArgumentException("Unable to deserialize the LUIS response.", ex);
}
}
}
/// <summary>
/// LUIS extension methods.
/// </summary>
public static partial class Extensions
{
/// <summary>
/// Query the LUIS service using this text.
/// </summary>
/// <param name="service">LUIS service.</param>
/// <param name="text">The query text.</param>
/// <param name="token">The cancellation token.</param>
/// <returns>The LUIS result.</returns>
public static async Task<LuisResult> QueryAsync(this ILuisService service, string text, CancellationToken token)
{
var luisRequest = service.ModifyRequest(new LuisRequest(query: text));
return await service.QueryAsync(luisRequest, token);
}
/// <summary>
/// Query the LUIS service using this request.
/// </summary>
/// <param name="service">LUIS service.</param>
/// <param name="request">Query request.</param>
/// <param name="token">Cancellation token.</param>
/// <returns>LUIS result.</returns>
public static async Task<LuisResult> QueryAsync(this ILuisService service, LuisRequest request, CancellationToken token)
{
service.ModifyRequest(request);
var uri = service.BuildUri(request);
return await service.QueryAsync(uri, token);
}
/// <summary>
/// Builds luis uri with text query.
/// </summary>
/// <param name="service">LUIS service.</param>
/// <param name="text">The query text.</param>
/// <returns>The LUIS request Uri.</returns>
public static Uri BuildUri(this ILuisService service, string text)
{
return service.BuildUri(service.ModifyRequest(new LuisRequest(query: text)));
}
}
}

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

@ -1,54 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for
// license information.
//
// Code generated by Microsoft (R) AutoRest Code Generator 0.16.0.0
// Changes may cause incorrect behavior and will be lost if the code is
// regenerated.
namespace Microsoft.Bot.Builder.Classic.Luis.Models
{
using System;
using System.Linq;
using System.Collections.Generic;
using Newtonsoft.Json;
using Microsoft.Rest;
using Microsoft.Rest.Serialization;
public partial class Action
{
/// <summary>
/// Initializes a new instance of the Action class.
/// </summary>
public Action() { }
/// <summary>
/// Initializes a new instance of the Action class.
/// </summary>
public Action(bool? triggered = default(bool?), string name = default(string), IList<ActionParameter> parameters = default(IList<ActionParameter>))
{
Triggered = triggered;
Name = name;
Parameters = parameters;
}
/// <summary>
/// True if the Luis action is triggered, false otherwise.
/// </summary>
[JsonProperty(PropertyName = "triggered")]
public bool? Triggered { get; set; }
/// <summary>
/// Name of the action.
/// </summary>
[JsonProperty(PropertyName = "name")]
public string Name { get; set; }
/// <summary>
/// The parameters for the action.
/// </summary>
[JsonProperty(PropertyName = "parameters")]
public IList<ActionParameter> Parameters { get; set; }
}
}

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

@ -1,54 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for
// license information.
//
// Code generated by Microsoft (R) AutoRest Code Generator 0.16.0.0
// Changes may cause incorrect behavior and will be lost if the code is
// regenerated.
namespace Microsoft.Bot.Builder.Classic.Luis.Models
{
using System;
using System.Linq;
using System.Collections.Generic;
using Newtonsoft.Json;
using Microsoft.Rest;
using Microsoft.Rest.Serialization;
public partial class ActionParameter
{
/// <summary>
/// Initializes a new instance of the ActionParameter class.
/// </summary>
public ActionParameter() { }
/// <summary>
/// Initializes a new instance of the ActionParameter class.
/// </summary>
public ActionParameter(string name = default(string), bool? required = default(bool?), IList<EntityModel> value = default(IList<EntityModel>))
{
Name = name;
Required = required;
Value = value;
}
/// <summary>
/// Name of the parameter.
/// </summary>
[JsonProperty(PropertyName = "name")]
public string Name { get; set; }
/// <summary>
/// True if the parameter is required, false otherwise.
/// </summary>
[JsonProperty(PropertyName = "required")]
public bool? Required { get; set; }
/// <summary>
/// Value of extracted entities for this parameter.
/// </summary>
[JsonProperty(PropertyName = "value")]
public IList<EntityModel> Value { get; set; }
}
}

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

@ -1,64 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for
// license information.
//
// Code generated by Microsoft (R) AutoRest Code Generator 0.16.0.0
// Changes may cause incorrect behavior and will be lost if the code is
// regenerated.
namespace Microsoft.Bot.Builder.Classic.Luis.Models
{
using System;
using System.Linq;
using System.Collections.Generic;
using Newtonsoft.Json;
using Microsoft.Rest;
using Microsoft.Rest.Serialization;
/// <summary>
/// Child entity in Luis composite entity.
/// </summary>
public partial class CompositeChild
{
/// <summary>
/// Initializes a new instance of the CompositeChild class.
/// </summary>
public CompositeChild() { }
/// <summary>
/// Initializes a new instance of the CompositeChild class.
/// </summary>
public CompositeChild(string type, string value)
{
Type = type;
Value = value;
}
/// <summary>
/// Type of child entity.
/// </summary>
[JsonProperty(PropertyName = "type")]
public string Type { get; set; }
/// <summary>
/// Value extracted by Luis.
/// </summary>
[JsonProperty(PropertyName = "value")]
public string Value { get; set; }
/// <summary>
/// Validate the object. Throws ValidationException if validation fails.
/// </summary>
public virtual void Validate()
{
if (Type == null)
{
throw new ValidationException(ValidationRules.CannotBeNull, "Type");
}
if (Value == null)
{
throw new ValidationException(ValidationRules.CannotBeNull, "Value");
}
}
}
}

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

@ -1,85 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for
// license information.
//
// Code generated by Microsoft (R) AutoRest Code Generator 0.16.0.0
// Changes may cause incorrect behavior and will be lost if the code is
// regenerated.
namespace Microsoft.Bot.Builder.Classic.Luis.Models
{
using System;
using System.Linq;
using System.Collections.Generic;
using Newtonsoft.Json;
using Microsoft.Rest;
using Microsoft.Rest.Serialization;
/// <summary>
/// Luis composite entity. Look at https://www.luis.ai/Help for more
/// information.
/// </summary>
public partial class CompositeEntityModel
{
/// <summary>
/// Initializes a new instance of the CompositeEntityModel class.
/// </summary>
public CompositeEntityModel() { }
/// <summary>
/// Initializes a new instance of the CompositeEntityModel class.
/// </summary>
public CompositeEntityModel(string parentType, string value, IList<CompositeChild> children)
{
ParentType = parentType;
Value = value;
Children = children;
}
/// <summary>
/// Type of parent entity.
/// </summary>
[JsonProperty(PropertyName = "parentType")]
public string ParentType { get; set; }
/// <summary>
/// Value for entity extracted by LUIS.
/// </summary>
[JsonProperty(PropertyName = "value")]
public string Value { get; set; }
/// <summary>
/// </summary>
[JsonProperty(PropertyName = "children")]
public IList<CompositeChild> Children { get; set; }
/// <summary>
/// Validate the object. Throws ValidationException if validation fails.
/// </summary>
public virtual void Validate()
{
if (ParentType == null)
{
throw new ValidationException(ValidationRules.CannotBeNull, "ParentType");
}
if (Value == null)
{
throw new ValidationException(ValidationRules.CannotBeNull, "Value");
}
if (Children == null)
{
throw new ValidationException(ValidationRules.CannotBeNull, "Children");
}
if (this.Children != null)
{
foreach (var element in this.Children)
{
if (element != null)
{
element.Validate();
}
}
}
}
}
}

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