不幸的是,这似乎是一个issue in the Bot Framework itself,这个问题的当前解决方法是注册一个DocumentDbBotDataStore 而不是TableBotDataStore,在你Global.asax.cs_ApplicationStart 中输入这些:
var uri = new Uri(ConfigurationManager.AppSettings["DocumentDBUri"]);
var key = ConfigurationManager.AppSettings["DocumentDBKey"];
var store = new DocumentDbBotDataStore(uri, key);
Conversation.UpdateContainer(
builder =>
{
builder.Register(c => store)
.Keyed<IBotDataStore<BotData>>(AzureModule.Key_DataStore)
.AsSelf()
.SingleInstance();
});
编辑:
有一个我不想发布的巨大管道胶带修复,我在不久前实施并设法让它发挥作用。这个想法基本上是定位问题制造服务并通过覆盖它并强制它重试来破解机器人框架。 bot 框架的令牌缓存管理器在某些时候出现故障,并且访问令牌在发出请求之前没有及时刷新,我们会导致 Unauthorized Error,因此我们还对其进行了破解并强制它刷新令牌。
方法如下:
我们覆盖了机器人框架的 IBotToUser 装饰器,该装饰器生成了导致错误的 HttpRequest:
public class RetryHandlerDecorator : IBotToUser
{
private readonly IMessageActivity _toBot;
private readonly IConnectorClient _client;
public RetryHandlerDecorator(IMessageActivity toBot, IConnectorClient client)
{
SetField.NotNull(out _toBot, nameof(toBot), toBot);
SetField.NotNull(out _client, nameof(client), client);
}
IMessageActivity IBotToUser.MakeMessage()
{
var toBotActivity = (Activity)_toBot;
return toBotActivity.CreateReply();
}
async Task IBotToUser.PostAsync(IMessageActivity message, CancellationToken cancellationToken)
{
try
{
await _client.Conversations.ReplyToActivityAsync((Activity)message, cancellationToken);
}
catch (Exception e)
{
if (IsTransientError(e))
{
await HandleRetry(message, cancellationToken);
}
else
{
throw;
}
}
}
private async Task HandleRetry(IMessageActivity activity, CancellationToken token)
{
await ForceRefreshTokenAsync();
await _client.Conversations.ReplyToActivityAsync((Activity)activity, token);
}
private async Task ForceRefreshTokenAsync()
{
var credentialsManager = new MicrosoftAppCredentials(
ConfigurationManager.AppSettings[MicrosoftAppCredentials.MicrosoftAppIdKey],
ConfigurationManager.AppSettings[MicrosoftAppCredentials.MicrosoftAppPasswordKey]);
// force the generation of a new token
// this will store the token in a static cache list, so no harm in creating a new instance of MicrosoftAppCredentials
await credentialsManager.GetTokenAsync(true);
}
private static bool IsTransientError(Exception e)
{
switch (e)
{
case ErrorResponseException ex:
return ex.Response.StatusCode == HttpStatusCode.Unauthorized;
default:
return false;
}
}
}
我们重写了注册IBotToUser服务的机器人框架模块:
public class BotToUserModuleOverride : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<RetryHandlerDecorator>().Keyed<IBotToUser>(typeof(RetryHandlerDecorator))
.InstancePerLifetimeScope();
RegisterAdapterChain<IBotToUser>(builder,
typeof(RetryHandlerDecorator), // this was previously AlwaysSendDirect_BotToUser
typeof(AutoInputHint_BotToUser),
typeof(MapToChannelData_BotToUser),
typeof(LogBotToUser)
)
.InstancePerLifetimeScope();
}
public static IRegistrationBuilder<TLimit, SimpleActivatorData, SingleRegistrationStyle> RegisterAdapterChain<TLimit>(ContainerBuilder builder, params Type[] types)
{
return
builder
.Register(c =>
{
var service = default(TLimit);
return types.Aggregate(service,
(current, t) => c.ResolveKeyed<TLimit>(t, TypedParameter.From(current)));
})
.As<TLimit>();
}
}
最后我们破解 bot 框架的 IoC 容器,方法是注册我们覆盖服务的模块,这需要在 Global.asax.cs ApplicationStart 中完成:
Conversation.UpdateContainer(
builder =>
{
builder.RegisterModule(new BotToUserModuleOverride());
});
所以会发生什么,每当UnauthorizedError 被ReplyToActivityAsync 方法引发时,都会有一个进程强制刷新访问令牌并重试。