Weather Bot using Bot Framework SDK

Rahman Bakhishli
KPMG UK Engineering
15 min readJul 6, 2023

Introduction

In this article, we’ll explore the process of building a weather bot using the Bot Framework SDK for C#, paired with the OpenWeatherMap API. The weather bot can be operated through the Bot Framework Emulator or via Microsoft Teams Chat. This project was inspired by Matthew Kruczek’s tutorial on Pluralsight, titled ‘Creating a Teams App Using the Microsoft Bot Framework 4’. To make my learning experience more engaging, I chose to experiment with a different API.

Prerequisites

Creating an Echo Bot

We will be using .NET Core Template to build new conversational AI bot using Bot Framework v4.

Using VS Code/CLI to install Bot Framework C# Echo bot template

To install the Bot Framework templates:

  1. Open a console window.
  2. Download and install .NET Core SDK download version 3.1 or later.
  3. You can use this command to determine which versions of the .NET Core command-line interface are installed.
dotnet - version

Install Bot Framework C# echo bot template:

dotnet new -i Microsoft.Bot.Framework.CSharp.EchoBot

Create a bot project

  1. In Visual Studio Code, open a new terminal window.
  2. Go to the directory in which you want to create your bot project.
  3. Create a new echo bot project using the following command. Replace <your-bot-name> with the name to use for your bot project.
dotnet new echobot -n <your-bot-name>

Start your bot

In Visual Studio Code:

  1. Open your bot project folder.
  2. In Visual Studio Code, open a new terminal window, make sure you are the same directory as your project.
  3. Run the following command:
dotnet run

The run command builds the application, deploys it to localhost, and launches the web browser to display the application’s default.html page.

At this point, your bot is running locally on port 3978.

Start the Emulator and connect your bot

  1. Start the Bot Framework Emulator.
  2. Select Open Bot on the Emulator’s Welcome tab.
  3. Enter your bot’s URL, which is your local host and port, with /api/messages added to the path. The address is usually: http://localhost:3978/api/messages
Bot Framework Emulator

4. Then select Connect.

Send a message to your bot, and the bot will respond back.

Echo Message from the bot

The Echo Bot is quite simple, as it will just echo back the message you entered. In the next section, we will be exploring how to connect to OpenWeatherMap API.

Connecting to OpenWeatherMap API

What is OpenWeatherMap and why to use it is API?

  • OpenWeatherMap is an online service that provides global weather data via an API.
  • This data includes current weather information, forecasts, nowcasts, and historical weather data for any geographical location.
  • The APIs provided by OpenWeatherMap support multiple languages and industry-standard data formats like JSON and XML, making it very developer-friendly.
  1. Make sure to create an account with Open Weather.

2. Once logged in Navigate to API keys and generate a new key for the bot, save you API key somewhere safe and don’t share it with others.

Please note that your API key will be activated automatically, up to 2 hours after your successful registration. However mine took more than a day.

On the OpenWeatherMap website, you’ll find a range of pricing tiers and subscriptions. The are a diverse selection of data collections to fit your specific needs. Be mindful, though, that certain subscriptions require a payment. In my case, I opted for the free subscription, named “5 Day / 3 Hour Forecast”.

{
"MicrosoftAppType": "",
"MicrosoftAppId": "",
"MicrosoftAppPassword": "",
"MicrosoftAppTenantId": "",
"OpenWeatherKey": "394aexxxxxxxxxxxxxxxxxxxxxxx",
"OpenWeatherUrl": "http://api.openweathermap.org/data/2.5/forecast"
}

You’ll need to update the corresponding fields in the appsettings.json file, such as “OpenWeatherKey”, where you’ll input your generated API key and

“OpenWeatherUrl” with “http://api.openweathermap.org/data/2.5/forecast". which is a OpenWeatherMap API endpoint for retrieving a 5-day weather forecast for a given location.

Modifying the Echo Bot to become a Weather Bot

First create a class called WeatherBot in the same folder (Bots) as the EchoBot class. Delete EchoBot class as we don’t need it.

