Search code examples
angularazureasp.net-core-webapi.net-8.0azure-bot-service

Azure EchoBot not sending any messages. No greeting and no answers


I created a minimal setup of Azure EchoBot. My solution includes an ASP.NET Core 8 Web API and an Angular app. I followed the template for the EchoBot and deployed it to an Azure app service /linux/.

The Angular app has the WebChat control installed and I am able to send messages, which show up in the chat. However the bot is not returning any answers or sending a greeting when first opened.

Here is a screenshot of the developer console in Chrome:

enter image description here

Here is my EchoBot class:

using Microsoft.Bot.Builder;
using Microsoft.Bot.Schema;

namespace limbo.dating.Server.Bots
{
    public class EchoBot : ActivityHandler
    {
        public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken)
        {
            // Log the activity type
            Console.WriteLine($"Activity Type: {turnContext.Activity.Type}");

            await base.OnTurnAsync(turnContext, cancellationToken);
        }

        protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
        {
            var replyText = $"Echo: {turnContext.Activity.Text}";
            await turnContext.SendActivityAsync(MessageFactory.Text(replyText, replyText), cancellationToken);
        }

        protected override async Task OnMembersAddedAsync(IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
        {
            var welcomeText = "Hello and welcome!";

            // Log the number of members added
            Console.WriteLine($"Members added count: {membersAdded.Count}");

            foreach (var member in membersAdded)
            {
                // Log each member added
                Console.WriteLine($"Member added: {member.Id}");

                if (member.Id != turnContext.Activity.Recipient.Id)
                {
                    await turnContext.SendActivityAsync(MessageFactory.Text(welcomeText, welcomeText), cancellationToken);
                }
            }
        }
    }
}

Here is the configuration in Program.cs:

using limbo.dating.Server.Bots;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Integration.AspNet.Core;
using Microsoft.Bot.Connector.Authentication;
using Microsoft.Identity.Web;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: "AllowedCorsOrigins",
        builder =>
        {
            builder.AllowAnyHeader()
                .AllowAnyOrigin()
                .AllowAnyMethod();
            //.AllowCredentials();
        });
});

// Add services to the container.
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"));

builder.Services.AddAuthorization(options =>
{
    options.FallbackPolicy = options.DefaultPolicy;
});

builder.Services.AddControllers();

// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddHttpClient();

// Create the Bot Framework Authentication to be used with the Bot Adapter.
builder.Services.AddSingleton<BotFrameworkAuthentication, ConfigurationBotFrameworkAuthentication>();

// Create the Bot Adapter with error handling enabled.
builder.Services.AddSingleton<IBotFrameworkHttpAdapter, AdapterWithErrorHandler>();

// Create the bot as a transient. In this case the ASP Controller is expecting an IBot.
builder.Services.AddTransient<IBot, limbo.dating.Server.Bots.EchoBot>();

var app = builder.Build();

app.UseDefaultFiles();
app.UseStaticFiles();
app.UseWebSockets();
app.UseRouting();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseCors();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.MapFallbackToFile("/index.html");

app.Run();

Appsettings.json:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "<mydomain>.onmicrosoft.com",
    "TenantId": "<mytenantid>",
    "ClientId": "<myclientid>",
    "CallbackPath": "/signin-oidc"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "MicrosoftAppType": "MultiTenant",
    "MicrosoftAppId": "<mymicrosoftappid>",
    "MicrosoftAppPassword": "mymicrosoftapppassword",
    "MicrosoftAppTenantId": ""
}

In the Angular app, I have the following for app.component.ts:

import { HttpClient } from '@angular/common/http';
import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import * as BotChat from "botframework-webchat";
import { createDirectLine } from 'botframework-webchat';
import { TokenService } from './services/token.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'] // Corrected property name
})
export class AppComponent implements AfterViewInit, OnInit {

  @ViewChild('botWindow')
  botWindowElement!: ElementRef;

  token!: string;

  constructor(private tokenService: TokenService) { }

