Merge pull request #260 from Microsoft/drmarsh/aspnet-integration-proactive-support

Add support for proactive endpoints to ASP.NET configuration
This commit is contained in:
Chris Mullins 2018-03-07 13:42:43 -08:00 коммит произвёл GitHub
Родитель 7f52a683a4 0bbb60cc89
Коммит 61fcb23c81
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
18 изменённых файлов: 271 добавлений и 98 удалений

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

@ -2,17 +2,31 @@
// Licensed under the MIT License.
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Bot.Builder.Adapters;
using Microsoft.Bot.Builder.Integration.AspNet.Core.Handlers;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System;
namespace Microsoft.Bot.Builder.Integration.AspNet.Core
{
public static class ApplicationBuilderExtensions
{
public static IApplicationBuilder UseBotFramework(this IApplicationBuilder applicationBuilder)
public static IApplicationBuilder UseBotFramework(this IApplicationBuilder applicationBuilder) =>
applicationBuilder.UseBotFramework(paths => {});
public static IApplicationBuilder UseBotFramework(this IApplicationBuilder applicationBuilder, Action<BotFrameworkPaths> configurePaths)
{
if (applicationBuilder == null)
{
throw new ArgumentNullException(nameof(applicationBuilder));
}
if (configurePaths == null)
{
throw new ArgumentNullException(nameof(configurePaths));
}
var options = applicationBuilder.ApplicationServices.GetRequiredService<IOptions<BotFrameworkOptions>>().Value;
var botFrameworkAdapter = new BotFrameworkAdapter(options.CredentialProvider);
@ -22,13 +36,20 @@ namespace Microsoft.Bot.Builder.Integration.AspNet.Core
botFrameworkAdapter.Use(middleware);
}
var botActivitiesPath = new PathString(options.RouteBaseUrl);
var paths = new BotFrameworkPaths();
botActivitiesPath.Add("/messages");
configurePaths(paths);
if (options.EnableProactiveMessages)
{
applicationBuilder.Map(
paths.BasePath + paths.ProactiveMessagesPath,
botProactiveAppBuilder => botProactiveAppBuilder.Run(new BotProactiveMessageHandler(botFrameworkAdapter).HandleAsync));
}
applicationBuilder.Map(
botActivitiesPath,
botActivitiesAppBuilder => botActivitiesAppBuilder.Run(new BotActivitiesHandler(botFrameworkAdapter).HandleAsync));
paths.BasePath + paths.MessagesPath,
botActivitiesAppBuilder => botActivitiesAppBuilder.Run(new BotMessageHandler(botFrameworkAdapter).HandleAsync));
return applicationBuilder;

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

@ -1,22 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using Microsoft.Bot.Builder.Middleware;
using Microsoft.Extensions.DependencyInjection;
using System.Collections.Generic;
namespace Microsoft.Bot.Builder.Integration.AspNet.Core
{
internal class BotConfigurationBuilder : IBotConfigurationBuilder
{
private readonly List<IMiddleware> _middleware = new List<IMiddleware>();
private readonly IServiceCollection _serviceCollection;
public BotConfigurationBuilder(IServiceCollection serviceCollection)
{
_serviceCollection = serviceCollection;
}
public List<IMiddleware> Middleware { get => _middleware; }
}
}

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

@ -1,9 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using Microsoft.AspNetCore.Http;
using BotMiddleware = Microsoft.Bot.Builder.Middleware;
using System.Collections.Generic;
using BotMiddleware = Microsoft.Bot.Builder.Middleware;
using Microsoft.Bot.Connector.Authentication;
namespace Microsoft.Bot.Builder.Integration.AspNet.Core
@ -15,12 +14,10 @@ namespace Microsoft.Bot.Builder.Integration.AspNet.Core
public BotFrameworkOptions()
{
_middleware = new List<BotMiddleware.IMiddleware>();
RouteBaseUrl = "/api";
}
public PathString RouteBaseUrl { get; set; }
public ICredentialProvider CredentialProvider { get; set; }
public IList<BotMiddleware.IMiddleware> Middleware { get => _middleware; }
public bool EnableProactiveMessages { get; set; }
}
}

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

@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using Microsoft.AspNetCore.Http;
namespace Microsoft.Bot.Builder.Integration.AspNet.Core
{
public class BotFrameworkPaths
{
public BotFrameworkPaths()
{
this.BasePath = "/api";
this.MessagesPath = "/messages";
this.ProactiveMessagesPath = "/messages/proactive";
}
public PathString BasePath { get; set; }
public PathString MessagesPath { get; set; }
public PathString ProactiveMessagesPath { get; set; }
}
}

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

@ -0,0 +1,37 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using Microsoft.AspNetCore.Http;
using Microsoft.Bot.Builder.Adapters;
using Microsoft.Bot.Schema;
using Newtonsoft.Json;
using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Bot.Builder.Integration.AspNet.Core.Handlers
{
public class BotMessageHandler : BotMessageHandlerBase
{
public BotMessageHandler(BotFrameworkAdapter botFrameworkAdapter) : base(botFrameworkAdapter)
{
}
protected override async Task ProcessMessageRequestAsync(HttpRequest request, BotFrameworkAdapter botFrameworkAdapter, Func<IBotContext, Task> botCallbackHandler)
{
var activity = default(Activity);
using (var bodyReader = new JsonTextReader(new StreamReader(request.Body, Encoding.UTF8)))
{
activity = BotMessageHandlerBase.BotMessageSerializer.Deserialize<Activity>(bodyReader);
}
await botFrameworkAdapter.ProcessActivity(
request.Headers["Authorization"],
activity,
botCallbackHandler);
}
}
}

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