We will need to navigate to Startup.cs file and update the line 41 to use our new class WeatherBot instead of EchoBot. As shown below:

services.AddTransient<IBot, Bots.WeatherBot>();

Lastly, you will need to register HttpClient in the Startup.cs as shown below and also add using EchoBot.Services; to reference WeatherService class.

services.AddHttpClient<WeatherService>();

This code is configuring the application’s dependency injection container to register an instance of the HttpClient class for the WeatherService class.

Final ConfigureService method in the Startup.cs

public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient().AddControllers().AddNewtonsoftJson(options =>
{
options.SerializerSettings.MaxDepth = HttpHelper.BotMessageSerializerSettings.MaxDepth;
});

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

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

// Create the bot as a transient. In this case the ASP Controller is expecting an IBot.
services.AddTransient<IBot, Bots.WeatherBot>();

services.AddHttpClient<WeatherService>();
}

Back to WeatherBot class, now we will start adding properties and a constructor.

using System;
using System.Threading;
using System.Threading.Tasks;
using EchoBot.Services;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Schema;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using AdaptiveCards;
using Newtonsoft.Json;

namespace EchoBot.Bots
{
public class WeatherBot : ActivityHandler
{
private readonly ILogger _logger;
private readonly WeatherService _weatherService;
private readonly string _weatherKey;
private readonly string _weatherUrl;

public WeatherBot(ILogger<WeatherBot> logger, WeatherService weatherService, IConfiguration configuration)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_weatherService = weatherService ?? throw new ArgumentNullException(nameof(weatherService));
_weatherKey = configuration["OpenWeatherKey"] ?? throw new ArgumentNullException(nameof(configuration));
_weatherUrl = configuration["OpenWeatherUrl"] ?? throw new ArgumentNullException(nameof(configuration));
}
}
}

WeatherBot class inherits from ActivityHandler. You might be thinking why is that? Well, ActivityHandler is a class provided by the Microsoft Bot Framework that provides a set of default behaviours for handling different types of activities that a bot can receive, such as messages, conversation updates, and events. By inheriting from ActivityHandler, the WeatherBot class can take advantage of these default behaviours and override them as needed to implement its own custom logic for handling activities, which we will see shortly with OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken) method.

The ILogger is used for logging messages, while the IConfiguration is used to retrieve configuration settings.

The constructor initializes two private fields _weatherKey and _weatherUrl with values retrieved from the configuration settings (appsettings.json file we have discussed it previously).

_weatherKey is used to store an API key for the OpenWeatherMap API, while _weatherUrl is used to store the base URL for the OpenWeatherMap API.

using System;
using System.Threading;
using System.Threading.Tasks;
using EchoBot.Services;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Schema;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using AdaptiveCards;
using Newtonsoft.Json;

namespace EchoBot.Bots
{
public class WeatherBot : ActivityHandler
{
private readonly ILogger _logger;
private readonly WeatherService _weatherService;
private readonly string _weatherKey;
private readonly string _weatherUrl;

public WeatherBot(ILogger<WeatherBot> logger, WeatherService weatherService, IConfiguration configuration)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_weatherService = weatherService ?? throw new ArgumentNullException(nameof(weatherService));
_weatherKey = configuration["OpenWeatherKey"] ?? throw new ArgumentNullException(nameof(configuration));
_weatherUrl = configuration["OpenWeatherUrl"] ?? throw new ArgumentNullException(nameof(configuration));
}

protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
var text = turnContext.Activity.Text.Trim();

if (text.StartsWith("weather in "))
{
//custom logic to retrieve and send weather information for the specified city
}
// Execute Base Code
await base.OnMessageActivityAsync(turnContext, cancellationToken);
}
else
{
var replyText = $"Typed: {turnContext.Activity.Text}. Type 'weather in <city>' to get weather.";
await turnContext.SendActivityAsync(MessageFactory.Text(replyText, replyText), cancellationToken);
}
}
}
}

This is an overridden method OnMessageActivityAsync in the WeatherBot class that handles incoming message activities. The method retrieves the text of the message activity using turnContext.Activity.Text.Trim().

