Implement botbuilder middlware pipeline and default post to connector middleware
This commit is contained in:
Родитель
2ae53701ba
Коммит
07b36f00ee
|
@ -0,0 +1,22 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Microsoft.Bot.Connector;
|
||||
|
||||
namespace Microsoft.Bot.Builder
|
||||
{
|
||||
public class ActivityResolver
|
||||
{
|
||||
private IActivity activity;
|
||||
|
||||
public void Register(IActivity activity)
|
||||
{
|
||||
SetField.NotNull(out this.activity, nameof(activity), activity);
|
||||
}
|
||||
|
||||
public IActivity Resolve()
|
||||
{
|
||||
return this.activity;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Connector;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.Bot.Builder
|
||||
{
|
||||
public class Bot
|
||||
{
|
||||
private readonly MiddlewareSet middlewareSet;
|
||||
|
||||
public Bot(MiddlewareSet middlewareSet)
|
||||
{
|
||||
SetField.NotNull(out this.middlewareSet, nameof(middlewareSet), middlewareSet);
|
||||
}
|
||||
|
||||
public Func<BotContext, CancellationToken, Task<bool>> OnReceive = null;
|
||||
|
||||
public MiddlewareSet MiddlewareSet => middlewareSet;
|
||||
|
||||
public async Task<bool> Receive(BotContext context, CancellationToken token = default(CancellationToken))
|
||||
{
|
||||
var done = false;
|
||||
try
|
||||
{
|
||||
await this.middlewareSet.ContextCreated(context, token);
|
||||
done = await this.middlewareSet.ReceiveActivity(context, token);
|
||||
if(!done && OnReceive != null)
|
||||
{
|
||||
done = await this.OnReceive(context, token);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
await this.middlewareSet.ContextDone(context, token);
|
||||
}
|
||||
return done;
|
||||
}
|
||||
}
|
||||
|
||||
public static partial class BotExtensions
|
||||
{
|
||||
public static void Use(this Bot bot, params IMiddleware[] middlewares)
|
||||
{
|
||||
foreach (var middlerware in middlewares)
|
||||
{
|
||||
bot.MiddlewareSet.Middlewares.Add(middlerware);
|
||||
}
|
||||
}
|
||||
|
||||
public static IServiceCollection UseBotServices(this IServiceCollection services)
|
||||
{
|
||||
// BotLogger
|
||||
services.AddSingleton<IBotLogger, NullLogger>();
|
||||
|
||||
// Activity resolver and IActivity
|
||||
services.AddScoped<ActivityResolver>();
|
||||
services.AddScoped<IActivity>(provider => provider.GetRequiredService<ActivityResolver>().Resolve());
|
||||
|
||||
// Setup botContext
|
||||
services.AddScoped<IDataContext, NullDataContext>();
|
||||
services.AddScoped<BotContext>();
|
||||
services.AddScoped<IBotContext>(provider => provider.GetService<BotContext>());
|
||||
|
||||
// add post to connector as the default middleware
|
||||
services.AddScoped<PostToConnectorMiddleWare>();
|
||||
services.AddScoped<IMiddleware>(provider => provider.GetService<PostToConnectorMiddleWare>())
|
||||
.AddScoped<IList<IMiddleware>>(provider => provider.GetServices<IMiddleware>().ToList());
|
||||
|
||||
// register middleware set and set it up as IPostToUser
|
||||
services.AddScoped<MiddlewareSet>()
|
||||
.AddScoped<IPostToUser>(provider => provider.GetService<MiddlewareSet>());
|
||||
|
||||
services.AddScoped<Bot>();
|
||||
return services;
|
||||
}
|
||||
}
|
||||
|
||||
public class PostToConnectorMiddleWare : IMiddleware
|
||||
{
|
||||
private readonly IConnector connector;
|
||||
|
||||
public PostToConnectorMiddleWare(IConnector connector)
|
||||
{
|
||||
SetField.NotNull(out this.connector, nameof(connector), connector);
|
||||
}
|
||||
|
||||
public Task ContextCreated(BotContext context, CancellationToken token)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task<bool> ReceiveActivity(BotContext context, CancellationToken token)
|
||||
{
|
||||
return await Task.FromResult(false);
|
||||
}
|
||||
|
||||
public async Task ContextDone(BotContext context, CancellationToken token)
|
||||
{
|
||||
await this.FlushResponses(context, token);
|
||||
}
|
||||
|
||||
public async Task PostAsync(BotContext context, IList<IActivity> activities, CancellationToken token)
|
||||
{
|
||||
|
||||
foreach(var activity in activities)
|
||||
{
|
||||
context.Responses.Add(activity);
|
||||
}
|
||||
await this.FlushResponses(context, token);
|
||||
}
|
||||
|
||||
private async Task FlushResponses(BotContext context, CancellationToken token)
|
||||
{
|
||||
await this.connector.Post(context.Responses, token);
|
||||
context.Responses.Clear();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Connector;
|
||||
|
||||
namespace Microsoft.Bot.Builder
|
||||
{
|
||||
public interface IBotContext : IDataContext
|
||||
{
|
||||
IActivity Request { get; }
|
||||
IList<IActivity> Responses { get; set; }
|
||||
IBotLogger Logger { get; }
|
||||
}
|
||||
|
||||
public static partial class BotContextExtension
|
||||
{
|
||||
public static async Task PostAsync(this BotContext context, CancellationToken token)
|
||||
{
|
||||
await context.PostAsync(context, new List<IActivity>(), token);
|
||||
}
|
||||
|
||||
public static Task DoneAsync(this IBotContext context, CancellationToken token)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public class BotContext : DynamicContext, IBotContext, IPostToUser
|
||||
{
|
||||
private readonly IActivity request;
|
||||
private IList<IActivity> responses;
|
||||
private IBotLogger logger;
|
||||
private IDataContext dataContext;
|
||||
private IPostToUser postToUser;
|
||||
|
||||
public BotContext(IActivity request, IDataContext dataContext, IPostToUser postToUser, IBotLogger logger = null)
|
||||
{
|
||||
SetField.NotNull(out this.request, nameof(request), request);
|
||||
SetField.NotNull(out this.dataContext, nameof(dataContext), dataContext);
|
||||
SetField.NotNull(out this.postToUser, nameof(postToUser), postToUser);
|
||||
this.logger = logger;
|
||||
this.responses = new List<IActivity>();
|
||||
}
|
||||
|
||||
public async Task PostAsync(BotContext context, IList<IActivity> acitivties, CancellationToken token)
|
||||
{
|
||||
await this.postToUser.PostAsync(context, acitivties, token);
|
||||
}
|
||||
|
||||
public IActivity Request => request;
|
||||
|
||||
public IList<IActivity> Responses { get => responses; set => this.responses = value; }
|
||||
|
||||
public IUserContext User => dataContext.User;
|
||||
|
||||
public IConversationContext Conversation => dataContext.Conversation;
|
||||
|
||||
public IBotContextData Data => dataContext.Data;
|
||||
|
||||
public IBotLogger Logger => this.logger;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Bot.Builder
|
||||
{
|
||||
/// <summary>
|
||||
/// A property bag of bot data.
|
||||
/// </summary>
|
||||
public interface IBotDataBag
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the number of key/value pairs contained in the <see cref="IBotDataBag"/>.
|
||||
/// </summary>
|
||||
int Count { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Checks if data bag contains a value with specified key
|
||||
/// </summary>
|
||||
/// <param name="key">The key.</param>
|
||||
bool ContainsKey(string key);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value associated with the specified key.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the value to set.</typeparam>
|
||||
/// <param name="key">The key of the value to get.</param>
|
||||
/// <param name="value">
|
||||
/// When this method returns, contains the value associated with the specified key, if the key is found;
|
||||
/// otherwise, the default value for the type of the value parameter. This parameter is passed uninitialized.
|
||||
/// </param>
|
||||
/// <returns>true if the <see cref="IBotDataBag"/> contains an element with the specified key; otherwise, false.</returns>
|
||||
bool TryGetValue<T>(string key, out T value);
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified key and value to the bot data bag.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the value to get.</typeparam>
|
||||
/// <param name="key">The key of the element to add.</param>
|
||||
/// <param name="value">The value of the element to add. The value can be null for reference types.</param>
|
||||
void SetValue<T>(string key, T value);
|
||||
|
||||
/// <summary>
|
||||
/// Removes the specified key from the bot data bag.
|
||||
/// </summary>
|
||||
/// <param name="key">They key of the element to remove</param>
|
||||
/// <returns>True if removal of the key is successful; otherwise, false</returns>
|
||||
bool RemoveValue(string key);
|
||||
|
||||
/// <summary>
|
||||
/// Removes all of the values from data bag.
|
||||
/// </summary>
|
||||
void Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper methods.
|
||||
/// </summary>
|
||||
public static partial class Extensions
|
||||
{
|
||||
[System.Obsolete(@"Use GetValue<T> instead", false)]
|
||||
public static T Get<T>(this IBotDataBag bag, string key)
|
||||
{
|
||||
T value;
|
||||
if (!bag.TryGetValue(key, out value))
|
||||
{
|
||||
throw new KeyNotFoundException(key);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value associated with the specified key.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the value to get.</typeparam>
|
||||
/// <param name="bag">The bot data bag.</param>
|
||||
/// <param name="key">The key of the value to get or set.</param>
|
||||
/// <returns>The value associated with the specified key. If the specified key is not found, a get operation throws a KeyNotFoundException.</returns>
|
||||
/// <exception cref="KeyNotFoundException"><paramref name="key"/></exception>
|
||||
public static T GetValue<T>(this IBotDataBag bag, string key)
|
||||
{
|
||||
T value;
|
||||
if (!bag.TryGetValue(key, out value))
|
||||
{
|
||||
throw new KeyNotFoundException(key);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value associated with the specified key or a default value if not found.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the value to get.</typeparam>
|
||||
/// <param name="bag">The bot data bag.</param>
|
||||
/// <param name="key">The key of the value to get or set.</param>
|
||||
/// <param name="defaultValue">The value to return if the key is not present</param>
|
||||
/// <returns>The value associated with the specified key. If the specified key is not found, <paramref name="defaultValue"/>
|
||||
/// is returned </returns>
|
||||
public static T GetValueOrDefault<T>(this IBotDataBag bag, string key, T defaultValue = default(T))
|
||||
{
|
||||
T value;
|
||||
if (!bag.TryGetValue(key, out value))
|
||||
{
|
||||
value = defaultValue;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Bot.Builder
|
||||
{
|
||||
public interface IBotLogger
|
||||
{
|
||||
}
|
||||
|
||||
public class NullLogger : IBotLogger
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Connector;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Microsoft.Bot.Builder
|
||||
{
|
||||
public interface IConnector
|
||||
{
|
||||
Task Receive(IDictionary<string, StringValues> headers, IActivity activity, CancellationToken token);
|
||||
|
||||
Task Post(IList<IActivity> activities, CancellationToken token);
|
||||
}
|
||||
|
||||
public abstract class Connector : IConnector
|
||||
{
|
||||
protected readonly IServiceProvider serviceProvider;
|
||||
public Connector(IServiceProvider serviceProvider)
|
||||
{
|
||||
SetField.NotNull(out this.serviceProvider, nameof(serviceProvider), serviceProvider);
|
||||
}
|
||||
|
||||
public abstract Task Post(IList<IActivity> activities, CancellationToken token);
|
||||
|
||||
public virtual async Task Receive(IDictionary<string, StringValues> headers, IActivity activity, CancellationToken token)
|
||||
{
|
||||
var resolver = this.serviceProvider.GetRequiredService<ActivityResolver>();
|
||||
resolver.Register(activity);
|
||||
var bot = this.serviceProvider.GetRequiredService<Bot>();
|
||||
var context = this.serviceProvider.GetRequiredService<BotContext>();
|
||||
await bot.Receive(context, token);
|
||||
}
|
||||
}
|
||||
|
||||
public class BotFrameworkConnector : Connector
|
||||
{
|
||||
private readonly BotAuthenticator botAuthenticator;
|
||||
|
||||
public BotFrameworkConnector(IServiceProvider serviceProvider, BotAuthenticator botAuthenticator)
|
||||
: base(serviceProvider)
|
||||
{
|
||||
SetField.NotNull(out this.botAuthenticator, nameof(botAuthenticator), botAuthenticator);
|
||||
}
|
||||
|
||||
public async override Task Post(IList<IActivity> activities, CancellationToken token)
|
||||
{
|
||||
var connectorClient = this.serviceProvider.GetRequiredService<IConnectorClient>();
|
||||
foreach (Activity activity in activities)
|
||||
{
|
||||
await connectorClient.Conversations.SendToConversationAsync(activity, token);
|
||||
}
|
||||
}
|
||||
|
||||
public async override Task Receive(IDictionary<string, StringValues> headers, IActivity activity, CancellationToken token)
|
||||
{
|
||||
if (await botAuthenticator.TryAuthenticateAsync(headers, new[] { activity }, token))
|
||||
{
|
||||
await base.Receive(headers, activity, token);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new UnauthorizedAccessException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class TraceConnector : Connector
|
||||
{
|
||||
public TraceConnector(IServiceProvider serviceProvider)
|
||||
: base(serviceProvider)
|
||||
{
|
||||
}
|
||||
|
||||
public override Task Post(IList<IActivity> activities, CancellationToken token)
|
||||
{
|
||||
foreach (var activity in activities)
|
||||
{
|
||||
if (activity.GetActivityType() == ActivityTypes.Message)
|
||||
{
|
||||
Trace.WriteLine((activity as IMessageActivity).Text);
|
||||
}
|
||||
else
|
||||
{
|
||||
Trace.WriteLine((activity as Activity).Type);
|
||||
}
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
public static partial class ConnectorExtensions
|
||||
{
|
||||
public static IServiceCollection UseTraceConnector(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<IConnector, TraceConnector>();
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection UseBotFrameworkConnector(this IServiceCollection services)
|
||||
{
|
||||
services.UseBotConnector();
|
||||
services.AddScoped<IConnector, BotFrameworkConnector>();
|
||||
services.AddScoped<IConnectorClient>(provider =>
|
||||
{
|
||||
var activity = provider.GetRequiredService<IActivity>();
|
||||
return new ConnectorClient(new Uri(activity.ServiceUrl));
|
||||
});
|
||||
services.AddSingleton<BotAuthenticator>(provider =>
|
||||
{
|
||||
var config = provider.GetRequiredService<IConfigurationRoot>();
|
||||
var appId = config.GetSection(MicrosoftAppCredentials.MicrosoftAppIdKey)?.Value;
|
||||
var passwrod = config.GetSection(MicrosoftAppCredentials.MicrosoftAppPasswordKey)?.Value;
|
||||
return new BotAuthenticator(appId, passwrod);
|
||||
});
|
||||
return services;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Microsoft.Bot.Connector;
|
||||
|
||||
namespace Microsoft.Bot.Builder
|
||||
{
|
||||
public interface IUserContext
|
||||
{
|
||||
IBotDataBag Data { get; }
|
||||
}
|
||||
|
||||
public interface IConversationContext
|
||||
{
|
||||
ConversationReference reference { get; }
|
||||
IBotDataBag Data { get; }
|
||||
}
|
||||
|
||||
public interface IBotContextData
|
||||
{
|
||||
IBotDataBag Data { get; }
|
||||
}
|
||||
|
||||
public interface IDataContext
|
||||
{
|
||||
IUserContext User { get; }
|
||||
IConversationContext Conversation { get; }
|
||||
IBotContextData Data { get; }
|
||||
}
|
||||
|
||||
public sealed class DataContext : IDataContext
|
||||
{
|
||||
private readonly IUserContext user;
|
||||
private readonly IConversationContext conversation;
|
||||
private readonly IBotContextData data;
|
||||
|
||||
public DataContext(IUserContext user, IConversationContext conversation, IBotContextData data)
|
||||
{
|
||||
SetField.NotNull(out this.user, nameof(user), user);
|
||||
SetField.NotNull(out this.conversation, nameof(conversation), conversation);
|
||||
SetField.NotNull(out this.data, nameof(data), data);
|
||||
}
|
||||
|
||||
public IUserContext User => this.user;
|
||||
|
||||
public IConversationContext Conversation => this.conversation;
|
||||
|
||||
public IBotContextData Data => this.data;
|
||||
}
|
||||
|
||||
public sealed class NullDataContext : IDataContext
|
||||
{
|
||||
public NullDataContext()
|
||||
{
|
||||
}
|
||||
|
||||
public IUserContext User => null;
|
||||
|
||||
public IConversationContext Conversation => null;
|
||||
|
||||
public IBotContextData Data => null;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Connector;
|
||||
|
||||
namespace Microsoft.Bot.Builder
|
||||
{
|
||||
public interface IContextInitializer
|
||||
{
|
||||
Task ContextCreated(BotContext context, CancellationToken token);
|
||||
}
|
||||
|
||||
public interface IPostToBot
|
||||
{
|
||||
Task<bool> ReceiveActivity(BotContext context, CancellationToken token);
|
||||
}
|
||||
|
||||
public interface IPostToUser
|
||||
{
|
||||
Task PostAsync(BotContext context, IList<IActivity> acitivties, CancellationToken token);
|
||||
}
|
||||
|
||||
public interface IContextFinalizer
|
||||
{
|
||||
Task ContextDone(BotContext context, CancellationToken token);
|
||||
}
|
||||
|
||||
public interface IMiddleware : IContextInitializer, IPostToBot, IPostToUser, IContextFinalizer
|
||||
{
|
||||
}
|
||||
}
|
|
@ -8,4 +8,8 @@
|
|||
<PackageReference Include="System.Dynamic.Runtime" Version="4.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Microsoft.Bot.Connector\Microsoft.Bot.Connector.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,50 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Connector;
|
||||
|
||||
namespace Microsoft.Bot.Builder
|
||||
{
|
||||
public class MiddlewareSet : IMiddleware
|
||||
{
|
||||
private readonly IList<IMiddleware> middlewares;
|
||||
public MiddlewareSet(IList<IMiddleware> middlewares)
|
||||
{
|
||||
SetField.NotNull(out this.middlewares, nameof(middlewares), middlewares);
|
||||
}
|
||||
|
||||
public IList<IMiddleware> Middlewares => middlewares;
|
||||
|
||||
public virtual async Task ContextCreated(BotContext context, CancellationToken token)
|
||||
{
|
||||
foreach (var m in middlewares)
|
||||
await m.ContextCreated(context, token);
|
||||
}
|
||||
|
||||
public virtual async Task<bool> ReceiveActivity(BotContext context, CancellationToken token)
|
||||
{
|
||||
var handled = false;
|
||||
foreach (var middleware in middlewares)
|
||||
{
|
||||
handled = await middleware.ReceiveActivity(context, token);
|
||||
if (handled) break;
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
|
||||
public virtual async Task ContextDone(BotContext context, CancellationToken token)
|
||||
{
|
||||
foreach (var m in middlewares.Reverse())
|
||||
await m.ContextDone(context, token);
|
||||
}
|
||||
|
||||
public virtual async Task PostAsync(BotContext context, IList<IActivity> acitivties, CancellationToken token)
|
||||
{
|
||||
foreach (var m in middlewares.Reverse())
|
||||
await m.PostAsync(context, acitivties, token);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Bot.Builder
|
||||
{
|
||||
public static class SetField
|
||||
{
|
||||
public static void NotNull<T>(out T field, string name, T value)
|
||||
where T : class
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
field = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentNullException(name);
|
||||
}
|
||||
}
|
||||
|
||||
public static void CheckNull<T>(string name, T value)
|
||||
where T : class
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче