├── .gitignore ├── LICENSE ├── README.md ├── TeamsAppSample.NETCore.sln ├── TeamsAppSample.NETCore ├── Bot │ └── TeamsAppBot.cs ├── Pages │ ├── Auth │ │ ├── AuthFinishedRedirect.cshtml │ │ ├── AuthFinishedRedirect.cshtml.cs │ │ ├── Index.cshtml │ │ ├── Index.cshtml.cs │ │ ├── InitiateAuthFlow.cshtml │ │ └── InitiateAuthFlow.cshtml.cs │ ├── Index.cshtml │ └── Index.cshtml.cs ├── Program.cs ├── Startup.cs ├── TeamsAppSample.NETCore.csproj ├── TeamsManifest │ ├── color.png │ ├── manifest.json │ └── outline.png └── appsettings.json └── doc ├── configure-microsoft-teams-channel.png ├── entry-in-apps-list.png ├── install-app-pop-up-window.png ├── more-apps-menu-item.png ├── sample-app-menu-item.png ├── sample-app-tabs.png ├── set-up-bot-1.png ├── set-up-bot-2.png ├── store-upload-custom-app.png ├── team-settings.png └── webapp.png /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # Benchmark Results 49 | BenchmarkDotNet.Artifacts/ 50 | 51 | # .NET Core 52 | project.lock.json 53 | project.fragment.lock.json 54 | artifacts/ 55 | **/Properties/launchSettings.json 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_i.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.iobj 68 | *.pch 69 | *.pdb 70 | *.ipdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *.log 81 | *.vspscc 82 | *.vssscc 83 | .builds 84 | *.pidb 85 | *.svclog 86 | *.scc 87 | 88 | # Chutzpah Test files 89 | _Chutzpah* 90 | 91 | # Visual C++ cache files 92 | ipch/ 93 | *.aps 94 | *.ncb 95 | *.opendb 96 | *.opensdf 97 | *.sdf 98 | *.cachefile 99 | *.VC.db 100 | *.VC.VC.opendb 101 | 102 | # Visual Studio profiler 103 | *.psess 104 | *.vsp 105 | *.vspx 106 | *.sap 107 | 108 | # Visual Studio Trace Files 109 | *.e2e 110 | 111 | # TFS 2012 Local Workspace 112 | $tf/ 113 | 114 | # Guidance Automation Toolkit 115 | *.gpState 116 | 117 | # ReSharper is a .NET coding add-in 118 | _ReSharper*/ 119 | *.[Rr]e[Ss]harper 120 | *.DotSettings.user 121 | 122 | # JustCode is a .NET coding add-in 123 | .JustCode 124 | 125 | # TeamCity is a build add-in 126 | _TeamCity* 127 | 128 | # DotCover is a Code Coverage Tool 129 | *.dotCover 130 | 131 | # AxoCover is a Code Coverage Tool 132 | .axoCover/* 133 | !.axoCover/settings.json 134 | 135 | # Visual Studio code coverage results 136 | *.coverage 137 | *.coveragexml 138 | 139 | # NCrunch 140 | _NCrunch_* 141 | .*crunch*.local.xml 142 | nCrunchTemp_* 143 | 144 | # MightyMoose 145 | *.mm.* 146 | AutoTest.Net/ 147 | 148 | # Web workbench (sass) 149 | .sass-cache/ 150 | 151 | # Installshield output folder 152 | [Ee]xpress/ 153 | 154 | # DocProject is a documentation generator add-in 155 | DocProject/buildhelp/ 156 | DocProject/Help/*.HxT 157 | DocProject/Help/*.HxC 158 | DocProject/Help/*.hhc 159 | DocProject/Help/*.hhk 160 | DocProject/Help/*.hhp 161 | DocProject/Help/Html2 162 | DocProject/Help/html 163 | 164 | # Click-Once directory 165 | publish/ 166 | 167 | # Publish Web Output 168 | *.[Pp]ublish.xml 169 | *.azurePubxml 170 | # Note: Comment the next line if you want to checkin your web deploy settings, 171 | # but database connection strings (with potential passwords) will be unencrypted 172 | *.pubxml 173 | *.publishproj 174 | 175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 176 | # checkin your Azure Web App publish settings, but sensitive information contained 177 | # in these scripts will be unencrypted 178 | PublishScripts/ 179 | 180 | # NuGet Packages 181 | *.nupkg 182 | # The packages folder can be ignored because of Package Restore 183 | **/[Pp]ackages/* 184 | # except build/, which is used as an MSBuild target. 185 | !**/[Pp]ackages/build/ 186 | # Uncomment if necessary however generally it will be regenerated when needed 187 | #!**/[Pp]ackages/repositories.config 188 | # NuGet v3's project.json files produces more ignorable files 189 | *.nuget.props 190 | *.nuget.targets 191 | 192 | # Microsoft Azure Build Output 193 | csx/ 194 | *.build.csdef 195 | 196 | # Microsoft Azure Emulator 197 | ecf/ 198 | rcf/ 199 | 200 | # Windows Store app package directories and files 201 | AppPackages/ 202 | BundleArtifacts/ 203 | Package.StoreAssociation.xml 204 | _pkginfo.txt 205 | *.appx 206 | 207 | # Visual Studio cache files 208 | # files ending in .cache can be ignored 209 | *.[Cc]ache 210 | # but keep track of directories ending in .cache 211 | !*.[Cc]ache/ 212 | 213 | # Others 214 | ClientBin/ 215 | ~$* 216 | *~ 217 | *.dbmdl 218 | *.dbproj.schemaview 219 | *.jfm 220 | *.pfx 221 | *.publishsettings 222 | orleans.codegen.cs 223 | 224 | # Including strong name files can present a security risk 225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 226 | #*.snk 227 | 228 | # Since there are multiple workflows, uncomment next line to ignore bower_components 229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 230 | #bower_components/ 231 | 232 | # RIA/Silverlight projects 233 | Generated_Code/ 234 | 235 | # Backup & report files from converting an old project file 236 | # to a newer Visual Studio version. Backup files are not needed, 237 | # because we have git ;-) 238 | _UpgradeReport_Files/ 239 | Backup*/ 240 | UpgradeLog*.XML 241 | UpgradeLog*.htm 242 | ServiceFabricBackup/ 243 | *.rptproj.bak 244 | 245 | # SQL Server files 246 | *.mdf 247 | *.ldf 248 | *.ndf 249 | 250 | # Business Intelligence projects 251 | *.rdl.data 252 | *.bim.layout 253 | *.bim_*.settings 254 | *.rptproj.rsuser 255 | 256 | # Microsoft Fakes 257 | FakesAssemblies/ 258 | 259 | # GhostDoc plugin setting file 260 | *.GhostDoc.xml 261 | 262 | # Node.js Tools for Visual Studio 263 | .ntvs_analysis.dat 264 | node_modules/ 265 | 266 | # Visual Studio 6 build log 267 | *.plg 268 | 269 | # Visual Studio 6 workspace options file 270 | *.opt 271 | 272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 273 | *.vbw 274 | 275 | # Visual Studio LightSwitch build output 276 | **/*.HTMLClient/GeneratedArtifacts 277 | **/*.DesktopClient/GeneratedArtifacts 278 | **/*.DesktopClient/ModelManifest.xml 279 | **/*.Server/GeneratedArtifacts 280 | **/*.Server/ModelManifest.xml 281 | _Pvt_Extensions 282 | 283 | # Paket dependency manager 284 | .paket/paket.exe 285 | paket-files/ 286 | 287 | # FAKE - F# Make 288 | .fake/ 289 | 290 | # JetBrains Rider 291 | .idea/ 292 | *.sln.iml 293 | 294 | # CodeRush 295 | .cr/ 296 | 297 | # Python Tools for Visual Studio (PTVS) 298 | __pycache__/ 299 | *.pyc 300 | 301 | # Cake - Uncomment if you are using it 302 | # tools/** 303 | # !tools/packages.config 304 | 305 | # Tabs Studio 306 | *.tss 307 | 308 | # Telerik's JustMock configuration file 309 | *.jmconfig 310 | 311 | # BizTalk build output 312 | *.btp.cs 313 | *.btm.cs 314 | *.odx.cs 315 | *.xsd.cs 316 | 317 | # OpenCover UI analysis results 318 | OpenCover/ 319 | 320 | # Azure Stream Analytics local run output 321 | ASALocalRun/ 322 | 323 | # MSBuild Binary and Structured Log 324 | *.binlog 325 | 326 | # NVidia Nsight GPU debugger configuration file 327 | *.nvuser 328 | 329 | # MFractors (Xamarin productivity tool) working folder 330 | .mfractor/ 331 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Microsoft 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Teams App Sample - .NET Core 2 | ============================ 3 | 4 | [Microsoft Teams apps](https://developer.microsoft.com/en-us/microsoft-teams) are web apps. 5 | The foundation of Teams specific apps is partially based on the building blocks that make up the 6 | [Microsoft Bot Framework](https://dev.botframework.com/). In addition to that Teams app typically 7 | utilize tabs (web content in `iframe`), connectors to allow rich notifications and 8 | [more](https://developer.microsoft.com/en-us/microsoft-teams/scenarios). But that doesn't change the 9 | fact that a Teams app is simply a web app. 10 | 11 | #### Microsoft Bot Framework v4 supports .NET Core #### 12 | 13 | The short-lived purpose of this sample is to serve as a quick guide on how to implement a Teams app 14 | using [Bot Framework v4](https://github.com/Microsoft/botbuilder-dotnet) - which as of writing this 15 | is still **in preview** - instead of the current v3.x. A reason one might have to target the preview 16 | version is the fact that while **v3.x is based on .NET Framework 4.x**, the new **v4 targets .NET 17 | Core**. Furthermore, the reason one might care is based on the platform their running their 18 | software on; **.NET Core is multi-platform** including Linux support. To learn more, visit 19 | [docs.microsoft.com](https://docs.microsoft.com/en-us/dotnet/standard/choosing-core-framework-server). 20 | 21 | ![Author's highly technical illustration of a web app.](/doc/webapp.png?size=50)
22 | *Author's highly technical illustration of a web app.* 23 | 24 | Is there any reason .NET Core/Bot Framework v4 combo wouldn't work as a basis for a Teams app? Not 25 | that I can tell - please refer to the technical outline above. As long as the web app looks the same 26 | outside providing the appropriate inputs and outputs and knows how to handle the messages the same 27 | way, it is reasonable to expect everything to just work. If it looks like a duck, swims like a duck, 28 | and quacks like a duck... you get the point. 29 | 30 | Moreover, this sample includes the notorious somewhat tricky authentication bit in 31 | Teams apps. Namely, the sample provides the code required to authenticate using the 32 | [Microsoft Graph](https://developer.microsoft.com/en-us/graph) **within Teams**. 33 | 34 | If it's 2019 and you're reading this, then it's fair to assume the information here is no longer of 35 | any use as Bot Framework v4 should be the official version (no longer in preview) now. Thanks for 36 | visiting anyways. 37 | 38 | #### Contents #### 39 | 40 | * [How to set up and install the sample in Teams](#so-how-do-i) 41 | * [Authentication using Microsoft Graph/Azure AD](#authentication) 42 | * [Troubleshooting](#troubleshooting) 43 | * [Links to proper documentation](#further-reading) 44 | * May contain traces of nuts 45 | 46 | 47 | ## So, how do I... ## 48 | 49 | Before getting into the Teams specific stuff, let's first deploy the app (BOT!) and make sure it 50 | works. Now, 51 | you may ask yourself "how do I work this?" and 52 | you may ask yourself "what does the bot have to do with any of this?", but, as mentioned earlier, 53 | Teams apps are partially based on the building blocks of the bot framework and this sample comes 54 | with a bot. So just go with it, ok? And when we deploy the bot, we are, in fact, deploying the whole 55 | Teams app itself. 56 | 57 | Note that the steps described here are for 58 | [**Visual Studio**](https://visualstudio.microsoft.com/downloads/). In case you're using some other 59 | IDE, most of the content still applies, but you may have to consult a web search engine. The other 60 | thing here is that the deployment steps are for **Azure**. The bot app can be hosted practically 61 | anywhere, but then again you must look for the instructions elsewhere. Isn't life full of choices? 62 | 63 | If you don't have an Azure subscription yet, do not worry - you can get one for FREE here: 64 | [Create your Azure free account today](https://azure.microsoft.com/en-us/free/). Yeyyaah! 65 | 66 | Here goes: 67 | 68 | 1. Open the solution (`TeamsAppSample.NETCore.sln`) in Visual Studio/your IDE and make sure it 69 | compiles without any errors (or warnings) 70 | 2. Follow the steps in this article carefully: 71 | [Deploy your bot to Azure](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-howto-deploy-azure?view=azure-bot-service-4.0) 72 | * Top tip: Create a new [Azure resource group](https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-overview#resource-groups) 73 | for the app so that if stuff goes wrong, it's really easy to just delete the whole group and 74 | start over 75 | * Having issues testing the bot (as in "The dang thing doesn't work!!") - check the following: 76 | * Did you remember to include `/api/messages` in the messaging endpoint 77 | (Bot Channels Registration/Settings)? 78 | * Did you remember to create and add the credentials (`MicrosoftAppId` and `MicrosoftAppPassword`)? 79 | 80 | By the way: You can define a custom messaging endpoint in the code like this 81 | (see [`Startup.cs`](/TeamsAppSample.NETCore/Startup.cs)): 82 | 83 | ```cs 84 | // app is of type IApplicationBuilder 85 | app.UseBotFramework(bot => 86 | { 87 | bot.BasePath = "/api"; 88 | bot.MessagesPath = "/tidings"; 89 | }); 90 | ``` 91 | 92 | Finally add the credentials (`MicrosoftAppId` and `MicrosoftAppPassword`) to the 93 | [`appsettings.json` file](/TeamsAppSample.NETCore/appsettings.json) and republish the bot - now all 94 | you need to do to republish is to right-click the app project in the **Solution Explorer** in 95 | Visual Studio, select **Publish...** and click the **Publish** button on the tab (named in the 96 | sample "TeamsAppSample.NETCore"). 97 | 98 | Well, that was quick and easy (I hope). Next, why not download the 99 | [Bot Framework Emulator](https://docs.microsoft.com/en-us/azure/bot-service/bot-service-debug-emulator?view=azure-bot-service-3.0) 100 | and try it out! 101 | 102 | ### Enable and install the app in Teams ### 103 | 104 | First, we want to make sure our bot is enabled in Teams. Go to the 105 | [Azure portal](https://portal.azure.com) and to the familiar **Bot Channels Registration** resource 106 | you created previously. Select **Channels** under **BOT MANAGEMENT** and click the Teams icon: 107 | 108 | ![Configuring Microsoft Teams channel in Azure portal](/doc/configure-microsoft-teams-channel.png?size=50) 109 | 110 | Then package and upload the app manifest in Teams. Remember when I said (wrote) that a Teams app is 111 | nothing but a web app? Still true. That also means that your app doesn't live inside Teams, but 112 | rather extends its tentacles (endpoints) into the abyss of the Teams registry. For the exact details see 113 | [this article](https://docs.microsoft.com/en-us/microsoftteams/platform/concepts/apps/apps-upload), 114 | **but** here's the quick guide: 115 | 116 | 1. Open the [`manifest.json`](/TeamsAppSample.NETCore/TeamsManifest/manifest.json) file in text 117 | editor and update the base of the URLs to match the URL of your published bot **and** add your 118 | bot ID (`MicrosoftAppId`) as the value of `botId` within `bots` array 119 | * Top tip: Use [App Studio for Microsoft Teams](https://docs.microsoft.com/en-us/microsoftteams/platform/get-started/get-started-app-studio) 120 | to create and manage manifest files (and to see what properties there are) 121 | 2. Package the content in the [`TeamsManifest`](/TeamsAppSample.NETCore/TeamsManifest/) folder in a 122 | `.zip` file so that all the files are in the root of the package (the name of the package is 123 | insignificant in the grand scheme of things) 124 | 125 | Next you can choose whether to install the app for the team only or to install for personal use 126 | (given that the app provides personal features such as 127 | [static tabs](https://docs.microsoft.com/en-us/microsoftteams/platform/concepts/tabs/tabs-static), 128 | which our sample does) and/or for the team. 129 | 130 | #### Option 1. Install for the team only #### 131 | 132 | **If you want to test the authentication bit as well, skip right to the option 2.** 133 | 134 | 1. In Teams, click the three dots next to the team, where you want to install the app, and select 135 | **Manage team**: 136 | 137 | ![Team settings](/doc/team-settings.png?size=50) 138 | 2. Navigate to the **Apps** tab and locate the **Upload a custom app** link in the bottom-right 139 | corner and click it, CLICK IT! 140 | 3. Browse to the location of your `.zip` package containing the manifest file and the icons and 141 | select **Open** 142 | * If there were no errors, you should now see your app in the list - looks something like this: 143 | 144 | ![Entry in apps list](/doc/entry-in-apps-list.png?size=50) 145 | 4. You can set up the bot by simply calling it in channel conversation, but there is a guided way to 146 | do it: 147 | * Still in the manage apps view in team settings, click the app list item (shown in the image above) 148 | * Click **Available** link in the pop-up window 149 | 150 | ![First pop-up window](/doc/set-up-bot-1.png) 151 | * In the new pop-up window, select the desired channel and click the **Set up** button 152 | 153 | ![Second pop-up window](/doc/set-up-bot-2.png) 154 | 155 | #### Option 2. Install for personal use/the team #### 156 | 157 | 1. In teams, click the three dots on the left-most pane (typically on purple background under the 158 | **Files** icon) and select **More apps** 159 | 160 | ![More apps menu item](/doc/more-apps-menu-item.png) 161 | 2. Select **Upload a custom app** in the menu on the left 162 | 163 | ![Store menu](/doc/store-upload-custom-app.png) 164 | 3. Browse to the location of your `.zip` package containing the manifest file and the icons and 165 | select **Open** 166 | 4. Select the desired team to install the app for in the pop-up window and click **Install** 167 | 168 | ![Install app pop-up window](/doc/install-app-pop-up-window.png) 169 | 5. Follow the instructions to set up the bot, if you so desire 170 | 6. You can now find the app in the app menu: 171 | 172 | ![Sample app menu item](/doc/sample-app-menu-item.png) 173 | 7. Click the app in the menu to view the personal tabs, which in the case of the sample look like this: 174 | 175 | ![Sample app tabs](/doc/sample-app-tabs.png) 176 | 177 | ### Authentication ### 178 | 179 | [Microsoft Graph](https://developer.microsoft.com/en-us/graph/docs/concepts/overview) provides an 180 | API to authenticate users via 181 | [Azure Active Directory](https://docs.microsoft.com/en-us/azure/active-directory/fundamentals/active-directory-whatis) 182 | (Azure AD). This sample contains the necessary bits to execute the authentication process within 183 | Teams (the authentication tab shown above). The code for the authentication is shamelessly copied 184 | from 185 | [this Microsoft Teams sample](https://github.com/OfficeDev/microsoft-teams-sample-complete-csharp/tree/tutorial_11_authentication_graph), 186 | but modified into 187 | [Razor pages](https://docs.microsoft.com/en-us/aspnet/core/mvc/razor-pages/?view=aspnetcore-2.1&tabs=visual-studio) - 188 | webpage building blocks provided by **ASP.NET Core**. 189 | 190 | You can find the authentication specific code in the 191 | [/TeamsAppSample.NETCore/Pages/Auth](/TeamsAppSample.NETCore/Pages/Auth) folder. 192 | 193 | To test the authentication flow do the following: 194 | 195 | 1. Register a new application in the [Application Registration Portal](https://apps.dev.microsoft.com) 196 | * Follow the instructions under the **Register the application** header [here](https://developer.microsoft.com/en-us/graph/docs/concepts/aspnetmvc), but 197 | * Add redirect URL `https:///Auth/AuthFinishedRedirect` and 198 | * ignore other instructions on the page 199 | 2. Copy the application ID of the app you just registered into the 200 | [`manifest.json`](/TeamsAppSample.NETCore/TeamsManifest/manifest.json) file as the value of the 201 | `AuthClientId` property 202 | * The [`InitiateAuthFlow.cshtml`](/TeamsAppSample.NETCore/Pages/Auth/InitiateAuthFlow.cshtml) 203 | page then picks it up into the query parameters of the auth endpoint call 204 | 3. Republish the app 205 | 4. Try it out! 206 | 207 | Now don't feel bad, if the whole authentication process feels weird - it's not that straightforward. I encourage you to check out the blog post, [Why is authenticating a bot so hard?](http://pedro.digitaldias.com/?p=478), written by Pedro Dias. 208 | 209 | ## Troubleshooting ## 210 | 211 | Or as I like to call it: Things that make you cuss and bang your head on hard solid vertical planes. 212 | 213 | ### Why doesn't my bot talk to me? ### 214 | 215 | Given that you didn't mess with the actual code, there's one thing remember: 216 | * Endpoint URL 217 | * Make sure this is set in the settings of the **Bot Channels Registration** resource in the 218 | **Azure Portal** and that it ends with the appropriate path (`/api/messages` by default) 219 | 220 | ...no wait, actually three things - two in addition to the one I just mentioned: 221 | 222 | * `MicrosoftAppId` 223 | * `MicrosoftAppPassword` 224 | 225 | Make sure these are set in: 226 | 227 | * the [`appsettings.json` file](/TeamsAppSample.NETCore/appsettings.json) file 228 | * the **Application Settings** of the bot **App Service** (again in Azure Portal) 229 | * the Bot Framework Emulator, when using it 230 | 231 | No! Wait! One more thing. There are four things to remember. Did you remember to enable the 232 | **Channels** (such as Microsoft Teams, Slack etc.) under **BOT MANAGEMENT** of the **Bot Channels 233 | Registration** resource in - you guessed it! - **Azure Portal**? You did? Good. 234 | 235 | Let's move o...SH*T! Five things. Five things to remember. If your bot's not co-operative in Teams, 236 | then check the value of the `botId` property in `bots` array - that needs to match your 237 | `MicrosoftAppId`. That's how the Teams know to which bot to pass the messages to. 238 | 239 | That's it! 240 | 241 | ### No content shown on tabs ### 242 | 243 | Microsoft Teams app manifest contains a property (array) named `validDomains`, which lists the 244 | domains where the app is allowed to load content from. Make sure you remember this, when creating 245 | your app. In this sample I've **NEGLECTED ALL SECURITY** and simply done this: 246 | 247 | ```json 248 | "validDomains": [ 249 | "*.azurewebsites.net" 250 | ] 251 | ``` 252 | 253 | Read more: [Manifest schema: validDomains](https://docs.microsoft.com/en-us/microsoftteams/platform/resources/schema/manifest-schema#validdomains) 254 | 255 | ### Web page 404 ### 256 | 257 | Well, now you must have messed with the code! But I really want to mention this: Razor pages use 258 | MVC. And why should you care? Because if you don't, bad things can happen, and by bad things I mean 259 | you'll get **404**. Check out [`Startup.cs`](/TeamsAppSample.NETCore/Startup.cs) - there are two 260 | spots there that must be defined in order for the pages to be accessible: 261 | 262 | In `ConfigureServices` method: 263 | 264 | ```cs 265 | services.AddMvc(); 266 | ``` 267 | 268 | In `Configure` method: 269 | 270 | ```cs 271 | app.UseMvc(); 272 | ``` 273 | 274 | ## Further reading ## 275 | 276 | * [The Microsoft Teams developer platform](https://docs.microsoft.com/en-us/microsoftteams/platform/overview) 277 | * [Create a bot with the Bot Builder SDK v4 for .NET](https://docs.microsoft.com/en-us/azure/bot-service/dotnet/bot-builder-dotnet-sdk-quickstart?view=azure-bot-service-4.0) 278 | * [Getting started building Microsoft Graph apps](https://developer.microsoft.com/en-us/graph/docs/concepts/get-started) 279 | * [Why is authenticating a bot so hard?](http://pedro.digitaldias.com/?p=478) 280 | -------------------------------------------------------------------------------- /TeamsAppSample.NETCore.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27703.2026 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TeamsAppSample.NETCore", "TeamsAppSample.NETCore\TeamsAppSample.NETCore.csproj", "{9E7BC430-49C6-4A40-859B-C7CD0DCBAFF2}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {9E7BC430-49C6-4A40-859B-C7CD0DCBAFF2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {9E7BC430-49C6-4A40-859B-C7CD0DCBAFF2}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {9E7BC430-49C6-4A40-859B-C7CD0DCBAFF2}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {9E7BC430-49C6-4A40-859B-C7CD0DCBAFF2}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {A9C4043D-26FF-4E80-B764-96A413814E79} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /TeamsAppSample.NETCore/Bot/TeamsAppBot.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Bot; 2 | using Microsoft.Bot.Builder; 3 | using Microsoft.Bot.Schema; 4 | using System.Threading.Tasks; 5 | 6 | namespace TeamsAppSample.NETCore.Bot 7 | { 8 | public class TeamsAppBot : IBot 9 | { 10 | public async Task OnTurn(ITurnContext context) 11 | { 12 | if (context.Activity.Type == ActivityTypes.Message) 13 | { 14 | await context.SendActivity($"You wrote '{context.Activity.Text}'"); 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /TeamsAppSample.NETCore/Pages/Auth/AuthFinishedRedirect.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model TeamsAppSample.NETCore.Pages.Auth.AuthFinishedRedirectModel 3 | @{ 4 | ViewData["Title"] = "AuthFinishedRedirect"; 5 | } 6 | 7 | 8 | 9 | 53 | -------------------------------------------------------------------------------- /TeamsAppSample.NETCore/Pages/Auth/AuthFinishedRedirect.cshtml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.RazorPages; 2 | 3 | namespace TeamsAppSample.NETCore.Pages.Auth 4 | { 5 | public class AuthFinishedRedirectModel : PageModel 6 | { 7 | public void OnGet() 8 | { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /TeamsAppSample.NETCore/Pages/Auth/Index.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @using TeamsAppSample.NETCore.Pages.Auth 3 | @model IndexModel 4 | @{ 5 | ViewData["Title"] = "Authenticate"; 6 | } 7 | 8 | 9 | 10 | 11 | 70 | 71 |

72 | Click on the "Login" button below to login to Azure AD, and grant the sample app access to your profile information. 73 | The app will fetch your profile using Microsoft Graph, and display information about you below. 74 |

75 | 76 | 77 | 78 | 79 | 80 |

81 |

82 | 89 |

90 | -------------------------------------------------------------------------------- /TeamsAppSample.NETCore/Pages/Auth/Index.cshtml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.RazorPages; 2 | 3 | namespace TeamsAppSample.NETCore.Pages.Auth 4 | { 5 | public class IndexModel : PageModel 6 | { 7 | public void OnGet() 8 | { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /TeamsAppSample.NETCore/Pages/Auth/InitiateAuthFlow.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model TeamsAppSample.NETCore.Pages.Auth.InitiateAuthFlowModel 3 | @{ 4 | } 5 | 6 | 7 | 8 | 119 | -------------------------------------------------------------------------------- /TeamsAppSample.NETCore/Pages/Auth/InitiateAuthFlow.cshtml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.AspNetCore.Mvc.RazorPages; 3 | using Microsoft.Extensions.Configuration; 4 | 5 | namespace TeamsAppSample.NETCore.Pages.Auth 6 | { 7 | public class InitiateAuthFlowModel : PageModel 8 | { 9 | [BindProperty] 10 | public string ClientId 11 | { 12 | get 13 | { 14 | return Configuration["AuthClientId"]; 15 | } 16 | } 17 | 18 | private IConfiguration Configuration 19 | { 20 | get; 21 | } 22 | 23 | public InitiateAuthFlowModel(IConfiguration configuration) 24 | { 25 | Configuration = configuration; 26 | } 27 | 28 | public void OnGet() 29 | { 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /TeamsAppSample.NETCore/Pages/Index.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @using TeamsAppSample.NETCore.Pages 3 | @model IndexModel 4 | @{ 5 | } 6 | 7 |

Teams App Sample - .NET Core

8 | 9 |

10 | The short-lived purpose of this sample is to serve as a quick guide on how to implement a Teams app using Bot Framework v4 11 | - which as of writing this is still in preview - instead of the current v3.x. 12 | Visit https://github.com/tompaana/teams-app-net-core-sample to learn more. 13 |

14 | 15 |

Bot

16 | 17 |

The bot endpoint URL is:

18 | 19 | 24 | 25 |

The bot credentials are:

26 | 30 | 31 | 37 | -------------------------------------------------------------------------------- /TeamsAppSample.NETCore/Pages/Index.cshtml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.AspNetCore.Mvc.RazorPages; 3 | using Microsoft.Extensions.Configuration; 4 | 5 | namespace TeamsAppSample.NETCore.Pages 6 | { 7 | public class IndexModel : PageModel 8 | { 9 | [BindProperty] 10 | public string BotEndpointPath 11 | { 12 | get 13 | { 14 | return $"{Configuration["BotBasePath"]}{Configuration["BotMessagesPath"]}"; 15 | } 16 | } 17 | 18 | [BindProperty] 19 | public string BotAppId 20 | { 21 | get 22 | { 23 | #if DEBUG 24 | return Configuration["MicrosoftAppId"]; 25 | #else 26 | return "None of your business"; 27 | #endif 28 | } 29 | } 30 | 31 | [BindProperty] 32 | public string BotAppPassword 33 | { 34 | get 35 | { 36 | #if DEBUG 37 | return Configuration["MicrosoftAppPassword"]; 38 | #else 39 | return "Also none of your business"; 40 | #endif 41 | } 42 | } 43 | 44 | private IConfiguration Configuration 45 | { 46 | get; 47 | } 48 | 49 | public IndexModel(IConfiguration configuration) 50 | { 51 | Configuration = configuration; 52 | } 53 | 54 | public void OnGet() 55 | { 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /TeamsAppSample.NETCore/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore; 2 | using Microsoft.AspNetCore.Hosting; 3 | 4 | namespace TeamsAppSample.NETCore 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | BuildWebHost(args).Run(); 11 | } 12 | 13 | public static IWebHost BuildWebHost(string[] args) => 14 | WebHost.CreateDefaultBuilder(args) 15 | .UseStartup() 16 | .Build(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /TeamsAppSample.NETCore/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.Bot.Builder.BotFramework; 4 | using Microsoft.Bot.Builder.Core.Extensions; 5 | using Microsoft.Bot.Builder.Integration.AspNet.Core; 6 | using Microsoft.Bot.Builder.TraceExtensions; 7 | using Microsoft.Extensions.Configuration; 8 | using Microsoft.Extensions.DependencyInjection; 9 | using System; 10 | using TeamsAppSample.NETCore.Bot; 11 | 12 | namespace TeamsAppSample.NETCore 13 | { 14 | public class Startup 15 | { 16 | public IConfiguration Configuration 17 | { 18 | get; 19 | } 20 | 21 | // This method gets called by the runtime. Use this method to add services to the container. 22 | // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 23 | public Startup(IHostingEnvironment env) 24 | { 25 | var builder = new ConfigurationBuilder() 26 | .SetBasePath(env.ContentRootPath) 27 | .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) 28 | .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) 29 | .AddEnvironmentVariables(); 30 | 31 | Configuration = builder.Build(); 32 | } 33 | 34 | // This method gets called by the runtime. Use this method to add services to the container. 35 | public void ConfigureServices(IServiceCollection services) 36 | { 37 | services.AddBot(options => 38 | { 39 | options.CredentialProvider = new ConfigurationCredentialProvider(Configuration); 40 | 41 | // The CatchExceptionMiddleware provides a top-level exception handler for your bot. 42 | // Any exceptions thrown by other Middleware, or by your OnTurn method, will be 43 | // caught here. To facillitate debugging, the exception is sent out, via Trace, 44 | // to the emulator. Trace activities are NOT displayed to users, so in addition 45 | // an "Ooops" message is sent. 46 | options.Middleware.Add(new CatchExceptionMiddleware(async (context, exception) => 47 | { 48 | await context.TraceActivity("Bot Exception", exception); 49 | await context.SendActivity("Sorry, it looks like something went wrong!"); 50 | })); 51 | 52 | // The Memory Storage used here is for local bot debugging only. When the bot 53 | // is restarted, anything stored in memory will be gone. 54 | IStorage dataStore = new MemoryStorage(); 55 | 56 | // The File data store, shown here, is suitable for bots that run on 57 | // a single machine and need durable state across application restarts. 58 | // IStorage dataStore = new FileStorage(System.IO.Path.GetTempPath()); 59 | 60 | // For production bots use the Azure Table Store, Azure Blob, or 61 | // Azure CosmosDB storage provides, as seen below. To include any of 62 | // the Azure based storage providers, add the Microsoft.Bot.Builder.Azure 63 | // Nuget package to your solution. That package is found at: 64 | // https://www.nuget.org/packages/Microsoft.Bot.Builder.Azure/ 65 | 66 | // IStorage dataStore = new Microsoft.Bot.Builder.Azure.AzureTableStorage("AzureTablesConnectionString", "TableName"); 67 | // IStorage dataStore = new Microsoft.Bot.Builder.Azure.AzureBlobStorage("AzureBlobConnectionString", "containerName"); 68 | }); 69 | 70 | services.AddMvc(); // Required by Razor pages 71 | } 72 | 73 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 74 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 75 | { 76 | if (env.IsDevelopment()) 77 | { 78 | app.UseDeveloperExceptionPage(); 79 | } 80 | 81 | app.UseDefaultFiles() 82 | .UseStaticFiles() 83 | .UseMvc() // Required by Razor pages 84 | .UseBotFramework(bot => 85 | { 86 | // This is how you can define a custom endpoint in case you're unhappy with 87 | // the default "/api/messages": 88 | bot.BasePath = Configuration["BotBasePath"]; 89 | bot.MessagesPath = Configuration["BotMessagesPath"]; 90 | }); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /TeamsAppSample.NETCore/TeamsAppSample.NETCore.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /TeamsAppSample.NETCore/TeamsManifest/color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tompaana/teams-app-net-core-sample/676ada58c261816214c2a1f07514b394e059a427/TeamsAppSample.NETCore/TeamsManifest/color.png -------------------------------------------------------------------------------- /TeamsAppSample.NETCore/TeamsManifest/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://statics.teams.microsoft.com/sdk/v1.2/manifest/MicrosoftTeams.schema.json", 3 | "manifestVersion": "1.2", 4 | "version": "0.0.1", 5 | "id": "1ac75537-e838-44d9-82e4-6445c49fb81a", 6 | "packageName": "com.underscore.teams.sample.app", 7 | "developer": { 8 | "name": "Tomi", 9 | "websiteUrl": "https://www.github.com/tompaana/teams-app-net-core-sample", 10 | "privacyUrl": "https://www.github.com/tompaana/teams-app-net-core-sample/README.md", 11 | "termsOfUseUrl": "https://www.github.com/tompaana/teams-app-net-core-sample/LICENSE" 12 | }, 13 | "icons": { 14 | "color": "color.png", 15 | "outline": "outline.png" 16 | }, 17 | "name": { 18 | "short": "Teams App Sample", 19 | "full": ".NET Core Teams App Sample" 20 | }, 21 | "description": { 22 | "short": ".NET Core Teams App Sample", 23 | "full": "A sample app for developers demonstrating how to implement a .NET Core based Teams app utilizing Bot Framework v4 preview." 24 | }, 25 | "accentColor": "#00FF00", 26 | "configurableTabs": [ 27 | ], 28 | "staticTabs": [ 29 | { 30 | "entityId": "index", 31 | "name": "Index", 32 | "contentUrl": "https://teamsappsamplenetcore.azurewebsites.net/", 33 | "scopes": [ 34 | "personal" 35 | ] 36 | }, 37 | { 38 | "entityId": "auth", 39 | "name": "Authentication", 40 | "contentUrl": "https://teamsappsamplenetcore.azurewebsites.net/Auth", 41 | "scopes": [ 42 | "personal" 43 | ] 44 | } 45 | ], 46 | "bots": [ 47 | { 48 | "botId": "", 49 | "scopes": [ 50 | "personal", 51 | "team" 52 | ] 53 | } 54 | ], 55 | "permissions": [ 56 | "identity", 57 | "messageTeamMembers" 58 | ], 59 | "validDomains": [ 60 | "*.azurewebsites.net" 61 | ] 62 | } -------------------------------------------------------------------------------- /TeamsAppSample.NETCore/TeamsManifest/outline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tompaana/teams-app-net-core-sample/676ada58c261816214c2a1f07514b394e059a427/TeamsAppSample.NETCore/TeamsManifest/outline.png -------------------------------------------------------------------------------- /TeamsAppSample.NETCore/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "MicrosoftAppId": "", 3 | "MicrosoftAppPassword": "", 4 | "BotBasePath": "/api", 5 | "BotMessagesPath": "/messages", 6 | "AuthClientId": "" 7 | } 8 | -------------------------------------------------------------------------------- /doc/configure-microsoft-teams-channel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tompaana/teams-app-net-core-sample/676ada58c261816214c2a1f07514b394e059a427/doc/configure-microsoft-teams-channel.png -------------------------------------------------------------------------------- /doc/entry-in-apps-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tompaana/teams-app-net-core-sample/676ada58c261816214c2a1f07514b394e059a427/doc/entry-in-apps-list.png -------------------------------------------------------------------------------- /doc/install-app-pop-up-window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tompaana/teams-app-net-core-sample/676ada58c261816214c2a1f07514b394e059a427/doc/install-app-pop-up-window.png -------------------------------------------------------------------------------- /doc/more-apps-menu-item.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tompaana/teams-app-net-core-sample/676ada58c261816214c2a1f07514b394e059a427/doc/more-apps-menu-item.png -------------------------------------------------------------------------------- /doc/sample-app-menu-item.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tompaana/teams-app-net-core-sample/676ada58c261816214c2a1f07514b394e059a427/doc/sample-app-menu-item.png -------------------------------------------------------------------------------- /doc/sample-app-tabs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tompaana/teams-app-net-core-sample/676ada58c261816214c2a1f07514b394e059a427/doc/sample-app-tabs.png -------------------------------------------------------------------------------- /doc/set-up-bot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tompaana/teams-app-net-core-sample/676ada58c261816214c2a1f07514b394e059a427/doc/set-up-bot-1.png -------------------------------------------------------------------------------- /doc/set-up-bot-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tompaana/teams-app-net-core-sample/676ada58c261816214c2a1f07514b394e059a427/doc/set-up-bot-2.png -------------------------------------------------------------------------------- /doc/store-upload-custom-app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tompaana/teams-app-net-core-sample/676ada58c261816214c2a1f07514b394e059a427/doc/store-upload-custom-app.png -------------------------------------------------------------------------------- /doc/team-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tompaana/teams-app-net-core-sample/676ada58c261816214c2a1f07514b394e059a427/doc/team-settings.png -------------------------------------------------------------------------------- /doc/webapp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tompaana/teams-app-net-core-sample/676ada58c261816214c2a1f07514b394e059a427/doc/webapp.png --------------------------------------------------------------------------------