If the text of the message starts with the phrase “weather in “, the bot will execute custom logic to retrieve and send weather information for the specified city. If the text of the message does not start with “weather in “, the bot will simply echo back the message text (“Type ‘weather in <city>’ to get weather.”) with a prompt to enter a valid weather query for example “Type ‘weather in London’ to get weather for London city.”

The bot sends a message back to the user using turnContext.SendActivityAsync().

The MessageFactory.Text method is a helper method that creates a message activity with the specified text and optional SSML. It takes two parameters, the first is the text of the message and the second is the SSML, which is a markup language for speech synthesis.

using System;
using System.Threading;
using System.Threading.Tasks;
using EchoBot.Services;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Schema;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using AdaptiveCards;
using Newtonsoft.Json;

namespace EchoBot.Bots
{
public class WeatherBot : ActivityHandler
{
private readonly ILogger _logger;
private readonly WeatherService _weatherService;
private readonly string _weatherKey;
private readonly string _weatherUrl;

public WeatherBot(ILogger<WeatherBot> logger, WeatherService weatherService, IConfiguration configuration)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_weatherService = weatherService ?? throw new ArgumentNullException(nameof(weatherService));
_weatherKey = configuration["OpenWeatherKey"] ?? throw new ArgumentNullException(nameof(configuration));
_weatherUrl = configuration["OpenWeatherUrl"] ?? throw new ArgumentNullException(nameof(configuration));
}

protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
var text = turnContext.Activity.Text.Trim();

if (text.StartsWith("weather in "))
{
var cityName = text.Substring("weather in ".Length);
var weather = await _weatherService.GetWeatherAsync(cityName, _weatherKey, _logger, _weatherUrl);

}
// Execute Base Code
await base.OnMessageActivityAsync(turnContext, cancellationToken);
}
else
{
var replyText = $"Typed: {turnContext.Activity.Text}. Type 'weather in <city>' to get weather.";
await turnContext.SendActivityAsync(MessageFactory.Text(replyText, replyText), cancellationToken);
}
}
}
}

cityName variable is a string that contains the name of the city for which the user is requesting weather information.

The Substring method is then called on text, passing in the length of the string "weather in " as the starting index is 11 as the string is 11 characters long. This has the effect of removing the "weather in " prefix from the message text, leaving only the name of the city.

The GetWeatherAsync method takes four parameters: the name of the city for which to retrieve weather information, an API key for the OpenWeatherMap API, an ILogger instance for logging messages, and the base URL for the OpenWeatherMap API.

The await keyword is used to asynchronously wait for the GetWeatherAsync method to complete before continuing execution of the OnMessageActivityAsync method.

Creating weather service

using Newtonsoft.Json;
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;

namespace EchoBot.Services
{
public class WeatherService
{
private readonly HttpClient _httpClient;

public WeatherService(HttpClient httpClient)
{
_httpClient = httpClient;
}

public async Task<WeatherResponse> GetWeatherAsync(string cityName, string apiKey, ILogger log, string weatherUrl)
{
try
{
// Create the call
var url = $"{weatherUrl}?q={cityName}&appid={apiKey}&units=metric";
// var url = $"http://api.openweathermap.org/data/2.5/forecast?q={cityName}&units=metric&appid={apiKey}";

// Log the Url
log.LogInformation("Pulling data from " + url);

// Issue the call
var responseBody = await _httpClient.GetStringAsync(url);

// Interpret the response
var response = JsonConvert.DeserializeObject<WeatherResponse>(responseBody);

log.LogInformation($"Successfully retrieved weather data for {cityName}");

return response;
}
catch (Exception e)
{
log.LogError(e, e.Message);
}

return null;
}
}
}

You will need to create a folder called Services and create a new classes WeatherService. It will have only one method called GetWeatherAsync.

The GetWeatherAsync method uses the HttpClient class to make an HTTP request to the weather API using the specified weatherUrl and cityName parameters. The HttpClient class is used to send HTTP requests and receive HTTP responses.

