Build a stock update notification bot for Microsoft Teams using C# and Teams Toolkit for Visual Studio
Tags: microsoft365dev, visualstudio, csharp, dotnet
Published: 2022-11-08 13:31:00

---
title: Build a stock update notification bot for Microsoft Teams using C# and Teams Toolkit for Visual Studio
published: true
description: Microsoft Teams Toolkit for Visual Studio enables you to create, debug and deploy Microsoft Teams apps to a Microsoft 365 tenant fast using a no configuration approach. In this tutorial, I will guide you through the steps to create a Stock Update Notification bot for Teams Toolkit and C#
tags: microsoft365dev,visualstudio,csharp,dotnet
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qf751v2wnkcqldyxopsw.png
# Use a ratio of 100:42 for best results.
# published_at: 2022-11-08 13:30 +0000
---

Microsoft Teams Toolkit for Visual Studio enables you to create, debug and deploy Microsoft Teams apps to a Microsoft 365 tenant fast using a no configuration approach.

In this tutorial, I will guide you through the steps to create a Stock Update Notification bot for Teams Toolkit and C#.

![Animated GIF showing Microsoft Teams receiving a notification from a bot, the user opening the bot to find the latest stock price for Microsoft Corporation rendered as an Adaptive Card](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ousl8nhmo2wsjti675ho.gif)