@ -3,22 +3,19 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Bot.Builder.Adapters;
using Microsoft.Bot.Schema;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Net.Http.Headers;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.IO;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.Bot.Builder.Integration.AspNet.Core
namespace Microsoft.Bot.Builder.Integration.AspNet.Core.Handlers
{
internal class BotActivitiesHandler
public abstract class BotMessageHandlerBase
{
private static readonly JsonSerializer ActivitySerializer = JsonSerializer.Create(new JsonSerializerSettings
public static readonly JsonSerializer BotMessageSerializer = JsonSerializer.Create(new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
Formatting = Newtonsoft.Json.Formatting.Indented,
@ -27,7 +24,7 @@ namespace Microsoft.Bot.Builder.Integration.AspNet.Core
private BotFrameworkAdapter _botFrameworkAdapter;
public BotActivitiesHandler(BotFrameworkAdapter botFrameworkAdapter)
public BotMessageHandlerBase(BotFrameworkAdapter botFrameworkAdapter)
{
_botFrameworkAdapter = botFrameworkAdapter;
}
@ -60,18 +57,11 @@ namespace Microsoft.Bot.Builder.Integration.AspNet.Core
return;
}
var activity = default(Activity);
using (var bodyReader = new JsonTextReader(new StreamReader(request.Body, Encoding.UTF8)))
{
activity = ActivitySerializer.Deserialize<Activity>(bodyReader);
}
try
{
await _botFrameworkAdapter.ProcessActivity(
request.Headers["Authorization"],
activity,
await ProcessMessageRequestAsync(
request,
_botFrameworkAdapter,
botContext =>
{
var bot = httpContext.RequestServices.GetRequiredService<IBot>();
@ -86,5 +76,7 @@ namespace Microsoft.Bot.Builder.Integration.AspNet.Core
response.StatusCode = (int)HttpStatusCode.Forbidden;
}
}
protected abstract Task ProcessMessageRequestAsync(HttpRequest request, BotFrameworkAdapter botFrameworkAdapter, Func<IBotContext, Task> botCallbackHandler);
}
}

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

@ -0,0 +1,33 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using Microsoft.AspNetCore.Http;
using Microsoft.Bot.Builder.Adapters;
using Microsoft.Bot.Schema;
using Newtonsoft.Json;
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.Bot.Builder.Integration.AspNet.Core.Handlers
{
public class BotProactiveMessageHandler : BotMessageHandlerBase
{
public BotProactiveMessageHandler(BotFrameworkAdapter botFrameworkAdapter) : base(botFrameworkAdapter)
{
}
protected override async Task ProcessMessageRequestAsync(HttpRequest request, BotFrameworkAdapter botFrameworkAdapter, Func<IBotContext, Task> botCallbackHandler)
{
var conversationReference = default(ConversationReference);
using (var bodyReader = new JsonTextReader(new StreamReader(request.Body, Encoding.UTF8)))
{
conversationReference = BotMessageHandlerBase.BotMessageSerializer.Deserialize<ConversationReference>(bodyReader);
}
await botFrameworkAdapter.ContinueConversation(conversationReference, botCallbackHandler);
}
}
}

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

@ -2,7 +2,6 @@
// Licensed under the MIT License.
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using System;
@ -16,18 +15,6 @@ namespace Microsoft.Bot.Builder.Integration.AspNet.Core
{
services.AddTransient<IBot, TBot>();
var botBuilder = new BotConfigurationBuilder(services);
services.Configure<BotFrameworkOptions>(options =>
{
var optionsMiddleware = options.Middleware;
foreach (var mw in botBuilder.Middleware)
{
optionsMiddleware.Add(mw);
}
});
services.Configure(setupAction);
return services;

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

@ -2,6 +2,7 @@
// Licensed under the MIT License.
using Microsoft.Bot.Builder.Middleware;
using System;
using Microsoft.Bot.Connector.Authentication;
namespace Microsoft.Bot.Builder.Integration.AspNet.WebApi
@ -10,9 +11,9 @@ namespace Microsoft.Bot.Builder.Integration.AspNet.WebApi
{
private BotFrameworkOptions _options;
public BotFrameworkConfigurationBuilder()
public BotFrameworkConfigurationBuilder(BotFrameworkOptions botFrameworkOptions)
{
_options = new BotFrameworkOptions();
_options = botFrameworkOptions;
}
public BotFrameworkOptions BotFrameworkOptions { get => _options; }
@ -33,5 +34,25 @@ namespace Microsoft.Bot.Builder.Integration.AspNet.WebApi
return this;
}
public BotFrameworkConfigurationBuilder EnableProactiveMessages(string proactiveMessagesPath = default(string))
{
_options.EnableProactiveMessages = true;
if (proactiveMessagesPath != null)
{
_options.Paths.ProactiveMessagesPath = proactiveMessagesPath;
}
return this;
}
public BotFrameworkConfigurationBuilder UsePaths(Action<BotFrameworkPaths> configurePaths)
{
configurePaths(_options.Paths);
return this;
}
}
}

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

@ -10,15 +10,17 @@ namespace Microsoft.Bot.Builder.Integration.AspNet.WebApi
public class BotFrameworkOptions
{
private readonly List<IMiddleware> _middleware;
private readonly BotFrameworkPaths _paths;
public BotFrameworkOptions()
{
RouteBaseUrl = "api/";
_middleware = new List<IMiddleware>();
_paths = new BotFrameworkPaths();
}
public string RouteBaseUrl { get; set; }
public ICredentialProvider CredentialProvider { get; set; }
public List<IMiddleware> Middleware { get => _middleware; }
public bool EnableProactiveMessages { get; set; }
public BotFrameworkPaths Paths { get => _paths; }
}
}

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

@ -0,0 +1,19 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
namespace Microsoft.Bot.Builder.Integration.AspNet.WebApi
{
public class BotFrameworkPaths
{
public BotFrameworkPaths()
{
this.BasePath = "api/";
this.MessagesPath = "messages";
this.ProactiveMessagesPath = "messages/proactive";
}
public string BasePath { get; set; }
public string MessagesPath { get; set; }
public string ProactiveMessagesPath { get; set; }
}
}

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

@ -0,0 +1,29 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using Microsoft.Bot.Builder.Adapters;
using Microsoft.Bot.Schema;
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Bot.Builder.Integration.AspNet.WebApi.Handlers
{
internal sealed class BotMessageHandler : BotMessageHandlerBase
{
public BotMessageHandler(BotFrameworkAdapter botFrameworkAdapter) : base(botFrameworkAdapter)
{
}
protected override async Task ProcessMessageRequestAsync(HttpRequestMessage request, BotFrameworkAdapter botFrameworkAdapter, Func<IBotContext, Task> botCallbackHandler, CancellationToken cancellationToken)
{
var activity = await request.Content.ReadAsAsync<Activity>(BotMessageHandlerBase.BotMessageMediaTypeFormatters, cancellationToken);
await botFrameworkAdapter.ProcessActivity(
request.Headers.Authorization?.Parameter,
activity,
botCallbackHandler);
}
}
}

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

@ -2,7 +2,6 @@
// Licensed under the MIT License.
using Microsoft.Bot.Builder.Adapters;
using Microsoft.Bot.Schema;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
@ -12,11 +11,11 @@ using System.Net.Http.Formatting;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Bot.Builder.Integration.AspNet.WebApi
namespace Microsoft.Bot.Builder.Integration.AspNet.WebApi.Handlers
{
internal sealed class BotActivitiesHandler : HttpMessageHandler
public abstract class BotMessageHandlerBase : HttpMessageHandler
{
private static readonly MediaTypeFormatter[] BotActivityMediaTypeFormatters = new [] {
public static readonly MediaTypeFormatter[] BotMessageMediaTypeFormatters = new [] {
new JsonMediaTypeFormatter
{
SerializerSettings =
@ -30,7 +29,7 @@ namespace Microsoft.Bot.Builder.Integration.AspNet.WebApi
private readonly BotFrameworkAdapter _botFrameworkAdapter;
public BotActivitiesHandler(BotFrameworkAdapter botFrameworkAdapter)
public BotMessageHandlerBase(BotFrameworkAdapter botFrameworkAdapter)
{
_botFrameworkAdapter = botFrameworkAdapter;
}
@ -42,26 +41,23 @@ namespace Microsoft.Bot.Builder.Integration.AspNet.WebApi
return request.CreateResponse(HttpStatusCode.MethodNotAllowed);
}
var requestContent = request.Content;
var requestContentHeaders = requestContent.Headers;
var requestContentHeaders = request.Content.Headers;
if (requestContentHeaders.ContentLength == 0)
{
return request.CreateErrorResponse(HttpStatusCode.BadRequest, "Request body should not be empty.");
}
if (!BotActivityMediaTypeFormatters[0].SupportedMediaTypes.Contains(requestContentHeaders.ContentType))
if (!BotMessageMediaTypeFormatters[0].SupportedMediaTypes.Contains(requestContentHeaders.ContentType))
{
return request.CreateErrorResponse(HttpStatusCode.NotAcceptable, $"Expecting Content-Type of \"{BotActivityMediaTypeFormatters[0].SupportedMediaTypes[0].MediaType}\".");
return request.CreateErrorResponse(HttpStatusCode.NotAcceptable, $"Expecting Content-Type of \"{BotMessageMediaTypeFormatters[0].SupportedMediaTypes[0].MediaType}\".");
}
var activity = await requestContent.ReadAsAsync<Activity>(BotActivitiesHandler.BotActivityMediaTypeFormatters, cancellationToken);
try
{
await _botFrameworkAdapter.ProcessActivity(
request.Headers.Authorization?.Parameter,
activity,
await ProcessMessageRequestAsync(
request,
_botFrameworkAdapter,
botContext =>
{
cancellationToken.ThrowIfCancellationRequested();
@ -77,13 +73,14 @@ namespace Microsoft.Bot.Builder.Integration.AspNet.WebApi
bot = null;
}
if(bot == null)
if (bot == null)
{
throw new InvalidOperationException($"Did not find an {typeof(IBot).Name} service via the dependency resolver. Please make sure you have registered your bot with your dependency injection container.");
}
return bot.OnReceiveActivity(botContext);
});
},
cancellationToken);
return request.CreateResponse(HttpStatusCode.OK);
}
@ -96,5 +93,7 @@ namespace Microsoft.Bot.Builder.Integration.AspNet.WebApi
return request.CreateErrorResponse(HttpStatusCode.NotFound, e.Message);
}
}
protected abstract Task ProcessMessageRequestAsync(HttpRequestMessage request, BotFrameworkAdapter botFrameworkAdapter, Func<IBotContext, Task> botCallbackHandler, CancellationToken cancellationToken);
}
}

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