The GetStringAsync method is a built-in method of the HttpClient class that is used to send an HTTP GET request to the specified URL and return the response body as a string.

Parsing JSON response

The response body is then deserialized into a WeatherResponse object using the JsonConvert.DeserializeObject method.

If the HTTP request is successful and the response body is successfully deserialized into a WeatherResponse object, the method logs a message indicating that weather data has been successfully retrieved for the specified city and returns the WeatherResponse object. If the HTTP request fails or the response body cannot be deserialized into a WeatherResponse object, the method logs an error message and returns null.

WeatherResponse Object

using System.Collections.Generic;

public class WeatherResponse
{
public List<Forecast> list { get; set; }
}

public class Forecast
{
public long dt { get; set; }
public Main main { get; set; }
public List<Weather> weather { get; set; }
}

public class Main
{
public double temp { get; set; }
public int humidity { get; set; }
}

public class Weather
{
public string description { get; set; }
public string icon { get; set; }
}

Create a new folder called Models and the WeatherResponse.cs file.

The WeatherResponse class represents the top-level response object returned by the OpenWeatherMap API when retrieving weather information for a city. It has a single property list that is a list of Forecast objects.

The Forecast class represents a single forecast for a specific date and time. It has three properties: dt, which is a Unix timestamp representing the date and time of the forecast, main, which is a Main object containing information about the forecasted temperature and humidity, and weather, which is a list of Weather objects containing information about the forecasted weather conditions.

using System;
using System.Threading;
using System.Threading.Tasks;
using EchoBot.Services;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Schema;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using AdaptiveCards;
using Newtonsoft.Json;

namespace EchoBot.Bots
{
public class WeatherBot : ActivityHandler
{
private readonly ILogger _logger;
private readonly WeatherService _weatherService;
private readonly string _weatherKey;
private readonly string _weatherUrl;

public WeatherBot(ILogger<WeatherBot> logger, WeatherService weatherService, IConfiguration configuration)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_weatherService = weatherService ?? throw new ArgumentNullException(nameof(weatherService));
_weatherKey = configuration["OpenWeatherKey"] ?? throw new ArgumentNullException(nameof(configuration));
_weatherUrl = configuration["OpenWeatherUrl"] ?? throw new ArgumentNullException(nameof(configuration));
}
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
var text = turnContext.Activity.Text.Trim();

if (text.StartsWith("weather in "))
{
var cityName = text.Substring("weather in ".Length);
var weather = await _weatherService.GetWeatherAsync(cityName, _weatherKey, _logger, _weatherUrl);
foreach (var forecast in weather.list)
{
var dateTime = DateTimeOffset.FromUnixTimeSeconds(forecast.dt).DateTime;
var description = (forecast.weather[0].description).ToUpper();
var temperature = forecast.main.temp;
var humidity = forecast.main.humidity;
var icon = forecast.weather[0].icon;

}
// Execute Base Code
await base.OnMessageActivityAsync(turnContext, cancellationToken);
}
else
{
var replyText = $"Echo: {turnContext.Activity.Text}. Say 'weather in <city>' to get weather.";
await turnContext.SendActivityAsync(MessageFactory.Text(replyText, replyText), cancellationToken);
}
}
}
}

Foreach loop iterates over a list of Forecast objects contained in the WeatherResponse object returned by the OpenWeatherMap API when retrieving weather information for a city.

  • dateTime: This is the date and time of the forecast data. The dt field in the API response represents the date and time in Unix timestamp format.
  • description: This is a text description of the weather conditions (e.g., "clear sky", "broken clouds").
  • temperature: This is the forecasted temperature in degrees Celsius.
  • humidity: This is the forecasted humidity in percent.
  • icon: This is a code for an icon that represents the weather conditions visually. The URL http://openweathermap.org/img/wn/{icon}@2x.png can be used to display the corresponding weather icon.

You can view the icons, their values, and descriptions in the icon list.

Here is the official documentation of the API, where you can find more detailed explanations of these fields and others: OpenWeatherMap 5 day / 3 hour forecast.

Using Adaptive Cards