If you want to see the finished version, checkout the [sample code](https://github.com/OfficeDev/TeamsFx-Samples/tree/dev/stocks-update-notification-bot-dotnet) in the TeamsFX Samples GitHub repository.

## Table of Contents

- [Prerequisites](#prereqs)
- [Install Teams Toolkit for Visual Studio](#install)
- [Scaffold the project](#scaffold)
- [Update the adaptive card](#adaptive-card)
- [Update the bot logic](#bot-logic)
- [Start ngrok](#ngrok)
- [Prepare dependencies](#dependencies)
- [Test your bot](#test)
- [What next?](#next)
- [Learn more!](#learn-more)

<h2 id="prereqs">🏁 Prerequisites</h2>

To follow this guide successfully you will need.

 - **Microsoft 365 Tenant**, which has been [enabled for developing custom applications with](https://learn.microsoft.com/microsoftteams/platform/m365-apps/prerequisites#prepare-a-developer-tenant-for-testing?WT.mc_id=m365-81147-garrytrinder). If you do not have a tenant, I highly recommend that you use a Microsoft 365 Developer Tenant, which you can obtain for free by [joining the Microsoft 365 Developer Program](https://developer.microsoft.com/microsoft-365/dev-program?WT.mc_id=m365-81147-garrytrinder). It is pre-configured for Microsoft Teams app development right out of the box.
 - **Visual Studio 2022 17.3+**, you can use either of the Community, Professional or Enterprise editions. If you do not have Visual Studio installed, [try out the Community edition for free](https://visualstudio.microsoft.com/vs/community/)!

 - **ngrok**, is a globally distributed reverse proxy which you can use to promote web services running on your local machine to an internet available address, in short, it puts localhost on the internet. This is required for Microsoft Teams to talk to the code running locally on your machine. If you do not have ngrok you can [download for free](https://ngrok.com/).

With the prerequisites done, let’s begin!

<h2 id="install">🛠 Install Teams Toolkit for Visual Studio</h2>

Our first step is to install Teams Toolkit into Visual Studio.

1. Open the _Visual Studio Installer_.
2. Click the _Modify_ button to configure your installation.
3. On the _Workloads_ tab, select the _ASP.NET and web development_ workload.
4. In the _Installation details_ pane on the right, scroll down the list and check the option called _Microsoft Teams development tools_.
5. Click the _Modify_ button to install Teams Toolkit.

Once the installation is complete, you can close the Visual Studio installer.

![Screenshot of Visual Studio Installer showing the ASP.NET and web development workload and Microsoft Teams development tools checked](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0s7wabbgg16lllz5psc0.png)

<h2 id="scaffold">👷 Scaffold the project</h2>

After installing Teams Toolkit into Visual Studio, it is now time to create our project.

1. Open _Visual Studio 2022_
2. From the start-up screen, select _Create a new project_.
3. On the _Create a new project screen_, expand the _All platforms_ dropdown, and select _Microsoft Teams_, this will display a single template called _Microsoft Teams App_, click _Next_ to confirm the template choice.
4. On the Configure your new project screen, enter _StocksUpdateNotificationBot_ in the Project name field, then click _Create_ to confirm.
5. In the _Create a new Teams application dialog_, expand the _Trigger type_ dropdown, and select _Timer Trigger (Azure Function)_, then click _Create_ to confirm.

Teams Toolkit will now generate a new project for you and open it for you.

![Screenshot of the Create a new Teams application dialog, with Notification Bot selected and the Trigger Type dropdown expanded](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dbp6nez3zduin1e2lr1b.png)

<h2 id="adaptive-card">ℹ️ Update the adaptive card</h2>

We will use an Adaptive Card to represent our stock price update, so let’s update the default adaptive card design to fit our needs.

Open the _NotificationDefault.json_ file in the Resources directory and replace the contents with the following JSON.

```json
{
  "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
  "type": "AdaptiveCard",
  "version": "1.4",
  "body": [
    {
      "type": "Container",
      "items": [
        {
          "type": "TextBlock",
          "text": "${name}",
          "size": "Medium",
          "wrap": true
        },
        {
          "type": "TextBlock",
          "text": "${symbol}",
          "isSubtle": true,
          "spacing": "None",
          "wrap": true
        },
        {
          "type": "TextBlock",
          "text": "{{DATE(${timestamp},SHORT)}} {{TIME(${timestamp})}}",
          "wrap": true
        }
      ]
    },
    {
      "type": "Container",
      "spacing": "None",
      "items": [
        {
          "type": "ColumnSet",
          "columns": [
            {
              "type": "Column",
              "width": "stretch",
              "items": [
                {
                  "type": "TextBlock",
                  "text": "${formatNumber(price,2)}",
                  "size": "ExtraLarge",
                  "wrap": true
                },
                {
                  "type": "TextBlock",
                  "text": "${if(change >= 0, '▲', '▼')} ${formatNumber(change,2)} USD (${formatNumber(changePercent, 2)}%)",
                  "spacing": "None",
                  "wrap": true
                }
              ]
            },
            {
              "type": "Column",
              "width": "auto",
              "items": [
                {
                  "type": "FactSet",
                  "facts": [
                    {
                      "title": "Open",
                      "value": "${formatNumber(open,2)}"
                    },
                    {
                      "title": "High",
                      "value": "${formatNumber(high,2)}"
                    },
                    {
                      "title": "Low",
                      "value": "${formatNumber(low,2)}"
                    }
                  ]
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}
```

We are using Adaptive Cards Template Language here to provide separation between our data and layout. Using binding expressions i.e., _${name}_, we can add placeholders to our layout in locations where we want our data to be rendered.

Let’s create a class to represent the data object to use with the template. Open the _NotificationDefaultModel.ts_ file in the Models directory and replace the contents with the following.

```csharp
namespace StocksUpdateNotificationBot.Models{
    public class GlobalQuote
    {
        public string Symbol { get; set; }
        public double Open { get; set; }
        public double High { get; set; }
        public double Low { get; set; }
        public double Price { get; set; }
        public double Volume { get; set; }
        public string LatestTradingDay { get; set; }
        public double PreviousClose { get; set; }
        public double Change { get; set; }
        public double ChangePercent { get; set; }
        public string Name { get; set; }
        public string Timestamp { get; set; }
    }
}

```

When we combine our template and data using the Adaptive Cards SDK, the notification will look something like this.

![A rendered Adaptive Card showing stock price information for MSFT](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y5tqvgpctbr2gh26vn4f.png)

<h2 id="bot-logic">🤖 Update the bot logic</h2>

Open the _NotifyTimerTrigger.cs_ file, this file contains the core logic of our bot that will be executed on a schedule. 

For now, let’s remove the contents of Run method and remove unused using statements, you can remove the statements by using the keyboard shortcut, _Ctrl + R followed by Ctrl + G_. 

As our bot will call out to an external API, we will need an HTTP client to execute the requests, so let’s also create a new read-only property in the _NotifyTimerTrigger_ class and create a new HTTP client.

```csharp
private readonly HttpClient _client = new();
```

Your file should look like the below block of code.

```csharp
using Microsoft.Azure.WebJobs;
using Microsoft.Extensions.Logging;
using Microsoft.TeamsFx.Conversation;

using ExecutionContext = Microsoft.Azure.WebJobs.ExecutionContext;

namespace StocksUpdateNotificationBot
{
    public sealed class NotifyTimerTrigger
    {
        private readonly ConversationBot _conversation;
        private readonly ILogger<NotifyTimerTrigger> _log;
        private readonly HttpClient _client = new();

        public NotifyTimerTrigger(ConversationBot conversation, ILogger<NotifyTimerTrigger> log)
        {
            _conversation = conversation;
            _log = log;
        }

        [FunctionName("NotifyTimerTrigger")]
        public async Task Run([TimerTrigger("*/30 * * * * *")]TimerInfo myTimer, ExecutionContext context, CancellationToken cancellationToken)
        {

        }
    }
}
```

Let’s add in the logic for our bot.

First let’s add a _try/catch_ block to the _Run_ method.

```csharp
try {
    // bot logic goes here
} catch (HttpRequestException e) {
    _log.LogError(e.Message);
}
```

When the bot is executed, our bot will need to obtain the latest stock price, to do this we will send a HTTP request to the Alpha Vantage API.

Replace the _// bot logic goes here_ comment inside the _try_ block, with the following code.

```csharp
// Get quote data from Alpha Vantage API
var response = await _client.GetStringAsync($"https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=MSFT&apikey=demo", cancellationToken);
```

Next, we need to deserialize and transform the response into the correct shape to use with our Adaptive Card model.

Add the following using statements to the top of the file.

```csharp
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using StocksUpdateNotificationBot.Models;
```

Add the following code into the _try_ block.

```csharp
// Deserialize JSON response
JObject jObj = (JObject)JsonConvert.DeserializeObject(response);

// Transform Global Quote object
var globalQuote = new GlobalQuote
{
    Symbol = jObj["Global Quote"]["01. symbol"].ToString(),
    Open = double.Parse(jObj["Global Quote"]["02. open"].ToString()),
    High = double.Parse(jObj["Global Quote"]["03. high"].ToString()),
    Low = double.Parse(jObj["Global Quote"]["04. low"].ToString()),
    Price = double.Parse(jObj["Global Quote"]["05. price"].ToString()),
    Volume = double.Parse(jObj["Global Quote"]["06. volume"].ToString()),
    LatestTradingDay = jObj["Global Quote"]["07. latest trading day"].ToString(),
    PreviousClose = double.Parse(jObj["Global Quote"]["08. previous close"].ToString()),
    Change = double.Parse(jObj["Global Quote"]["09. change"].ToString()),
    ChangePercent = double.Parse(jObj["Global Quote"]["10. change percent"].ToString().Replace("%", string.Empty)),
    Name = "Microsoft Corporation",
    Timestamp = $"{DateTime.Now.ToUniversalTime().ToString("o").Split(".")[0]}Z"
};
```

Now that we have all the data and it has been transformed into the correct shape, we need to obtain our Adaptive Card template.

Add the following code into the _try_ block.

```csharp
// Read adaptive card template
var adaptiveCardFilePath = Path.Combine(context.FunctionAppDirectory, "Resources", "NotificationDefault.json");
var cardTemplate = await File.ReadAllTextAsync(adaptiveCardFilePath, cancellationToken);
```

Finally, we want to iterate over all the locations where the bot has been installed in Microsoft Teams, render our Adaptive Card by combining the data and template, and send the card output into those locations.

Add the following code into the _try_ block.

```csharp
// Get bot installation
var installations = await _conversation.Notification.GetInstallationsAsync(cancellationToken);

foreach (var installation in installations)
{
    // Build and send adaptive card
    var cardContent = new AdaptiveCardTemplate(cardTemplate).Expand(globalQuote);
    await installation.SendAdaptiveCard(JsonConvert.DeserializeObject(cardContent), cancellationToken);
}
```

That’s the code complete, so let’s move onto getting our project ready to run our bot locally.

<h2 id="ngrok">📡 Start ngrok</h2>

We need to ensure that ngrok is running so that Microsoft Teams can communicate with the code running on our local machines. 

Open a terminal prompt at the location where you installed the ngrok executable and start the ngrok service forwarding port 5130 using the following command.

```cmd
.\ngrok http 5130
```

When ngrok is running, a dashboard will be displayed in your terminal prompt with the internet accessible ngrok address that is forwarding network traffic to your localhost hostname, which will be in the format of _<randomid>.<region>.ngrok.io_.


![Screenshot of ngrok running in Windows Terminal](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3ivl3wpqmawfhlnmlhnx.png)

<h2 id="dependencies">🧑‍🍳 Prepare dependencies</h2>

In Visual Studio, right click the _StocksUpdateNotificationBot_ project in the Solution Explorer pane, expand the _Teams Toolkit_ menu and select the first option in the menu, _Prepare Teams Apps Dependencies_.

![Screenshot of the steps to start the Prepare Teams Apps Dependencies process in Visual Studio](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oqr66x7h8bpf4k9em53n.png)

A Visual Studio account dialog box will be shown, add your Microsoft 365 Tenant account and follow the authentication steps. Once your account has been authenticated, click _Continue_ to start the process.

The _Prepare Teams Apps Dependencies_ process ensures that the required resources have been provisioned in your target Microsoft 365 Tenant, that services that are needed are running on your machine, such as ngrok, and updating the app manifest file.

<h2 id="test">🧪 Test your bot</h2>

Now it is time to see our bot in action!

To do this, simply press F5 on your keyboard.

The first time you run your bot, you will need to allow the Azure Functions executable to send network requests through the Windows Defender firewall, to enable this, click _Allow access_ in the prompt.

Teams Toolkit will open a browser window for you and start to open Microsoft Teams, you may be asked to sign into you Microsoft 365 Tenant at this point, if you are, complete the process. When you are authenticated, you will be presented with Microsoft Teams dialog which enables you to side load our app into your Microsoft 365 tenant. 

Click the _Add_ button to install the bot as a personal app and you will be taken to the chat tab of your personal app.

Wait a few seconds for your bot to be executed, and you will see a message arrive from our bot with the latest stock price rendered as an Adaptive Card.

> Congratulations, you have just created your first notification bot using Teams Toolkit for Visual Studio! 🎉

<h2 id="next">⏭ What next?</h2>

Want to make some updates? Here are a few things you can try.
- **Use a different symbol**, currently the stock price for MSFT is returned, pass a different symbol into the API call, and update the company name in the _GlobalQuote_ object.
- **Change the timer frequency**, currently the timer trigger is executed every 30 seconds, change this by updating the CRON expression passed into the Run method in _NotificationTimerTrigger.cs_ file. 
- **Update the app manifest**, currently the app manifest contains mostly default values, use the _Open Manifest File_ menu item in the Teams Toolkit menu to open the app manifest template and makes changes to it.
- **Update the app icons**, currently the app uses stock icons, update the icons in the _Templates/appPackage/resources_ directory with new more relevant icons.
- **Disable the bot chat compose box and @ mentions**, by default the bot chat compose box and the ability to @ mention the bot is enabled. To disable this, edit the app manifest and update the _isNotificationOnly_ property to _true_.
- **Restrict supported Teams scopes**, by default the bot can be installed into a personal, chat or channel context, to restrict where the bot can be used, update the scopes array in the app manifest.

<h2 id="learn-more">📚 Learn more!</h2>

 - [Microsoft Teams Developer Center](https://developer.microsoft.com/microsoft-teams?WT.mc_id=m365-81147-garrytrinder)
 - [Teams Toolkit Documentation](https://docs.microsoft.com/microsoftteams/platform/overview?WT.mc_id=m365-81147-garrytrinder)
 - [Teams Toolkit Overview](https://learn.microsoft.com/microsoftteams/platform/toolkit/teams-toolkit-fundamentals?WT.mc_id=m365-81147-garrytrinder)
 - [Build your first app using C#](https://learn.microsoft.com/microsoftteams/platform/sbs-gs-csharp?WT.mc_id=m365-81147-garrytrinder)

> 👋 Happy coding!

> Banner image generated using DALL·E 2 text to image API, _A friendly robot stood on the wall street trading floor_