@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using Microsoft.Bot.Builder.Adapters;
using Microsoft.Bot.Schema;
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Bot.Builder.Integration.AspNet.WebApi.Handlers
{
public sealed class BotProactiveMessageHandler : BotMessageHandlerBase
{
public BotProactiveMessageHandler(BotFrameworkAdapter botFrameworkAdapter) : base(botFrameworkAdapter)
{
}
protected override async Task ProcessMessageRequestAsync(HttpRequestMessage request, BotFrameworkAdapter botFrameworkAdapter, Func<IBotContext, Task> botCallbackHandler, CancellationToken cancellationToken)
{
var conversationReference = await request.Content.ReadAsAsync<ConversationReference>(BotMessageHandlerBase.BotMessageMediaTypeFormatters, cancellationToken);
await botFrameworkAdapter.ContinueConversation(conversationReference, botCallbackHandler);
}
}
}

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

@ -2,6 +2,7 @@
// Licensed under the MIT License.
using Microsoft.Bot.Builder.Adapters;
using Microsoft.Bot.Builder.Integration.AspNet.WebApi.Handlers;
using System;
using System.Web.Http;
@ -11,13 +12,12 @@ namespace Microsoft.Bot.Builder.Integration.AspNet.WebApi
{
public static HttpConfiguration MapBotFramework(this HttpConfiguration httpConfiguration, Action<BotFrameworkConfigurationBuilder> configurer)
{
var optionsBuilder = new BotFrameworkConfigurationBuilder();
var options = new BotFrameworkOptions();
var optionsBuilder = new BotFrameworkConfigurationBuilder(options);
configurer(optionsBuilder);
var options = optionsBuilder.BotFrameworkOptions;
ConfigureBotRoute(BuildAdapter());
ConfigureBotRoutes(BuildAdapter());
return httpConfiguration;
@ -33,23 +33,32 @@ namespace Microsoft.Bot.Builder.Integration.AspNet.WebApi
return adapter;
}
void ConfigureBotRoute(BotFrameworkAdapter adapter)
void ConfigureBotRoutes(BotFrameworkAdapter adapter)
{
var botActivitiesRouteUrl = options.RouteBaseUrl;
var routes = httpConfiguration.Routes;
var baseUrl = options.Paths.BasePath;
if (!botActivitiesRouteUrl.EndsWith("/"))
if (!baseUrl.EndsWith("/"))
{
botActivitiesRouteUrl += "/";
baseUrl += "/";
}
botActivitiesRouteUrl += "messages";
httpConfiguration.Routes.MapHttpRoute(
"BotFrameworkV4 Activities Controller",
botActivitiesRouteUrl,
if (options.EnableProactiveMessages)
{
routes.MapHttpRoute(
"BotFramework - Proactive Message Handler",
baseUrl + options.Paths.ProactiveMessagesPath,
defaults: null,
constraints: null,
handler: new BotActivitiesHandler(adapter));
handler: new BotProactiveMessageHandler(adapter));
}
routes.MapHttpRoute(
"BotFramework - Message Handler",
baseUrl + options.Paths.MessagesPath,
defaults: null,
constraints: null,
handler: new BotMessageHandler(adapter));
}
}
}

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

@ -36,6 +36,7 @@ namespace Connector.Echo
{
options.CredentialProvider = new ConfigurationCredentialProvider(Configuration);
options.Middleware.Add(new ConversationState<EchoState>(new MemoryStorage()));
options.EnableProactiveMessages = true;
});
services.AddTransient<IMyService, MyService>();

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

@ -18,6 +18,7 @@ namespace Microsoft.Bot.Samples.EchoBot_AspNet461
{
botConfig
.UseMicrosoftApplicationIdentity(ConfigurationManager.AppSettings["BotFramework.MicrosoftApplicationId"], ConfigurationManager.AppSettings["BotFramework.MicrosoftApplicationPassword"])
.EnableProactiveMessages()
.UseMiddleware(new ConversationState<EchoState>(new MemoryStorage()));
});
}

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

@ -8,8 +8,8 @@ namespace Microsoft.Bot.Samples.EchoBot_AspNet461
{
GlobalConfiguration.Configure(config =>
{
WebApiConfig.Register(config);
BotConfig.Register(config);
WebApiConfig.Register(config);
});
}
}