You need to install the AdaptiveCards NuGet package. You can do this by running the following command in the NuGet Package Manager Console:

Install-Package AdaptiveCards

Or, if you prefer .NET CLI:

dotnet add package AdaptiveCards

At the top of your .NET C# file, you should include the following using directive:

using AdaptiveCards;

This will bring the AdaptiveCard namespace into scope, allowing you to use AdaptiveCard in your code.

Include the following using directive:

using Newtonsoft.Json;

The Newtonsoft.Json library provides a set of classes and methods for serializing and deserializing JSON data in a C# application.

Include the following using directive:

using System.Threading.Tasks;

OnMessageActivityAsync method is returning a Task, which is a type that represents an asynchronous operation.

The using System.Threading.Tasks statement is required in order to use the Task type in your code. Without this statement, the compiler would not recognize the Task type and would generate an error.

Adaptive Card Example from Bot Framework Emulator
AdaptiveCard card = new AdaptiveCard(new AdaptiveSchemaVersion(1, 0));
card.Body.Add(new AdaptiveTextBlock()
{
Text = $"Weather in {cityName} on {dateTime}",
Size = AdaptiveTextSize.Large,
Weight = AdaptiveTextWeight.Bolder
});
card.Body.Add(new AdaptiveTextBlock()
{
Text = $"{description}. Temperature: {temperature}°C. Humidity: {humidity}%",
Wrap = true
});
card.Body.Add(new AdaptiveImage()
{
Url = new Uri($"@4x.png">@4x.png">http://openweathermap.org/img/wn/{icon}@4x.png"),
Size = AdaptiveImageSize.Auto
});

The new AdaptiveCard object is initialized with a schema version of 1.0.

The body of the card is an array of AdaptiveElements and in our example there are three AdaptiveElement objects.

  1. AdaptiveTextBlock that displays the name of the city and the date and time of the weather forecast.
  2. Another AdaptiveTextBlock that displays the weather description, temperature, and humidity.
  3. AdaptiveImage that displays an icon representing the weather conditions. The Url property of the AdaptiveImage object is set to a Uri object that specifies the URL of the icon image. The Size property is set to AdaptiveImageSize.Auto, which tells the adaptive card to automatically size the image based on the available space.
// serialize the card to JSON
var cardJson = card.ToJson();

var attachment = new Attachment()
{
ContentType = "application/vnd.microsoft.card.adaptive",
Content = JsonConvert.DeserializeObject(cardJson),
};

var response = MessageFactory.Attachment(attachment);

The cardJson variable is a string that contains the JSON representation of the adaptiveCard object.

The Attachment class is part of the Microsoft.Bot.Schema namespace and is used to represent attachments in a bot message. The ContentType property of the Attachment object is set to "application/vnd.microsoft.card.adaptive", which specifies that the attachment is an adaptive card.

The other types of attachments that can be sent in a bot message using the Attachment class in the Microsoft.Bot.Schema namespace are Audio, Video, Animation, HeroCard, ThumbnailCard and ReceiptCard.

The Content property of the Attachment object is set to a deserialized version of the cardJson string. This creates a new AdaptiveCard object from the JSON representation of the adaptive card.

The MessageFactory.Attachment method is called with the Attachment object as a parameter to create a new bot message with the adaptive card attachment. This message is then returned to the user as a response to their query.

The SendActivityAsync method of the turnContext object is used to send a bot message back to the user.

The final code for WeatherBot.cs:

using System;
using System.Threading;
using System.Threading.Tasks;
using EchoBot.Services;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Schema;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using AdaptiveCards;
using Newtonsoft.Json;

