I'm trying to use the ShowTypingMiddleware, a custom middleware already given by BotFramework (see here: https://github.com/microsoft/botbuilder-dotnet/blob/master/libraries/Microsoft.Bot.Builder/ShowTypingMiddleware.cs) to send an typing message to my user while the bot is processing his request. I'm using the BotFramework V4.
It all works locally, but not when I publish it on Azure's WebChat.
I've followed the example in Microsoft's samples, where they create an adapter that adds the desired middleware to the bot pipeline (the sample I've used is here: https://github.com/microsoft/BotBuilder-Samples/tree/master/samples/csharp_dotnetcore/17.multilingual-bot. The custom adapter I'm referring to is AdapterWithErrorHandler.cs, and it adds the TranslationMiddleware to the pipeline).
Running locally, everything works as planned. The problem is: when I'm publishing it to Azure, the webchat stop working. It throws the following exception:
7/21/2019 1:07:33 PM There was an error sending this message to your bot: HTTP status code Unauthorized
7/21/2019 1:07:33 PM There was an error sending this message to your bot:
HTTP status code Unauthorized
In my StartUp.cs's ConfigureServices, I've injected my custom adapter and my middleware:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
// Create the credential provider to be used with the Bot Framework Adapter.
services.AddSingleton<ICredentialProvider, ConfigurationCredentialProvider>();
// Create the Bot Framework Adapter.
services.AddSingleton<IBotFrameworkHttpAdapter, AdapterWithTypingAndErrorHandler>();
// Create the bot as a transient. In this case the ASP Controller is expecting an IBot.
services.AddTransient<IBot, MyBot>();
services.AddSingleton<ShowTypingMiddleware>();
}
My AdapterWithTypingAndErrorHandler is as follows:
public class AdapterWithTypingAndErrorHandler : BotFrameworkHttpAdapter
{
public AdapterWithTypingAndErrorHandler(
IConfiguration configuration,
ILogger<BotFrameworkHttpAdapter> logger,
ShowTypingMiddleware showTypingMiddleware) : base(logger: logger)
{
if (showTypingMiddleware == null)
throw new NullReferenceException($"Could not load '{nameof(showTypingMiddleware)}' in custom adapter.");
AddAdapterToPipeline(showTypingMiddleware);
OnTurnError = async (turnContext, exception) =>
{
logger.LogError($"Exception caught : {exception.Message}");
await turnContext.SendActivityAsync("Sorry, something went wrong :/");
};
}
private void AddAdapterToPipeline(ShowTypingMiddleware showTypingMiddleware)
=> Use(showTypingMiddleware);
}
And I'm using it in my controller:
public class BotController : ControllerBase
{
private readonly IBotFrameworkHttpAdapter Adapter;
private readonly IBot Bot;
public BotController(IBotFrameworkHttpAdapter adapter, IBot bot)
{
Adapter = adapter;
Bot = bot;
}
[HttpPost]
public async Task PostAsync()
{
await Adapter.ProcessAsync(Request, Response, Bot);
}
}
As I said, everything works fine, locally, but when I publish it, the WebChat throws the Unauthorized exception. If I use the default Adapter (the BotFrameworkHttpAdapter.cs one), instead of my customized, it all works fine too.
What should I do?
Related
I have an API which contains a HostedService that is built in VS2022 with .Net 6.
When I run locally the service is called as expected and everything works fine but when deplyed, the service doesn't seem to start.
I have tried many different configurations and even tried using a background service but it all has the same result. Here is my code:
I have an existing app build in VS2019 .Net Core 3.1 that has a HostedService and is working fine. I noticed that when I converted my .Net Core app to .Net 6, the service did not start when I deployed so I decided to just build a little app to try and find what's causing the issue.
Program.cs
using HostedServices.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseSerilog((context, loggerConfiguration) => loggerConfiguration
.ReadFrom.Configuration(context.Configuration)
.Enrich.FromLogContext()
.Enrich.WithMachineName());
// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddHostedService<MainService>();
var app = builder.Build();
// Configure the HTTP request pipeline.
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Here is the Hosted Service
namespace HostedServices.Services
{
public class MainService : IHostedService, IDisposable
{
private int executionCount = 0;
private readonly ILogger<MainService> _logger;
private Timer _timer;
private Task _executingTask;
private readonly CancellationTokenSource _stoppingCts = new CancellationTokenSource();
public MainService(ILogger<MainService> logger)
{
_logger = logger;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation($"Test Hosted Service Started {DateTime.Now}.");
_timer = new Timer(DoWork, null, TimeSpan.Zero,
TimeSpan.FromMinutes(1));
return Task.CompletedTask;
}
private void DoWork(object state)
{
_executingTask = DoWorkAsync(_stoppingCts.Token);
}
private async Task DoWorkAsync(CancellationToken token)
{
_logger.LogInformation(
"Doing work: {0}", DateTime.Now);
}
public Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Service is stopping.");
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public void Dispose()
{
_timer?.Dispose();
}
}
}
Everything runs fine locally but when I deploy it, the service doesn't seem to start, no log files are generated and I can't seem to find any errors reported.
Any ideas?
We had the same problem and resolved it by following IIS settings:
Under "Page" Advanced Settings: "Preload Enabled" must be set to "true" (this forces app to run after every app pool recycle and on startup)
Under "App Pool" Advanced settings: "Start mode" must be set to "AlwaysRunning"
"Page" in IIS must have http allowed (since preload enabled works only on http and not via https)
The problem is that while IHostedService start when the site starts when does IIS start the site? By default, it doesn't until a request is made. If your site goes idle for a while IIS will kill the thread as well.
IIS has a feature you can enable called "Application Initialization".
You then need to configure the Application Pool to always start (startMode="AlwaysRunning").
Lastly, you configure the site's application to preload which sends a fake HTTP request to the site on startup (preloadEnabled="true").
The docs for this are here: https://learn.microsoft.com/en-us/iis/get-started/whats-new-in-iis-8/iis-80-application-initialization
I am using Twitter Adapter Sample.
In class TwitterAdapterSampleBot:IBot
I want to get access to IBotFrameworkHttpAdapter adapter, IConfiguration configuration and ILogger logger, which are created in Startup->ConfigureServices method
I tried simple implement constructor :
public class TwitterAdapterSampleBot : IBot
{
public TwitterAdapterSampleBot(IBotFrameworkHttpAdapter adapter, IConfiguration configuration)
But got internal exception on startup:
System.InvalidOperationException: Unable to resolve service for type 'Microsoft.Bot.Builder.Integration.AspNet.Core.IBotFrameworkHttpAdapter' while attempting to activate 'TwitterAdapter_Sample.TwitterAdapterSampleBot'.
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(Type serviceType, Type implementationType, CallSiteChain callSiteChain, ParameterInfo[] parameters, Boolean throwIfCallSiteNotFound)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(Type serviceType, Type implementationType, CallSiteChain callSiteChai
This "IBotFrameworkHttpAdapter" interface using to express the relationship between an mvc api Controller and a Bot Builder Adapter. So you need to resolve the dependency issue with it's implementation.
IBotFrameworkHttpAdapter is implemented in "BotFrameworkHttpAdapter" ( Bot Builder Adapter implementation ) class.
ConfigureServices in Asp.Net Core Startup Class:
services.AddSingleton<IBotFrameworkHttpAdapter, BotFrameworkHttpAdapter>();
You can implement the above scenario in another way for example create a botframework custom adapter error handler class with the implementation of BotFrameworkHttpAdapter.
Microsoft docs example:
public class AdapterWithErrorHandler : BotFrameworkHttpAdapter
{
private static log4net.ILog logger
= log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
public AdapterWithErrorHandler(
ICredentialProvider credentialProvider,
ConversationState conversationState = null)
: base(credentialProvider)
{
OnTurnError = async (turnContext, exception) =>
{
// Log any leaked exception from the application.
logger.Error($"Exception caught : {exception.Message}");
// Send a catch-all apology to the user.
await turnContext.SendActivityAsync("Sorry, it looks like something went wrong.");
if (conversationState != null)
{
try
{
// Delete the conversationState for the current conversation to prevent the
// bot from getting stuck in a error-loop caused by being in a bad state.
// ConversationState should be thought of as similar to "cookie-state" in a Web pages.
await conversationState.DeleteAsync(turnContext);
}
catch (Exception e)
{
logger.Error($"Exception caught on attempting to Delete ConversationState : {e.Message}");
}
}
};
}
}
ConfigureServices in Asp.Net Core Startup Class:
// Create the Bot Framework Adapter with error handling enabled.
services.AddSingleton<IBotFrameworkHttpAdapter, AdapterWithErrorHandler>();
Reference:
Microsoft V4 docs
BotFrameworkHttpAdapter docs
I'm using botframework composer with multi language and want each user to be able to select preferred language/locale. After resolving the local code for his selection with a choice dialog, how can I set it in conversation so that his locale setting in his device will be overruled for rest of conversation?
Changing locale in emulator works fine, want same behaviour after user selection.
Setting turn.locale works for one turn, but is reset on next turn.
supposing you don't have control over the client, which would be the best.
You can resort to an old overload on the ever-growing hierarchy of bot adapters that hasn't been marked as deprecated.
You'd have to use the PostAsync method (api/post-messages endpoint) in the following controller (showing the one created by the current set of bot framework templates just for comparison):
[Route("api")]
[ApiController]
public class BotController : ControllerBase
{
private readonly IBotFrameworkHttpAdapter StreamingAdapter;
private readonly BotFrameworkAdapter PostAdapter;
private readonly ConversationLocales ConversationLocales;
private readonly IBot Bot;
public BotController(
IBotFrameworkHttpAdapter streamingAdapter,
BotFrameworkAdapter postAdapter,
ConversationLocales conversationLocales,
IBot bot)
{
StreamingAdapter = streamingAdapter;
PostAdapter = postAdapter;
Bot = bot;
}
[HttpPost("messages"), HttpGet("messages")]
public async Task PostOrStreamingAsync()
{
// Delegate the processing of the HTTP POST to the adapter.
// The adapter will invoke the bot.
await StreamingAdapter.ProcessAsync(Request, Response, Bot);
}
[HttpPost("post-messages")]
public async Task<InvokeResponse> PostAsync([FromBody] Activity activity)
{
var savedLocale = ConversationLocales.GetLocaleForConversation(activity.Conversation.Id);
activity.Locale = savedLocale ?? activity.Locale;
return await PostAdapter.ProcessActivityAsync(string.Empty, activity, Bot.OnTurnAsync, default);
}
}
That's supposing you implement a ConversationLocales service that allows you to keep the selected locale for each conversation id.
In the code above we're using the BotFrameworkAdapter adapter instead of IBotFrameworkHttpAdapter, however the AdapterWithErrorHandler used in the templates inherits indirectly from BotFrameworkAdapter, so you could do something like this in ConfigureServices to register "both" adapters:
services.AddSingleton<AdapterWithErrorHandler>();
services.AddSingleton<IBotFrameworkHttpAdapter>(sp => sp.GetRequiredService<AdapterWithErrorHandler>());
services.AddSingleton<BotFrameworkAdapter>(sp => sp.GetRequiredService<AdapterWithErrorHandler>());
To have a single adapter instance.
Using this method the adapter won't be able to use the bot channel streaming endpoints, but that shouldn't be much of a trouble, as long as you don't use the speech client.
You can also read some other details that might be relevan to you in my blog post How does a Bot Builder v4 bot work?, it's a bit dated but still valid.
UPDATE - Found a better solution 😊
This one works with the current wave of adapters and uses the messages pipeline, so it's "modern".
It also requires you to use a custom runtime, that you'll customize as follows.
1 - Create the following middleware
public class LocaleSelectionMiddleware : IMiddleware
{
private readonly IStatePropertyAccessor<string> _userLocale;
public LocaleSelectionMiddleware(UserState userState)
{
_userLocale = userState.CreateProperty<string>("locale");
}
public async Task OnTurnAsync(ITurnContext turnContext, NextDelegate next, CancellationToken cancellationToken = default)
{
if (turnContext is null)
{
throw new ArgumentNullException(nameof(turnContext));
}
var userLocale = await _userLocale.GetAsync(turnContext, () => turnContext.Activity.Locale);
turnContext.Activity.Locale = userLocale;
(turnContext as TurnContext).Locale = userLocale;
await next(cancellationToken).ConfigureAwait(false);
}
}
2 - Configure the middleware in the adapter in GetBotAdapter() in Startup.cs
public class Startup
{
public Startup(IWebHostEnvironment env, IConfiguration configuration)
{
this.HostingEnvironment = env;
this.Configuration = configuration;
}
//...
public BotFrameworkHttpAdapter GetBotAdapter(IStorage storage, BotSettings settings, UserState userState, ConversationState conversationState, IServiceProvider s)
{
var adapter = IsSkill(settings)
? new BotFrameworkHttpAdapter(new ConfigurationCredentialProvider(this.Configuration), s.GetService<AuthenticationConfiguration>())
: new BotFrameworkHttpAdapter(new ConfigurationCredentialProvider(this.Configuration));
adapter
.UseStorage(storage)
.UseBotState(userState, conversationState)
.Use(new RegisterClassMiddleware<IConfiguration>(Configuration))
.Use(new LocaleSelectionMiddleware(userState)) // <-- Add the middleware here
.Use(s.GetService<TelemetryInitializerMiddleware>());
//...
return adapter;
}
//...
}
3 - Set the user.locale property in any dialog
Set the user.locale property from any dialog, and the next turn will have the desired locale, and will be persisted in the user state, until they change it again.
Version
Microsoft.Bot.Streaming and Microsoft.Bot.Builder
"4.7.1"
Describe the bug
For direct line speech local test, set IsAuthenticationDisabledAsync to true in building a BotFrameworkHttpAdapter. SendActivityAsync() fails to send response back with below error
Failed to fetch token before processing outgoing activity. An IIdentity is required in TurnState for this operation.
For webchat test, it failed at Conversation:: ReplyToActivityWithHttpMessagesAsync, I get 401 unauthorized issue
To Reproduce
Update nuget package of BF to 4.7.1
Implement public class DisabledAuthCredentialProvider : ICredentialProvider which set IsAuthenticationDisabledAsync to true
Build BotFrameworkHttpAdapterv with DisabledAuthCredentialProvider
See exception in SendActivitiesAsync
(await this.GetAppCredentialsAsync(this.GetBotAppId(turnContext), (string) null, new CancellationToken()).ConfigureAwait(false)).GetTokenAsync(false);
Seems to work for me (in Emulator). Running 4.7.1. Here's what I have in DisabledAuthCredentialProvider
public class DisabledAuthCredentialProvider : ICredentialProvider
{
public Task<string> GetAppPasswordAsync(string appId)
{
throw new NotImplementedException();
}
public Task<bool> IsAuthenticationDisabledAsync()
{
return Task.FromResult(true);
}
public Task<bool> IsValidAppIdAsync(string appId)
{
throw new NotImplementedException();
}
}
I can share my bot if that helps.
I want to send an observablecollection object through a WCF service and receive it in windows phone 8 app.
the service Is as below.
public ObservableCollection<State> GetName()
{
StateEntities objEntities = new StateEntities();
ObservableCollection<State> stateCollection = new ObservableCollection<State>();
foreach (State s in objEntities.States)
{
stateCollection.Add(s);
}
return stateCollection;
}
State is a Class having data from the table. The contract is given below
public interface IHost
{
[OperationContract]
string DoWork();
[OperationContract]
ObservableCollection<State> GetName();
}
Now I want to consume the service in a windows phone app.
A button click should trigger the service consumption , get data from the service as observable collection and feed it into a gridview.
public sealed partial class MainPage : Page
{
ServiceReference1.HostClient proxy;
public MainPage()
{
proxy = new HostClient();
this.InitializeComponent();
}
private async void Button_Click(object sender, RoutedEventArgs e)
{
ObservableCollection<State> stateCollection = new ObservableCollection<State>();
stateCollection = await proxy.GetNameAsync();
dataGrid.ItemsSource = stateCollection;
}
}
but it is throwing and exception in the await line.
This is the exception.
An exception of type 'System.ServiceModel.CommunicationException'
occurred in mscorlib.dll but was not handled in user code
Additional information: An error occurred while receiving the HTTP
response to "ttp://localhost:65338/Host.svc." This could be due to the
service endpoint binding not using the HTTP protocol. This could also
be due to an HTTP request context being aborted by the server
Can anybody suggest an alternative or solution ?