{"id":12256,"date":"2022-11-15T11:00:41","date_gmt":"2022-11-15T19:00:41","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/?p=12256"},"modified":"2022-11-16T06:57:21","modified_gmt":"2022-11-16T14:57:21","slug":"build-a-stock-update-notification-bot-for-microsoft-teams-using-c-and-teams-toolkit-for-visual-studio","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/build-a-stock-update-notification-bot-for-microsoft-teams-using-c-and-teams-toolkit-for-visual-studio\/","title":{"rendered":"Build a stock update notification bot using C# and Teams Toolkit for Visual Studio"},"content":{"rendered":"<p>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 guide you through the steps to create a Stock Update Notification bot for Teams Toolkit and C#.<\/p>\n<p>Check out the <a href=\"https:\/\/github.com\/OfficeDev\/TeamsFx-Samples\/tree\/dev\/stocks-update-notification-bot-dotnet\">sample code<\/a> in the TeamsFX GitHub repository to see the finished version.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2022\/11\/Bot-notification-for-Csharp.gif\"><img decoding=\"async\" class=\"aligncenter size-full wp-image-12261\" src=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2022\/11\/Bot-notification-for-Csharp.gif\" alt=\"Animation of the finished stock notification bot for Microsoft Teams using C#\" width=\"602\" height=\"346\" \/><\/a><\/p>\n<h2>Table of Contents<\/h2>\n<ul>\n<li>Prerequisites<\/li>\n<li>Install Teams Toolkit for Visual Studio<\/li>\n<li>Scaffold the project<\/li>\n<li>Update the adaptive card<\/li>\n<li>Update the bot logic<\/li>\n<li>Start ngrok<\/li>\n<li>Prepare dependencies<\/li>\n<li>Test your bot<\/li>\n<li>What next?<\/li>\n<li>Learn more!<\/li>\n<\/ul>\n<h2>\ud83c\udfc1 Prerequisites<\/h2>\n<p>To follow this guide successfully you need the following.<\/p>\n<ul>\n<li><strong>Microsoft 365 tenant<\/strong>\u00a0<a href=\"https:\/\/learn.microsoft.com\/microsoftteams\/platform\/m365-apps\/prerequisites#prepare-a-developer-tenant-for-testing?WT.mc_id=m365-81147-garrytrinder\">enabled for developing custom applications<\/a>. If you do not have a tenant, I highly recommend you use a free Microsoft 365 developer tenant, which you can obtain by <a href=\"https:\/\/developer.microsoft.com\/microsoft-365\/dev-program?WT.mc_id=m365-81147-garrytrinder\">joining the Microsoft 365 Developer Program.<\/a> It&#8217;s pre-configured for Microsoft Teams app development right out of the box.<\/li>\n<li><strong>Visual Studio 2022 17.3+. <\/strong>You can use either of the Community, Professional or Enterprise editions. If you do not have Visual Studio installed, <a href=\"https:\/\/visualstudio.microsoft.com\/vs\/community\/\">try out the Community edition for free<\/a>!<\/li>\n<li><strong>ngrok<\/strong> is a globally distributed reverse proxy 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 <a href=\"https:\/\/ngrok.com\/\">download for free<\/a>.<\/li>\n<\/ul>\n<p>With the pre-requisites done, let\u2019s begin!<\/p>\n<h2>\ud83d\udee0 Install Teams Toolkit for Visual Studio<\/h2>\n<p>Our first step is to install Teams Toolkit into Visual Studio.<\/p>\n<ol>\n<li>Open the <em>Visual Studio Installer<\/em>.<\/li>\n<li>Click the <em>Modify<\/em> button to configure your installation.<\/li>\n<li>On the <em>Workloads<\/em> tab, select the <em>NET and web development<\/em> workload.<\/li>\n<li>In the Installation details pane on the right, scroll down the list and check the option called <em>Microsoft Teams development tools<\/em>.<\/li>\n<li>Click the <em>Modify<\/em> button to install Teams Toolkit.<\/li>\n<\/ol>\n<p>Once the installation is complete, you can close the Visual Studio installer.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2022\/11\/ttk-for-vs.png\"><img decoding=\"async\" class=\"aligncenter size-full wp-image-12266\" src=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2022\/11\/ttk-for-vs.png\" alt=\"Image of the Visual Studio Installer once completed\" width=\"903\" height=\"512\" srcset=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2022\/11\/ttk-for-vs.png 903w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2022\/11\/ttk-for-vs-300x170.png 300w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2022\/11\/ttk-for-vs-768x435.png 768w\" sizes=\"(max-width: 903px) 100vw, 903px\" \/><\/a><\/p>\n<h2>\ud83d\udc77 Scaffold the project<\/h2>\n<p>After installing Teams Toolkit into Visual Studio, it is time to create our project.<\/p>\n<ol>\n<li>Open <em>Visual Studio 2022<\/em><\/li>\n<li>From the start-up screen, select <em>Create a new project<\/em>.<\/li>\n<li>On the <em>Create a new project screen<\/em>, expand the <em>All-platforms <\/em>dropdown, and select <em>Microsoft Teams<\/em>, this will display a single template called <em>Microsoft Teams App<\/em>, click <em>Next<\/em> to confirm the template choice.<\/li>\n<li>On the <em>Configure your new project<\/em> screen, enter <em>StocksUpdateNotificationBot<\/em> in the Project name field, then click <em>Create<\/em> to confirm.<\/li>\n<li>In the <em>Create a new Teams application<\/em> dialog, expand the <em>Trigger type<\/em> dropdown, and select <em>Timer Trigger (Azure Function), <\/em>then click <em>Create<\/em> to confirm.<\/li>\n<\/ol>\n<p>Teams Toolkit will now generate a new project for you and open it.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2022\/11\/create-a-new-teams-applications.png\"><img decoding=\"async\" class=\"aligncenter size-full wp-image-12262\" src=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2022\/11\/create-a-new-teams-applications.png\" alt=\"Image of how to create a new Teams application\" width=\"903\" height=\"620\" srcset=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2022\/11\/create-a-new-teams-applications.png 903w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2022\/11\/create-a-new-teams-applications-300x206.png 300w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2022\/11\/create-a-new-teams-applications-768x527.png 768w\" sizes=\"(max-width: 903px) 100vw, 903px\" \/><\/a><\/p>\n<h2>\u2139\ufe0f Update the Adaptive Card<\/h2>\n<p>We will use an Adaptive Card to represent our stock price update, so let\u2019s update the default Adaptive Card design to fit our needs.<\/p>\n<p>Open the\u00a0<em>NotificationDefault.json<\/em>\u00a0file in the\u00a0<em>Resources<\/em> directory and replace the contents with the following JSON.<\/p>\n<pre class=\"prettyprint\">{\r\n  \"$schema\": \"http:\/\/adaptivecards.io\/schemas\/adaptive-card.json\",\r\n  \"type\": \"AdaptiveCard\",\r\n  \"version\": \"1.4\",\r\n  \"body\": [\r\n    {\r\n      \"type\": \"Container\",\r\n      \"items\": [\r\n        {\r\n          \"type\": \"TextBlock\",\r\n          \"text\": \"${name}\",\r\n          \"size\": \"Medium\",\r\n          \"wrap\": true\r\n        },\r\n        {\r\n          \"type\": \"TextBlock\",\r\n          \"text\": \"${symbol}\",\r\n          \"isSubtle\": true,\r\n          \"spacing\": \"None\",\r\n          \"wrap\": true\r\n        },\r\n        {\r\n          \"type\": \"TextBlock\",\r\n          \"text\": \"{{DATE(${timestamp},SHORT)}} {{TIME(${timestamp})}}\",\r\n          \"wrap\": true\r\n        }\r\n      ]\r\n    },\r\n    {\r\n      \"type\": \"Container\",\r\n      \"spacing\": \"None\",\r\n      \"items\": [\r\n        {\r\n          \"type\": \"ColumnSet\",\r\n          \"columns\": [\r\n            {\r\n              \"type\": \"Column\",\r\n              \"width\": \"stretch\",\r\n              \"items\": [\r\n                {\r\n                  \"type\": \"TextBlock\",\r\n                  \"text\": \"${formatNumber(price,2)}\",\r\n                  \"size\": \"ExtraLarge\",\r\n                  \"wrap\": true\r\n                },\r\n                {\r\n                  \"type\": \"TextBlock\",\r\n                  \"text\": \"${if(change &gt;= 0, '\u25b2', '\u25bc')} ${formatNumber(change,2)} USD (${formatNumber(changePercent, 2)}%)\",\r\n                  \"spacing\": \"None\",\r\n                  \"wrap\": true\r\n                }\r\n              ]\r\n            },\r\n            {\r\n              \"type\": \"Column\",\r\n              \"width\": \"auto\",\r\n              \"items\": [\r\n                {\r\n                  \"type\": \"FactSet\",\r\n                  \"facts\": [\r\n                    {\r\n                      \"title\": \"Open\",\r\n                      \"value\": \"${formatNumber(open,2)}\"\r\n                    },\r\n                    {\r\n                      \"title\": \"High\",\r\n                      \"value\": \"${formatNumber(high,2)}\"\r\n                    },\r\n                    {\r\n                      \"title\": \"Low\",\r\n                      \"value\": \"${formatNumber(low,2)}\"\r\n                    }\r\n                  ]\r\n                }\r\n              ]\r\n            }\r\n          ]\r\n        }\r\n      ]\r\n    }\r\n  ]\r\n}<\/pre>\n<p>We use 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.<\/p>\n<p>Let\u2019s create a class to represent the data object to use with the template. Open the\u00a0<em>NotificationDefaultModel.ts<\/em>\u00a0file in the\u00a0<em>Models<\/em>\u00a0directory and replace the contents with the following.<\/p>\n<pre class=\"prettyprint\">namespace StocksUpdateNotificationBot.Models{\r\n    public class GlobalQuote\r\n    {\r\n        public string Symbol { get; set; }\r\n        public double Open { get; set; }\r\n        public double High { get; set; }\r\n        public double Low { get; set; }\r\n        public double Price { get; set; }\r\n        public double Volume { get; set; }\r\n        public string LatestTradingDay { get; set; }\r\n        public double PreviousClose { get; set; }\r\n        public double Change { get; set; }\r\n        public double ChangePercent { get; set; }\r\n        public string Name { get; set; }\r\n        public string Timestamp { get; set; }\r\n    }\r\n}<\/pre>\n<p>When we combine our template and data using the Adaptive Cards SDK, the notification will look something like this.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2022\/11\/test-bot.png\"><img decoding=\"async\" class=\"aligncenter size-full wp-image-12265\" src=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2022\/11\/test-bot.png\" alt=\"Image of a test bot notification using the Adaptive Cards SDK\" width=\"903\" height=\"480\" srcset=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2022\/11\/test-bot.png 903w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2022\/11\/test-bot-300x159.png 300w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2022\/11\/test-bot-768x408.png 768w\" sizes=\"(max-width: 903px) 100vw, 903px\" \/><\/a><\/p>\n<h2>\ud83e\udd16 Update the bot logic<\/h2>\n<p>Open the <em>NotifyTimerTrigger.cs<\/em> file, this file contains the core logic of our bot that will be executed on a schedule.<\/p>\n<p>For now, let\u2019s remove the contents of <em>Run<\/em> method and remove unused using statements. You can remove the statements by using the keyboard shortcut, <em>Ctrl + R followed by Ctrl + G<\/em>.<\/p>\n<p>As our bot will call out to an external API, we need an HTTP client to execute the requests. So, let\u2019s also create a new read-only property in the <em>NotifyTimerTrigger<\/em> class and create a new HTTP client.<\/p>\n<pre class=\"prettyprint\">private readonly HttpClient _client = new();<\/pre>\n<p>Your file should look like the below block of code.<\/p>\n<pre class=\"prettyprint\">using Microsoft.Azure.WebJobs;\r\nusing Microsoft.Extensions.Logging;\r\nusing Microsoft.TeamsFx.Conversation;\r\n\r\nusing ExecutionContext = Microsoft.Azure.WebJobs.ExecutionContext;\r\n\r\nnamespace StocksUpdateNotificationBot\r\n{\r\n    public sealed class NotifyTimerTrigger\r\n    {\r\n        private readonly ConversationBot _conversation;\r\n        private readonly ILogger&lt;NotifyTimerTrigger&gt; _log;\r\n        private readonly HttpClient _client = new();\r\n\r\n        public NotifyTimerTrigger(ConversationBot conversation, ILogger&lt;NotifyTimerTrigger&gt; log)\r\n        {\r\n            _conversation = conversation;\r\n            _log = log;\r\n        }\r\n\r\n        [FunctionName(\"NotifyTimerTrigger\")]\r\n        public async Task Run([TimerTrigger(\"*\/30 * * * * *\")]TimerInfo myTimer, ExecutionContext context, CancellationToken cancellationToken)\r\n        {\r\n\r\n        }\r\n    }\r\n}<\/pre>\n<p>Let\u2019s add in the logic for our bot.<\/p>\n<p>First, let\u2019s add a <em>try\/catch<\/em> block to the <em>Run<\/em> method.<\/p>\n<pre class=\"prettyprint\">try {\r\n    \/\/ bot logic goes here\r\n} catch (HttpRequestException e) {\r\n    _log.LogError(e.Message);\r\n}<\/pre>\n<p>When the bot is executed, our bot needs to obtain the latest stock price. To do this, we will send a HTTP request to the Alpha Vantage API.<\/p>\n<p>Replace the <em>\/\/ bot logic goes here<\/em> comment inside the <em>try<\/em> block, with the following code:<\/p>\n<pre class=\"prettyprint\">\/\/ Get quote data from Alpha Vantage API\r\nvar response = await _client.GetStringAsync($\"https:\/\/www.alphavantage.co\/query?function=GLOBAL_QUOTE&amp;symbol=MSFT&amp;apikey=demo\", cancellationToken);<\/pre>\n<p>Next, we need to deserialize and transform the response into the correct shape to use with our Adaptive Card model.<\/p>\n<p>Add the following using statements to the top of the file.<\/p>\n<pre class=\"prettyprint\">using Newtonsoft.Json;\r\nusing Newtonsoft.Json.Linq;\r\nusing StocksUpdateNotificationBot.Models;<\/pre>\n<p>Add the following code into the <em>try<\/em> block.<\/p>\n<pre class=\"prettyprint\">\/\/ Deserialize JSON response\r\nJObject jObj = (JObject)JsonConvert.DeserializeObject(response);\r\n\r\n\/\/ Transform Global Quote object\r\nvar globalQuote = new GlobalQuote\r\n{\r\n    Symbol = jObj[\"Global Quote\"][\"01. symbol\"].ToString(),\r\n    Open = double.Parse(jObj[\"Global Quote\"][\"02. open\"].ToString()),\r\n    High = double.Parse(jObj[\"Global Quote\"][\"03. high\"].ToString()),\r\n    Low = double.Parse(jObj[\"Global Quote\"][\"04. low\"].ToString()),\r\n    Price = double.Parse(jObj[\"Global Quote\"][\"05. price\"].ToString()),\r\n    Volume = double.Parse(jObj[\"Global Quote\"][\"06. volume\"].ToString()),\r\n    LatestTradingDay = jObj[\"Global Quote\"][\"07. latest trading day\"].ToString(),\r\n    PreviousClose = double.Parse(jObj[\"Global Quote\"][\"08. previous close\"].ToString()),\r\n    Change = double.Parse(jObj[\"Global Quote\"][\"09. change\"].ToString()),\r\n    ChangePercent = double.Parse(jObj[\"Global Quote\"][\"10. change percent\"].ToString().Replace(\"%\", string.Empty)),\r\n    Name = \"Microsoft Corporation\",\r\n    Timestamp = $\"{DateTime.Now.ToUniversalTime().ToString(\"o\").Split(\".\")[0]}Z\"\r\n};<\/pre>\n<p>Now that we have all the data and it has been transformed into the correct shape, we need to obtain our Adaptive Card template.<\/p>\n<p>Add the following code into the try block.<\/p>\n<pre class=\"prettyprint\">\/\/ Read adaptive card template\r\nvar adaptiveCardFilePath = Path.Combine(context.FunctionAppDirectory, \"Resources\", \"NotificationDefault.json\");\r\nvar cardTemplate = await File.ReadAllTextAsync(adaptiveCardFilePath, cancellationToken);<\/pre>\n<p>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.<\/p>\n<p>Add the following code into the <em>try<\/em> block.<\/p>\n<pre class=\"prettyprint\">\/\/ Get bot installation\r\nvar installations = await _conversation.Notification.GetInstallationsAsync(cancellationToken);\r\n\r\nforeach (var installation in installations)\r\n{\r\n    \/\/ Build and send adaptive card\r\n    var cardContent = new AdaptiveCardTemplate(cardTemplate).Expand(globalQuote);\r\n    await installation.SendAdaptiveCard(JsonConvert.DeserializeObject(cardContent), cancellationToken);\r\n}<\/pre>\n<p>That\u2019s the code complete, so let\u2019s move onto getting our project ready to run our bot locally.<\/p>\n<h2>\ud83d\udce1 Start ngrok<\/h2>\n<p>We need to ensure that ngrok is running so Microsoft Teams can communicate with the code running on our local machines.<\/p>\n<p>Open a terminal prompt at the location where you installed the <em>ngrok<\/em> executable and start the ngrok service forwarding port 5130 using the following command:<\/p>\n<pre class=\"prettyprint\">&gt; .\\ngrok http 5130<\/pre>\n<p>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. This will be in the format of <em>&lt;randomid&gt;.&lt;region&gt;.ngrok.io<\/em>.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2022\/11\/ngrok.png\"><img decoding=\"async\" class=\"aligncenter size-full wp-image-12263\" src=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2022\/11\/ngrok.png\" alt=\"Image of ngrok running\" width=\"903\" height=\"521\" srcset=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2022\/11\/ngrok.png 903w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2022\/11\/ngrok-300x173.png 300w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2022\/11\/ngrok-768x443.png 768w\" sizes=\"(max-width: 903px) 100vw, 903px\" \/><\/a><\/p>\n<p>&nbsp;<\/p>\n<h2>\ud83e\uddd1\u200d\ud83c\udf73 Prepare dependencies<\/h2>\n<p>In Visual Studio, right click the <em>StocksUpdateNotificationBot<\/em> project in the Solution Explorer pane, expand the <em>Teams Toolkit<\/em> menu and select the first option in the menu, <em>Prepare Teams Apps Dependencies.<\/em><\/p>\n<p>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 <em>Continue<\/em> to start the process.<\/p>\n<p>The <em>Prepare Teams Apps Dependencies<\/em> process ensures that the required resources have been provisioned in your target Microsoft 365 tenant, and that the services needed are running on your machine, such as ngrok, and updating the app manifest file.<\/p>\n<h2>\ud83e\uddea Test your bot!<\/h2>\n<p>Now, it is time to see our bot in action! To do this, simply press F5 on your keyboard.<\/p>\n<p>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 <em>Allow access<\/em> in the prompt.<\/p>\n<p>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.<\/p>\n<p>Click the <em>Add<\/em> button to install the bot as a personal app and you will be taken to the chat tab of your personal app.<\/p>\n<p>Wait a few seconds for your bot to be executed. You will see a message arrive from our bot with the latest stock price rendered as an Adaptive Card.<\/p>\n<p>Congratulations, you have just created your first notification bot using Teams Toolkit for Visual Studio! \ud83c\udf89<\/p>\n<h2>\u23ed What\u2019s next?<\/h2>\n<p>Want to make some updates? Here are a few things you can try.<\/p>\n<ul>\n<li><strong>Use a different symbol<\/strong>, currently the stock price for MSFT is returned, pass a different symbol into the API call, and update the company name in the <em>GlobalQuote<\/em> object.<\/li>\n<li><strong>Change the timer frequency<\/strong>, currently the timer trigger is executed every 30 seconds, change this by updating the\u00a0CRON expression passed into the <em>Run<\/em> method in <em>NotificationTimerTrigger.cs<\/em> file.<\/li>\n<li><strong>Update the app manifest<\/strong>, currently the app manifest contains mostly default values, use the\u00a0<em>Open Manifest File<\/em>\u00a0menu item in the Teams Toolkit menu to open the app manifest template and makes changes to it.<\/li>\n<li><strong>Update the app icons,\u00a0<\/strong>currently the app uses stock icons, update the icons in the\u00a0<em>Templates\/appPackage\/resources<\/em>\u00a0directory with new more relevant icons.<\/li>\n<li><strong>Disable the bot chat compose box<\/strong>, by default the bot chat compose box is enabled to disable this edit the app manifest and update the\u00a0<em>isNotificationOnly<\/em>\u00a0property to <em>true<\/em>.<\/li>\n<li><strong>Restrict supported Teams scopes<\/strong>, 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.<\/li>\n<\/ul>\n<h2>\ud83d\udcda Learn more!<\/h2>\n<ul>\n<li><a href=\"https:\/\/developer.microsoft.com\/microsoft-teams?WT.mc_id=m365-81147-garrytrinder\">Microsoft Teams Developer Center<\/a><\/li>\n<li><a href=\"https:\/\/docs.microsoft.com\/microsoftteams\/platform\/overview?WT.mc_id=m365-81147-garrytrinder\">Teams Toolkit Documentation<\/a><\/li>\n<li><a href=\"https:\/\/learn.microsoft.com\/microsoftteams\/platform\/toolkit\/teams-toolkit-fundamentals?WT.mc_id=m365-81147-garrytrinder\">Teams Toolkit Overview<\/a><\/li>\n<li><a href=\"https:\/\/learn.microsoft.com\/microsoftteams\/platform\/sbs-gs-csharp?WT.mc_id=m365-81147-garrytrinder\">Build your first app using C#<\/a><\/li>\n<\/ul>\n<p>Happy coding!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>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.\u00a0Learn how to create a Stock Update Notification bot for Teams Toolkit and C# in this tutorial.<\/p>\n","protected":false},"author":100385,"featured_media":12269,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[8,128],"tags":[219,23,217,55],"class_list":["post-12256","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-adaptive-cards","category-microsoft-teams","tag-c","tag-teams-toolkit","tag-ngrok","tag-visual-studio"],"acf":[],"blog_post_summary":"<p>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.\u00a0Learn how to create a Stock Update Notification bot for Teams Toolkit and C# in this tutorial.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/posts\/12256","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/users\/100385"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/comments?post=12256"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/posts\/12256\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/media\/12269"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/media?parent=12256"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/categories?post=12256"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/tags?post=12256"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}