  ngOnInit(): void {
    // Fetch the token when the component initializes
    this.tokenService.getDirectLineToken().subscribe(
      (data) => {
        this.token = data.token; // Assign the token
        console.log('Token fetched:', this.token);
        this.initializeWebChat(); // Initialize WebChat after token is fetched
      },
      (error) => {
        console.error('Error fetching token:', error);
      }
    );
  }

  ngAfterViewInit(): void {
    // Initialization moved to initializeWebChat method
  }

  initializeWebChat(): void {
    const directLine = createDirectLine({
      token: this.token
    });

    interface Action {
      type: string;
      payload?: any;
    }

    interface StoreAPI {
      dispatch: (action: Action) => void;
    }

    const store = BotChat.createStore({},
      (storeAPI: StoreAPI) => (next: (action: Action) => void) => (action: Action) => {
      if (action.type === 'DIRECT_LINE/CONNECT_FULFILLED') {
        storeAPI.dispatch({
          type: 'WEB_CHAT/SEND_EVENT',
          payload: {
            name: 'webchat/join',
            value: { language: window.navigator.language }
          }
        });
      }
      return next(action);
    });

    BotChat.renderWebChat({
      directLine: directLine,
      store: store,
      userID: '[email protected]',
      botID: 'limbo-dating-bot',
      withCredential: true,
    }, this.botWindowElement.nativeElement);

    directLine.connectionStatus$.subscribe(status => {
      if (status === 2) {
        console.log('DirectLine connected');
      } else if (status === 4) {
        console.error('DirectLine connection failed');
      }
    });

    directLine.connectionStatus$.subscribe(status => {
      if (status === 2) {
        console.log('DirectLine connected');

        // Send a test message to the bot
        directLine.postActivity({
          from: { id: '[email protected]', name: 'User' },
          type: 'message',
          text: 'Hello, bot!'
        }).subscribe(
          id => console.log('Message sent, activity id:', id),
          error => console.error('Error sending message:', error)
        );
      } else if (status === 4) {
        console.error('DirectLine connection failed');
      }
    });

    directLine.activity$.subscribe(activity => {
      if (activity.from.id !== '[email protected]') {
        console.log('Incoming message:', activity);
      } else {
        console.log('Outgoing message:', activity);
      }
    });
  }
}

I expect the EchoBot to reply with "Echo: " + whatever I typed in the chat window.

Any help will be greatly appreciated!


Solution

  • browser console: (Uncaught (in promise) Error: A listener indicated an asynchronous response...)

    The bot's endpoint /api/messages might not be responding correctly or the connection is being closed prematurely this is the only cause for the above error.

    Test the endpoint URL by making a POST request to

    https://limbo-dating-server-app-service.azurewebsites.net/api/messages
    

    JSON body:

    {
      "type": "message",
      "from": {
        "id": "test-user"
      },
      "text": "Hello, bot!"
    }
    

    Set the headers:

    • Authorization: Bearer <DirectLineToken>
    • Content-Type: application/json.

    In the Azure Bot Service, confirm the Messaging endpoint is like

    enter image description here

    Update the custom adapter (AdapterWithErrorHandler) to log more details about errors.

    public class AdapterWithErrorHandler : BotFrameworkHttpAdapter
    {
        public AdapterWithErrorHandler(IConfiguration configuration, ILogger<BotFrameworkHttpAdapter> logger)
            : base(configuration, logger)
        {
            OnTurnError = async (turnContext, exception) =>
            {
                logger.LogError(exception, "Unhandled error: {Message}", exception.Message);
                
                // Send error message to user
                await turnContext.SendActivityAsync("Sorry, something went wrong.");
            };
        }
    }
    
    • Monitor the activity$ observable to confirm if activities are received from the bot:
    directLine.activity$.subscribe(activity => {
      console.log('Activity received from bot:', activity);
    });
    

    Deployed the application to app service.

    enter image description here

    Tested:

    enter image description here

    Before running the application test the endpoint url - /api/messages using postman or curl.