namespace EchoBot.Bots
{
public class WeatherBot : ActivityHandler
{

private readonly ILogger _logger;
private readonly WeatherService _weatherService;
private readonly string _weatherKey;
private readonly string _weatherUrl;

public WeatherBot(ILogger<WeatherBot> logger, WeatherService weatherService, IConfiguration configuration)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_weatherService = weatherService ?? throw new ArgumentNullException(nameof(weatherService));
_weatherKey = configuration["OpenWeatherKey"] ?? throw new ArgumentNullException(nameof(configuration));
_weatherUrl = configuration["OpenWeatherUrl"] ?? throw new ArgumentNullException(nameof(configuration));
}

protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
var text = turnContext.Activity.Text.Trim();

if (text.StartsWith("weather in "))
{
var cityName = text.Substring("weather in ".Length);
var weather = await _weatherService.GetWeatherAsync(cityName, _weatherKey, _logger, _weatherUrl);
foreach (var forecast in weather.list)
{
var dateTime = DateTimeOffset.FromUnixTimeSeconds(forecast.dt).DateTime;
var description = (forecast.weather[0].description).ToUpper();
var temperature = forecast.main.temp;
var humidity = forecast.main.humidity;
var icon = forecast.weather[0].icon;

AdaptiveCard card = new AdaptiveCard(new AdaptiveSchemaVersion(1, 0));
card.Body.Add(new AdaptiveTextBlock()
{
Text = $"Weather in {cityName} on {dateTime}",
Size = AdaptiveTextSize.Large,
Weight = AdaptiveTextWeight.Bolder
});
card.Body.Add(new AdaptiveTextBlock()
{
Text = $"{description}. Temperature: {temperature}°C. Humidity: {humidity}%",
Wrap = true
});
card.Body.Add(new AdaptiveImage()
{
Url = new Uri($"@4x.png">@4x.png">http://openweathermap.org/img/wn/{icon}@4x.png"),
Size = AdaptiveImageSize.Auto
});

// serialize the card to JSON
var cardJson = card.ToJson();

var attachment = new Attachment()
{
ContentType = "application/vnd.microsoft.card.adaptive",
Content = JsonConvert.DeserializeObject(cardJson),
};

var response = MessageFactory.Attachment(attachment);
await turnContext.SendActivityAsync(response, cancellationToken);
}
// Execute Base Code
await base.OnMessageActivityAsync(turnContext, cancellationToken);
}
else
{
var replyText = $"You entered: {turnContext.Activity.Text}. Type 'weather in <city>' to get weather.";
await turnContext.SendActivityAsync(MessageFactory.Text(replyText, replyText), cancellationToken);
}
}
}
}

Testing the Weather Bot

Start the Emulator and connect your bot

  1. Start the Bot Framework Emulator.
  2. Select Open Bot on the Emulator’s Welcome tab.
  3. Enter your bot’s URL, which is your local host and port, with /api/messages added to the path. The address is usually: http://localhost:3978/api/messages
Bot Framework Emulator

4. Then select Connect.

Type a message to get a response.

Upon sending a message, the corresponding code gets triggered:

else
{
var replyText = $"You entered: {turnContext.Activity.Text}. Type 'weather in <city>' to get weather.";
await turnContext.SendActivityAsync(MessageFactory.Text(replyText, replyText), cancellationToken);
}

The user is instructed to Type “weather in <city>’ to get weather.” for that specific city.

If the user enters “weather in London”. The response will consist of adaptive cards displaying weather updates at 3-hour intervals for the forthcoming five days.

Debugging

If you get errors, I would recommend to use Visual Studio Code for debugging.

Conclusion

Throughout this tutorial, you’ve acquired the skills to construct a simple Echo Bot template and tailor it into your own weather bot. You’ve familiarized yourself with the OpenWeatherMap API, understanding how to connect with and query data from it. The tutorial also covered the variety of attachments that can be included in a bot message, even though we specifically focused on using the Adaptive Card in our example. You then had the opportunity to test the weather bot using the Bot Framework Emulator.

I encourage further exploration! If interested, you could tinker with the code, experimenting with different types of attachments such as the Hero Card or ThumbnailCard. You might also consider adding a button that queries the user if they desire a detailed weather breakdown, thereby providing even more comprehensive information.

Stay tuned for our next article where we’ll delve into how to integrate this weather bot into Microsoft Teams. We’ll touch upon tools like ngrok, Azure Bot Resources, Manifest files, and the process of uploading your applications to Microsoft Teams. Keep an eye out for it!

Resources and Further Reading

--

--