diff --git a/.github/workflows/build-complete-samples.yml b/.github/workflows/build-complete-samples.yml index e7129e8df9..a8405166da 100644 --- a/.github/workflows/build-complete-samples.yml +++ b/.github/workflows/build-complete-samples.yml @@ -270,7 +270,7 @@ jobs: - project_path: 'samples/bot-file-upload/csharp/TeamsFileUpload/TeamsFileUpload.csproj' name: 'bot-file-upload' - version: '6.0.x' + version: '10.0.x' - project_path: 'samples/bot-initiate-thread-in-channel/csharp/TeamsStartNewThreadInTeam/TeamsStartNewThreadInTeam.csproj' name: 'bot-initiate-thread-in-channel' @@ -278,7 +278,7 @@ jobs: - project_path: 'samples/bot-message-reaction/csharp/MessageReaction/MessageReaction.csproj' name: 'bot-message-reaction' - version: '8.0.x' + version: '10.0.x' - project_path: 'samples/bot-suggested-actions/csharp/SuggestedActions/SuggestedActionsBot.csproj' name: 'bot-suggested-actions' diff --git a/samples/bot-file-upload/csharp/M365Agent/.gitignore b/samples/bot-file-upload/csharp/M365Agent/.gitignore new file mode 100644 index 0000000000..c5cae9258c --- /dev/null +++ b/samples/bot-file-upload/csharp/M365Agent/.gitignore @@ -0,0 +1,10 @@ +# TeamsFx files +build +appPackage/build +env/.env.*.user +env/.env.local +appsettings.Development.json +.deployment + +# User-specific files +*.user diff --git a/samples/bot-message-reaction/csharp/M365Agent/M365Agent.ttkproj b/samples/bot-file-upload/csharp/M365Agent/M365Agent.atkproj similarity index 60% rename from samples/bot-message-reaction/csharp/M365Agent/M365Agent.ttkproj rename to samples/bot-file-upload/csharp/M365Agent/M365Agent.atkproj index 85e83e0367..124eb75046 100644 --- a/samples/bot-message-reaction/csharp/M365Agent/M365Agent.ttkproj +++ b/samples/bot-file-upload/csharp/M365Agent/M365Agent.atkproj @@ -1,12 +1,8 @@ - 3df7dbbf-6757-c96e-d6ec-3928332da9e0 + b069b3bd-f6bc-cc40-82ab-3fcc2ea50fdf - - - - diff --git a/samples/bot-file-upload/csharp/M365Agent/M365Agent.ttkproj b/samples/bot-file-upload/csharp/M365Agent/M365Agent.ttkproj deleted file mode 100644 index 2a1b69885b..0000000000 --- a/samples/bot-file-upload/csharp/M365Agent/M365Agent.ttkproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - c7fd0e24-2254-4184-bf12-6c75d2752f28 - - - - - - - - - - \ No newline at end of file diff --git a/samples/bot-file-upload/csharp/M365Agent/aad.manifest.json b/samples/bot-file-upload/csharp/M365Agent/aad.manifest.json deleted file mode 100644 index f635a7689f..0000000000 --- a/samples/bot-file-upload/csharp/M365Agent/aad.manifest.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "id": "${{AAD_APP_OBJECT_ID}}", - "appId": "${{AAD_APP_CLIENT_ID}}", - "displayName": "bot-file-upload-aad", - "signInAudience": "AzureADMyOrg", - "api": { - "requestedAccessTokenVersion": 2 - }, - "info": {}, - "optionalClaims": { - "idToken": [], - "accessToken": [ - { - "name": "idtyp", - "source": null, - "essential": false, - "additionalProperties": [] - } - ], - "saml2Token": [] - }, - "publicClient": {}, - "requiredResourceAccess": [ - { - "resourceAppId": "Microsoft Graph", - "resourceAccess": [ - { - "id": "User.Read", - "type": "Scope" - } - ] - } - ], - "web": { - "implicitGrantSettings": { - "enableIdTokenIssuance": true, - "enableAccessTokenIssuance": true - } - }, - "spa": {} -} diff --git a/samples/bot-file-upload/csharp/M365Agent/appPackage/color.png b/samples/bot-file-upload/csharp/M365Agent/appPackage/color.png new file mode 100644 index 0000000000..01aa37e347 Binary files /dev/null and b/samples/bot-file-upload/csharp/M365Agent/appPackage/color.png differ diff --git a/samples/bot-file-upload/csharp/M365Agent/appPackage/icon-color.png b/samples/bot-file-upload/csharp/M365Agent/appPackage/icon-color.png deleted file mode 100644 index b8cf81afbe..0000000000 Binary files a/samples/bot-file-upload/csharp/M365Agent/appPackage/icon-color.png and /dev/null differ diff --git a/samples/bot-file-upload/csharp/M365Agent/appPackage/icon-outline.png b/samples/bot-file-upload/csharp/M365Agent/appPackage/icon-outline.png deleted file mode 100644 index 2c3bf6fa65..0000000000 Binary files a/samples/bot-file-upload/csharp/M365Agent/appPackage/icon-outline.png and /dev/null differ diff --git a/samples/bot-file-upload/csharp/M365Agent/appPackage/manifest.json b/samples/bot-file-upload/csharp/M365Agent/appPackage/manifest.json index cd56018249..ae164bde7d 100644 --- a/samples/bot-file-upload/csharp/M365Agent/appPackage/manifest.json +++ b/samples/bot-file-upload/csharp/M365Agent/appPackage/manifest.json @@ -1,7 +1,7 @@ { - "$schema": "https://developer.microsoft.com/json-schemas/teams/v1.19/MicrosoftTeams.schema.json", - "manifestVersion": "1.19", - "version": "1.0", + "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.23/MicrosoftTeams.schema.json", + "manifestVersion": "1.23", + "version": "1.0.0", "id": "${{TEAMS_APP_ID}}", "developer": { "name": "Microsoft Corp", @@ -18,13 +18,13 @@ "full": "This bot sample for Teams demonstrates file upload capabilities using Bot Framework v4, enabling users to upload files and view inline images within chats." }, "icons": { - "outline": "icon-outline.png", - "color": "icon-color.png" + "outline": "outline.png", + "color": "color.png" }, "accentColor": "#abcdef", "bots": [ { - "botId": "${{AAD_APP_CLIENT_ID}}", + "botId": "${{BOT_ID}}", "scopes": [ "personal" ], diff --git a/samples/bot-file-upload/csharp/M365Agent/appPackage/outline.png b/samples/bot-file-upload/csharp/M365Agent/appPackage/outline.png new file mode 100644 index 0000000000..f7a4c86447 Binary files /dev/null and b/samples/bot-file-upload/csharp/M365Agent/appPackage/outline.png differ diff --git a/samples/bot-file-upload/csharp/M365Agent/env/.env.dev b/samples/bot-file-upload/csharp/M365Agent/env/.env.dev new file mode 100644 index 0000000000..df4f9da508 --- /dev/null +++ b/samples/bot-file-upload/csharp/M365Agent/env/.env.dev @@ -0,0 +1,15 @@ +# This file includes environment variables that will be committed to git by default. + +# Built-in environment variables +TEAMSFX_ENV=dev +APP_NAME_SUFFIX=dev + +# Updating AZURE_SUBSCRIPTION_ID or AZURE_RESOURCE_GROUP_NAME after provision may also require an update to RESOURCE_SUFFIX, because some services require a globally unique name across subscriptions/resource groups. +AZURE_SUBSCRIPTION_ID= +AZURE_RESOURCE_GROUP_NAME= +RESOURCE_SUFFIX= + +# Generated during provision, you can also add your own variables. +BOT_ID= +TEAMS_APP_ID= +BOT_AZURE_APP_SERVICE_RESOURCE_ID= \ No newline at end of file diff --git a/samples/bot-file-upload/csharp/M365Agent/env/.env.dev.user b/samples/bot-file-upload/csharp/M365Agent/env/.env.dev.user new file mode 100644 index 0000000000..8afeed1c3c --- /dev/null +++ b/samples/bot-file-upload/csharp/M365Agent/env/.env.dev.user @@ -0,0 +1,3 @@ +# This file includes environment variables that will not be committed to git by default. You can set these environment variables in your CI/CD system for your project. + +# Secrets. Keys prefixed with `SECRET_` will be masked in Microsoft 365 Agents Toolkit logs. \ No newline at end of file diff --git a/samples/bot-file-upload/csharp/M365Agent/env/.env.local b/samples/bot-file-upload/csharp/M365Agent/env/.env.local index 893eabd06f..86dfacc35a 100644 --- a/samples/bot-file-upload/csharp/M365Agent/env/.env.local +++ b/samples/bot-file-upload/csharp/M365Agent/env/.env.local @@ -7,20 +7,8 @@ APP_NAME_SUFFIX=local # Generated during provision, you can also add your own variables. BOT_ID= TEAMS_APP_ID= -RESOURCE_SUFFIX= -AAD_APP_CLIENT_ID= -AAD_APP_OBJECT_ID= -AAD_APP_TENANT_ID= -AAD_APP_OAUTH_AUTHORITY= -AAD_APP_OAUTH_AUTHORITY_HOST= TEAMS_APP_TENANT_ID= -MICROSOFT_APP_TYPE= -MICROSOFT_APP_TENANT_ID= -AZURE_SUBSCRIPTION_ID= -AZURE_RESOURCE_GROUP_NAME= - - - +BOT_OBJECT_ID= TEAMSFX_M365_USER_NAME= BOT_ENDPOINT= diff --git a/samples/bot-file-upload/csharp/M365Agent/env/.env.local.user b/samples/bot-file-upload/csharp/M365Agent/env/.env.local.user new file mode 100644 index 0000000000..4d30df6f33 --- /dev/null +++ b/samples/bot-file-upload/csharp/M365Agent/env/.env.local.user @@ -0,0 +1,5 @@ +# This file includes environment variables that will not be committed to git by default. You can set these environment variables in your CI/CD system for your project. + +# Secrets. Keys prefixed with `SECRET_` will be masked in Microsoft 365 Agents Toolkit logs. +SECRET_BOT_PASSWORD= +TEAMS_APP_UPDATE_TIME= \ No newline at end of file diff --git a/samples/bot-file-upload/csharp/M365Agent/infra/azure.bicep b/samples/bot-file-upload/csharp/M365Agent/infra/azure.bicep index c3ce051b3d..658e412a21 100644 --- a/samples/bot-file-upload/csharp/M365Agent/infra/azure.bicep +++ b/samples/bot-file-upload/csharp/M365Agent/infra/azure.bicep @@ -3,42 +3,84 @@ @description('Used to generate names for all resources in this file') param resourceBaseName string -@description('Required when create Azure Bot service') -param botAadAppClientId string - -param botAppDomain string +param webAppSKU string @maxLength(42) param botDisplayName string -param botServiceName string = resourceBaseName -param botServiceSku string = 'F0' -param microsoftAppType string -param microsoftAppTenantId string +param serverfarmsName string = resourceBaseName +param webAppName string = resourceBaseName +param identityName string = resourceBaseName +param location string = resourceGroup().location -// Register your web service as a bot with the Bot Framework -resource botService 'Microsoft.BotService/botServices@2021-03-01' = { - kind: 'azurebot' - location: 'global' - name: botServiceName - properties: { - displayName: botDisplayName - endpoint: 'https://${botAppDomain}/api/messages' - msaAppId: botAadAppClientId - msaAppType: microsoftAppType - msaAppTenantId: microsoftAppType == 'SingleTenant' ? microsoftAppTenantId : '' - } +resource identity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { + location: location + name: identityName +} + +// Compute resources for your Web App +resource serverfarm 'Microsoft.Web/serverfarms@2021-02-01' = { + kind: 'app' + location: location + name: serverfarmsName sku: { - name: botServiceSku + name: webAppSKU } } -// Connect the bot service to Microsoft Teams -resource botServiceMsTeamsChannel 'Microsoft.BotService/botServices/channels@2021-03-01' = { - parent: botService - location: 'global' - name: 'MsTeamsChannel' +// Web App that hosts your bot +resource webApp 'Microsoft.Web/sites@2021-02-01' = { + kind: 'app' + location: location + name: webAppName properties: { - channelName: 'MsTeamsChannel' + serverFarmId: serverfarm.id + httpsOnly: true + siteConfig: { + appSettings: [ + { + name: 'WEBSITE_RUN_FROM_PACKAGE' + value: '1' + } + { + name: 'Teams__ClientId' + value: identity.properties.clientId + } + { + name: 'Teams__TenantId' + value: identity.properties.tenantId + } + { + name: 'Teams__BotType' + value: 'UserAssignedMsi' + } + ] + ftpsState: 'FtpsOnly' + } + } + identity: { + type: 'UserAssigned' + userAssignedIdentities: { + '${identity.id}': {} + } + } +} + +// Register your web service as a bot with the Bot Framework +module azureBotRegistration './botRegistration/azurebot.bicep' = { + name: 'Azure-Bot-registration' + params: { + resourceBaseName: resourceBaseName + identityClientId: identity.properties.clientId + identityResourceId: identity.id + identityTenantId: identity.properties.tenantId + botAppDomain: webApp.properties.defaultHostName + botDisplayName: botDisplayName } } + +// The output will be persisted in .env.{envName}. Visit https://aka.ms/teamsfx-actions/arm-deploy for more details. +output BOT_AZURE_APP_SERVICE_RESOURCE_ID string = webApp.id +output BOT_DOMAIN string = webApp.properties.defaultHostName +output BOT_ID string = identity.properties.clientId +output BOT_TENANT_ID string = identity.properties.tenantId diff --git a/samples/bot-file-upload/csharp/M365Agent/infra/azure.parameters.json b/samples/bot-file-upload/csharp/M365Agent/infra/azure.parameters.json index d8454ee752..93a3dce412 100644 --- a/samples/bot-file-upload/csharp/M365Agent/infra/azure.parameters.json +++ b/samples/bot-file-upload/csharp/M365Agent/infra/azure.parameters.json @@ -1,24 +1,15 @@ { - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "resourceBaseName": { - "value": "bot${{RESOURCE_SUFFIX}}" - }, - "botAadAppClientId": { - "value": "${{AAD_APP_CLIENT_ID}}" - }, - "botAppDomain": { - "value": "${{BOT_DOMAIN}}" - }, - "botDisplayName": { - "value": "TestBot" - }, - "microsoftAppType": { - "value": "${{MICROSOFT_APP_TYPE}}" - }, - "microsoftAppTenantId": { - "value": "${{MICROSOFT_APP_TENANT_ID}}" + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "resourceBaseName": { + "value": "bot${{RESOURCE_SUFFIX}}" + }, + "webAppSKU": { + "value": "B1" + }, + "botDisplayName": { + "value": "TeamsFileUpload" + } } - } -} \ No newline at end of file + } \ No newline at end of file diff --git a/samples/bot-file-upload/csharp/M365Agent/infra/botRegistration/azurebot.bicep b/samples/bot-file-upload/csharp/M365Agent/infra/botRegistration/azurebot.bicep new file mode 100644 index 0000000000..a5a27b8fe4 --- /dev/null +++ b/samples/bot-file-upload/csharp/M365Agent/infra/botRegistration/azurebot.bicep @@ -0,0 +1,42 @@ +@maxLength(20) +@minLength(4) +@description('Used to generate names for all resources in this file') +param resourceBaseName string + +@maxLength(42) +param botDisplayName string + +param botServiceName string = resourceBaseName +param botServiceSku string = 'F0' +param identityResourceId string +param identityClientId string +param identityTenantId string +param botAppDomain string + +// Register your web service as a bot with the Bot Framework +resource botService 'Microsoft.BotService/botServices@2021-03-01' = { + kind: 'azurebot' + location: 'global' + name: botServiceName + properties: { + displayName: botDisplayName + endpoint: 'https://${botAppDomain}/api/messages' + msaAppId: identityClientId + msaAppMSIResourceId: identityResourceId + msaAppTenantId:identityTenantId + msaAppType:'UserAssignedMSI' + } + sku: { + name: botServiceSku + } +} + +// Connect the bot service to Microsoft Teams +resource botServiceMsTeamsChannel 'Microsoft.BotService/botServices/channels@2021-03-01' = { + parent: botService + location: 'global' + name: 'MsTeamsChannel' + properties: { + channelName: 'MsTeamsChannel' + } +} diff --git a/samples/bot-file-upload/csharp/M365Agent/infra/botRegistration/readme.md b/samples/bot-file-upload/csharp/M365Agent/infra/botRegistration/readme.md new file mode 100644 index 0000000000..d5416243cd --- /dev/null +++ b/samples/bot-file-upload/csharp/M365Agent/infra/botRegistration/readme.md @@ -0,0 +1 @@ +The `azurebot.bicep` module is provided to help you create Azure Bot service when you don't use Azure to host your app. If you use Azure as infrastrcture for your app, `azure.bicep` under infra folder already leverages this module to create Azure Bot service for you. You don't need to deploy `azurebot.bicep` again. \ No newline at end of file diff --git a/samples/bot-file-upload/csharp/M365Agent/launchSettings.json b/samples/bot-file-upload/csharp/M365Agent/launchSettings.json index d6491ef52c..2af8ce7a8a 100644 --- a/samples/bot-file-upload/csharp/M365Agent/launchSettings.json +++ b/samples/bot-file-upload/csharp/M365Agent/launchSettings.json @@ -1,15 +1,25 @@ { - "profiles": { - // Debug project within Teams - "Microsoft Teams (browser)": { - "commandName": "Project", - "launchUrl": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&appTenantId=${{TEAMS_APP_TENANT_ID}}&login_hint=${{TEAMSFX_M365_USER_NAME}}" - }, - // Launch project within Teams without prepare app dependencies - "Microsoft Teams (browser) (skip update app)": { - "commandName": "Project", - "environmentVariables": { "UPDATE_TEAMS_APP": "false" }, - "launchUrl": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&appTenantId=${{TEAMS_APP_TENANT_ID}}&login_hint=${{TEAMSFX_M365_USER_NAME}}" - } - } + "profiles": { + // Launch project within Microsoft 365 Agents Playground + "Microsoft 365 Agents Playground (browser)": { + "commandName": "Project", + "environmentVariables": { + "UPDATE_TEAMS_APP": "false", + "M365_AGENTS_PLAYGROUND_TARGET_SDK": "teams-ai-v2-dotnet" + }, + "launchTestTool": true, + "launchUrl": "http://localhost:56150", + }, + // Launch project within Teams + "Microsoft Teams (browser)": { + "commandName": "Project", + "launchUrl": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&appTenantId=${{TEAMS_APP_TENANT_ID}}&login_hint=${{TEAMSFX_M365_USER_NAME}}", + }, + // Launch project within Teams without prepare app dependencies + "Microsoft Teams (browser) (skip update app)": { + "commandName": "Project", + "environmentVariables": { "UPDATE_TEAMS_APP": "false" }, + "launchUrl": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&appTenantId=${{TEAMS_APP_TENANT_ID}}&login_hint=${{TEAMSFX_M365_USER_NAME}}" + }, + } } \ No newline at end of file diff --git a/samples/bot-file-upload/csharp/M365Agent/m365agents.local.yml b/samples/bot-file-upload/csharp/M365Agent/m365agents.local.yml index 8538a2b75f..f1ddaffa4a 100644 --- a/samples/bot-file-upload/csharp/M365Agent/m365agents.local.yml +++ b/samples/bot-file-upload/csharp/M365Agent/m365agents.local.yml @@ -1,72 +1,60 @@ -# yaml-language-server: $schema=https://aka.ms/teams-toolkit/v1.8/yaml.schema.json +# yaml-language-server: $schema=https://aka.ms/m365-agents-toolkits/v1.11/yaml.schema.json # Visit https://aka.ms/teamsfx-v5.0-guide for details on this file # Visit https://aka.ms/teamsfx-actions for details on actions -version: v1.8 - -additionalMetadata: - sampleTag: Microsoft-Teams-Samples:bot-file-upload-csharp +version: v1.11 provision: - - uses: aadApp/create # Creates a new Azure Active Directory (AAD) app to authenticate users if the environment variable that stores clientId is empty - with: - name: bot-file-upload-aad # Note: when you run aadApp/update, the AAD app name will be updated based on the definition in manifest. If you don't want to change the name, make sure the name in AAD manifest is the same with the name defined here. - generateClientSecret: true # If the value is false, the action will not generate client secret for you - signInAudience: "AzureADMultipleOrgs" # Multitenant - writeToEnvironmentFile: - # Write the information of created resources into environment file for the specified environment variable(s). - clientId: AAD_APP_CLIENT_ID - clientSecret: SECRET_AAD_APP_CLIENT_SECRET # Environment variable that starts with `SECRET_` will be stored to the .env.{envName}.user environment file - objectId: AAD_APP_OBJECT_ID - tenantId: AAD_APP_TENANT_ID - authority: AAD_APP_OAUTH_AUTHORITY - authorityHost: AAD_APP_OAUTH_AUTHORITY_HOST - # Creates a Teams app - uses: teamsApp/create with: # Teams app name - name: bot-file-upload${{APP_NAME_SUFFIX}} + name: TeamsFileUpload${{APP_NAME_SUFFIX}} # Write the information of created resources into environment file for # the specified environment variable(s). writeToEnvironmentFile: teamsAppId: TEAMS_APP_ID - - uses: script + # Create or reuse an existing Microsoft Entra application for bot. + - uses: aadApp/create with: - run: echo "::set-teamsfx-env MICROSOFT_APP_TYPE=SingleTenant"; echo - "::set-teamsfx-env MICROSOFT_APP_TENANT_ID=${{AAD_APP_TENANT_ID}}"; + # The Microsoft Entra application's display name + name: TeamsFileUpload${{APP_NAME_SUFFIX}} + generateClientSecret: true + generateServicePrincipal: true + signInAudience: AzureADMultipleOrgs + writeToEnvironmentFile: + # The Microsoft Entra application's client id created for bot. + clientId: BOT_ID + # The Microsoft Entra application's client secret created for bot. + clientSecret: SECRET_BOT_PASSWORD + # The Microsoft Entra application's object id created for bot. + objectId: BOT_OBJECT_ID # Generate runtime appsettings to JSON file - uses: file/createOrUpdateJsonFile with: - target: ../TeamsFileUpload/appsettings.json + target: ../TeamsFileUpload/appsettings.Development.json content: - MicrosoftAppId: ${{AAD_APP_CLIENT_ID}} - MicrosoftAppPassword: ${{SECRET_AAD_APP_CLIENT_SECRET}} - MicrosoftAppType: ${{MICROSOFT_APP_TYPE}} - MicrosoftAppTenantId: ${{MICROSOFT_APP_TENANT_ID}} - - - uses: arm/deploy # Deploy given ARM templates parallelly. - with: - subscriptionId: ${{AZURE_SUBSCRIPTION_ID}} # The AZURE_SUBSCRIPTION_ID is a built-in environment variable. TeamsFx will ask you select one subscription if its value is empty. You're free to reference other environment varialbe here, but TeamsFx will not ask you to select subscription if it's empty in this case. - resourceGroupName: ${{AZURE_RESOURCE_GROUP_NAME}} # The AZURE_RESOURCE_GROUP_NAME is a built-in environment variable. TeamsFx will ask you to select or create one resource group if its value is empty. You're free to reference other environment varialbe here, but TeamsFx will not ask you to select or create resource grouop if it's empty in this case. - templates: - - path: ./infra/azure.bicep - parameters: ./infra/azure.parameters.json - deploymentName: Create-resources-for-bot - bicepCliVersion: v0.9.1 # Microsoft 365 Agents Toolkit will download this bicep CLI version from github for you, will use bicep CLI in PATH if you remove this config. + Teams: + ClientId: ${{BOT_ID}} + ClientSecret: ${{SECRET_BOT_PASSWORD}} + TenantId: ${{TEAMS_APP_TENANT_ID}} - - uses: aadApp/update # Apply the AAD manifest to an existing AAD app. Will use the object id in manifest file to determine which AAD app to update. + # Create or update the bot registration on dev.botframework.com + - uses: botFramework/create with: - manifestPath: ./aad.manifest.json # Relative path to teamsfx folder. Environment variables in manifest will be replaced before apply to AAD app - outputFilePath: ./build/aad.manifest.${{TEAMSFX_ENV}}.json + botId: ${{BOT_ID}} + name: TeamsFileUpload + messagingEndpoint: ${{BOT_ENDPOINT}}/api/messages + description: "" + channels: + - name: msteams # Validate using manifest schema - uses: teamsApp/validateManifest with: # Path to manifest template manifestPath: ./appPackage/manifest.json - # Build Teams app package with latest env value - uses: teamsApp/zipAppPackage with: diff --git a/samples/bot-file-upload/csharp/M365Agent/m365agents.yml b/samples/bot-file-upload/csharp/M365Agent/m365agents.yml index e9fc66e1e4..fec9a618b4 100644 --- a/samples/bot-file-upload/csharp/M365Agent/m365agents.yml +++ b/samples/bot-file-upload/csharp/M365Agent/m365agents.yml @@ -1,9 +1,88 @@ -# yaml-language-server: $schema=https://aka.ms/teams-toolkit/v1.8/yaml.schema.json +# yaml-language-server: $schema=https://aka.ms/m365-agents-toolkits/v1.9/yaml.schema.json # Visit https://aka.ms/teamsfx-v5.0-guide for details on this file # Visit https://aka.ms/teamsfx-actions for details on actions -version: v1.8 - -additionalMetadata: - sampleTag: Microsoft-Teams-Samples:bot-file-upload-csharp +version: v1.9 environmentFolderPath: ./env + +# Triggered when 'teamsapp provision' is executed +provision: + # Creates a Teams app + - uses: teamsApp/create + with: + # Teams app name + name: TeamsFileUpload${{APP_NAME_SUFFIX}} + # Write the information of created resources into environment file for + # the specified environment variable(s). + writeToEnvironmentFile: + teamsAppId: TEAMS_APP_ID + + - uses: arm/deploy # Deploy given ARM templates parallelly. + with: + # AZURE_SUBSCRIPTION_ID is a built-in environment variable, + # if its value is empty, TeamsFx will prompt you to select a subscription. + # Referencing other environment variables with empty values + # will skip the subscription selection prompt. + subscriptionId: ${{AZURE_SUBSCRIPTION_ID}} + # AZURE_RESOURCE_GROUP_NAME is a built-in environment variable, + # if its value is empty, TeamsFx will prompt you to select or create one + # resource group. + # Referencing other environment variables with empty values + # will skip the resource group selection prompt. + resourceGroupName: ${{AZURE_RESOURCE_GROUP_NAME}} + templates: + - path: ./infra/azure.bicep # Relative path to this file + # Relative path to this yaml file. + # Placeholders will be replaced with corresponding environment + # variable before ARM deployment. + parameters: ./infra/azure.parameters.json + # Required when deploying ARM template + deploymentName: Create-resources-for-bot + # Microsoft 365 Agents Toolkit will download this bicep CLI version from github for you, + # will use bicep CLI in PATH if you remove this config. + bicepCliVersion: v0.9.1 + + # Validate using manifest schema + - uses: teamsApp/validateManifest + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + # Build Teams app package with latest env value + - uses: teamsApp/zipAppPackage + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + outputFolder: ./appPackage/build + # Validate app package using validation rules + - uses: teamsApp/validateAppPackage + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + # Apply the Teams app manifest to an existing Teams app in + # Developer Portal. + # Will use the app id in manifest file to determine which Teams app to update. + - uses: teamsApp/update + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + +# Triggered when 'teamsapp deploy' is executed +deploy: + - uses: cli/runDotnetCommand + with: + args: publish --configuration Release --runtime win-x86 --self-contained + TeamsFileUpload.csproj + workingDirectory: ../TeamsFileUpload + # Deploy your application to Azure App Service using the zip deploy feature. + # For additional details, refer to https://aka.ms/zip-deploy-to-app-services. + - uses: azureAppService/zipDeploy + with: + # Deploy base folder + artifactFolder: bin/Release/net10.0/win-x86/publish + # The resource id of the cloud resource to be deployed to. + # This key will be generated by arm/deploy action automatically. + # You can replace it with your existing Azure Resource id + # or add it to your environment variable file. + resourceId: ${{BOT_AZURE_APP_SERVICE_RESOURCE_ID}} + workingDirectory: ../TeamsFileUpload \ No newline at end of file diff --git a/samples/bot-file-upload/csharp/README.md b/samples/bot-file-upload/csharp/README.md index e4e19a7696..cc3c2ad081 100644 --- a/samples/bot-file-upload/csharp/README.md +++ b/samples/bot-file-upload/csharp/README.md @@ -1,6 +1,6 @@ -F--- +--- page_type: sample -description: This bot sample for Teams demonstrates file upload capabilities using Bot Framework v4, enabling users to upload files and view inline images within chats. +description: This bot sample for Teams demonstrates file upload capabilities using Teams AI Library (Teams SDK 2.0), enabling users to upload files and view inline images within chats. products: - office-teams - office @@ -14,16 +14,17 @@ urlFragment: officedev-microsoft-teams-samples-bot-file-upload-csharp --- # Teams File Upload Bot -This sample demonstrates how to upload files in Microsoft Teams using a bot built with Bot Framework v4. Users can send files as attachments or inline images directly within a chat, and the bot can handle, retrieve, and process these files effectively. The bot also illustrates interaction with adaptive cards and supports file uploads through various methods, making it versatile for file management in Teams. +This sample demonstrates how to upload files in Microsoft Teams using a bot built with the Teams AI Library (Teams SDK 2.0). Users can send files as attachments or inline images directly within a chat, and the bot can handle, retrieve, and process these files effectively. The bot also illustrates interaction with adaptive cards and supports file uploads through various methods, making it versatile for file management in Teams. ## Included Features * Bots * Adaptive Cards +* File Upload/Download +* Teams AI Library (SDK 2.0) ## Interaction with bot ![bot-file-upload ](Images/bot-file-upload.gif) - ## Try it yourself - experience the App in your Microsoft Teams client Please find below demo manifest which is deployed on Microsoft Azure and you can try it yourself by uploading the app manifest (.zip file link below) to your teams and/or as a personal app. (Uploading must be enabled for your tenant, [see steps here](https://docs.microsoft.com/microsoftteams/platform/concepts/build-and-test/prepare-your-o365-tenant#enable-custom-teams-apps-and-turn-on-custom-app-uploading)). @@ -32,7 +33,7 @@ Please find below demo manifest which is deployed on Microsoft Azure and you can ## Prerequisites - Microsoft Teams is installed and you have an account -- [.NET SDK](https://dotnet.microsoft.com/download) version 6.0 +- [.NET SDK](https://dotnet.microsoft.com/download) version 10.0 or later - [dev tunnel](https://learn.microsoft.com/en-us/azure/developer/dev-tunnels/get-started?tabs=windows) or [ngrok](https://ngrok.com/) latest version or equivalent tunnelling solution - [Microsoft 365 Agents Toolkit for Visual Studio](https://learn.microsoft.com/en-us/microsoftteams/platform/toolkit/toolkit-v4/install-teams-toolkit-vs?pivots=visual-studio-v17-7) @@ -50,51 +51,50 @@ The simplest way to run this sample in Teams is to use Microsoft 365 Agents Tool 1. In the opened web browser, select Add button to install the app in Teams > If you do not have permission to upload custom apps (uploading), Microsoft 365 Agents Toolkit will recommend creating and using a Microsoft 365 Developer Program account - a free program to get your own dev environment sandbox that includes Teams. -## Setup - -> Note these instructions are for running the sample on your local machine, the tunnelling solution is required because -the Teams service needs to call into the bot. +### Register your app with Azure AD. -1) Run ngrok - point to port 3978 - - ```bash - ngrok http 3978 --host-header="localhost:3978" - ``` - - Alternatively, you can also use the `dev tunnels`. Please follow [Create and host a dev tunnel](https://learn.microsoft.com/en-us/azure/developer/dev-tunnels/get-started?tabs=windows) and host the tunnel with anonymous user access command as shown below: - - ```bash - devtunnel host -p 3978 --allow-anonymous - ``` - -1) Register a new application in the [Microsoft Entra ID – App Registrations](https://go.microsoft.com/fwlink/?linkid=2083908) portal. - - A) Select **New Registration** and on the *register an application page*, set following values: + 1. Register a new application in the [Microsoft Entra ID – App Registrations](https://go.microsoft.com/fwlink/?linkid=2083908) portal. + 2. Select **New Registration** and on the *register an application page*, set following values: * Set **name** to your app name. * Choose the **supported account types** (any account type will work) * Leave **Redirect URI** empty. * Choose **Register**. - B) On the overview page, copy and save the **Application (client) ID, Directory (tenant) ID**. You'll need those later when updating your Teams application manifest and in the appsettings.json. - C) Navigate to **API Permissions**, and make sure to add the following permissions: + 3. On the overview page, copy and save the **Application (client) ID, Directory (tenant) ID**. You'll need those later when updating your Teams application manifest and in the appsettings.json. + 4. Navigate to **API Permissions**, and make sure to add the follow permissions: Select Add a permission * Select Add a permission * Select Microsoft Graph -\> Delegated permissions. * `User.Read` (enabled by default) * Click on Add permissions. Please make sure to grant the admin consent for the required permissions. - - -1) Setup for Bot + +### Setup for Bot In Azure portal, create a [Azure Bot resource](https://docs.microsoft.com/azure/bot-service/bot-service-quickstart-registration). - For bot handle, make up a name. - Select "Use existing app registration" (Create the app registration in Microsoft Entra ID beforehand.) - - Choose "Accounts in any organizational directory (Any Azure AD directory - Multitenant)" in Authentication section in your App Registration to run this sample smoothly. - __*If you don't have an Azure account*__ create an [Azure free account here](https://azure.microsoft.com/free/) - + In the new Azure Bot resource in the Portal, - Ensure that you've [enabled the Teams Channel](https://learn.microsoft.com/azure/bot-service/channel-connect-teams?view=azure-bot-service-4.0) - In Settings/Configuration/Messaging endpoint, enter the current `https` URL you were given by running the tunneling application. Append with the path `/api/messages` +## Setup + +> Note these instructions are for running the sample on your local machine, the tunnelling solution is required because +the Teams service needs to call into the bot. + +1) Run ngrok - point to port 3978 + + ```bash + ngrok http 3978 --host-header="localhost:3978" + ``` + + Alternatively, you can also use the `dev tunnels`. Please follow [Create and host a dev tunnel](https://learn.microsoft.com/en-us/azure/developer/dev-tunnels/get-started?tabs=windows) and host the tunnel with anonymous user access command as shown below: + + ```bash + devtunnel host -p 3978 --allow-anonymous + ``` + 1) Clone the repository ```bash @@ -105,23 +105,34 @@ the Teams service needs to call into the bot. - Launch Visual Studio - File -> Open -> Project/Solution - Navigate to `samples/bot-file-upload/csharp` folder - - Select `TeamsFileUpload.csproj` or `TeamsFileUpload.sln`file - - -1) Update the `appsettings.json` configuration for the bot to use the MicrosoftAppId, MicrosoftAppPassword, MicrosoftAppTenantId generated in Step 2 (App Registration creation). (Note the App Password is referred to as the "client secret" in the azure portal and you can always create a new client secret anytime.) - - Also, set MicrosoftAppType in the `appsettings.json`. (**Allowed values are: MultiTenant(default), SingleTenant, UserAssignedMSI**) + - Select `TeamsFileUpload/TeamsFileUpload.csproj` or `TeamsFileUpload.slnx` file + + +1) Update the `appsettings.Development.json` configuration with your bot credentials: + ```json + { + "Teams": { + "ClientId": "", + "ClientSecret": "", + "BotType": "", + "TenantId": "" + } + } + ``` + - Set **ClientId** to the Application (client) ID from the App Registration above + - Set **ClientSecret** to the client secret value (created in the Azure portal under Certificates & secrets) + - Set **TenantId** to the Directory (tenant) ID from the App Registration above + - Set **BotType** to one of: `MultiTenant`, `SingleTenant`, or `UserAssignedMsi` (use `UserAssignedMsi` for managed identity authentication) 1) Run your bot, either from Visual Studio with `F5` or using `dotnet run` in the appropriate folder. 1) __*This step is specific to Teams.*__ - - **Edit** the `manifest.json` contained in the `appPackage` folder to replace your Microsoft App Id (that was created when you registered your bot earlier) *everywhere* you see the place holder string `<>` (depending on the scenario the Microsoft App Id may occur multiple times in the `manifest.json`) + - **Edit** the `manifest.json` contained in the `M365Agent/appPackage` folder to replace your Microsoft App Id (that was created when you registered your bot earlier) *everywhere* you see the place holder string `<>` (depending on the scenario the Microsoft App Id may occur multiple times in the `manifest.json`) - **Edit** the `manifest.json` for `validDomains` with base Url domain. E.g. if you are using ngrok it would be `https://1234.ngrok-free.app` then your domain-name will be `1234.ngrok-free.app` and if you are using dev tunnels then your domain will be like: `12345.devtunnels.ms`. - - **Zip** up the contents of the `appPackage` folder to create a `manifest.zip` (Make sure that zip file does not contains any subfolder otherwise you will get error while uploading your .zip package) + - **Zip** up the contents of the `M365Agent/appPackage` folder to create a `manifest.zip` (Make sure that zip file does not contain any subfolder otherwise you will get error while uploading your .zip package) - **Upload** the `manifest.zip` to Teams (In Teams Apps/Manage your apps click "Upload an app". Browse to and Open the .zip file. At the next dialog, click the Add button.) - Add the app to personal scope (Supported app scope) -**Note**: If you are facing any issue in your app, please uncomment [this](https://github.com/OfficeDev/Microsoft-Teams-Samples/blob/main/samples/bot-file-upload/csharp/AdapterWithErrorHandler.cs#L25) line and put your debugger for local debug. - ## Running the sample > Note this `manifest.json` specified that the bot will be installed in "personal" scope which is why you immediately entered a one on one chat conversation with the bot. Please refer to Teams documentation for more details. @@ -147,11 +158,11 @@ To learn more about deploying a bot to Azure, see [Deploy your bot to Azure](htt ## Further reading +- [Teams AI Library Overview](https://learn.microsoft.com/en-us/microsoftteams/platform/bots/how-to/teams%20conversational%20ai/teams-conversation-ai-overview) - [Upload Files Using Bots](https://learn.microsoft.com/en-us/microsoftteams/platform/bots/how-to/bots-filesv4) -- [Bot Framework Documentation](https://docs.botframework.com) -- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) -- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) -- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) +- [Teams AI Library for .NET](https://github.com/microsoft/teams-ai) +- [Build bots for Teams](https://learn.microsoft.com/en-us/microsoftteams/platform/bots/what-are-bots) +- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/) \ No newline at end of file diff --git a/samples/bot-file-upload/csharp/TeamsFileUpload.sln b/samples/bot-file-upload/csharp/TeamsFileUpload.sln deleted file mode 100644 index 34d8e86e15..0000000000 --- a/samples/bot-file-upload/csharp/TeamsFileUpload.sln +++ /dev/null @@ -1,39 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.13.35617.110 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TeamsFileUpload", "TeamsFileUpload\TeamsFileUpload.csproj", "{BABDB690-2880-A864-65A1-5034C37CE0A8}" -EndProject -Project("{A9E3F50B-275E-4AF7-ADCE-8BE12D41E305}") = "M365Agent", "M365Agent\M365Agent.ttkproj", "{C7FD0E24-2254-4184-BF12-6C75D2752F28}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8EC462FD-D22E-90A8-E5CE-7E832BA40C5D}" - ProjectSection(SolutionItems) = preProject - TeamsFileUpload.slnLaunch.user = TeamsFileUpload.slnLaunch.user - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {BABDB690-2880-A864-65A1-5034C37CE0A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BABDB690-2880-A864-65A1-5034C37CE0A8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BABDB690-2880-A864-65A1-5034C37CE0A8}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {BABDB690-2880-A864-65A1-5034C37CE0A8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BABDB690-2880-A864-65A1-5034C37CE0A8}.Release|Any CPU.Build.0 = Release|Any CPU - {BABDB690-2880-A864-65A1-5034C37CE0A8}.Release|Any CPU.Deploy.0 = Release|Any CPU - {C7FD0E24-2254-4184-BF12-6C75D2752F28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C7FD0E24-2254-4184-BF12-6C75D2752F28}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C7FD0E24-2254-4184-BF12-6C75D2752F28}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {C7FD0E24-2254-4184-BF12-6C75D2752F28}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C7FD0E24-2254-4184-BF12-6C75D2752F28}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {AA59FF1C-9748-4484-B71F-14E92D37F36F} - EndGlobalSection -EndGlobal diff --git a/samples/bot-file-upload/csharp/TeamsFileUpload.slnLaunch.user b/samples/bot-file-upload/csharp/TeamsFileUpload.slnLaunch.user index e051fa3c6c..3c5a4a0a97 100644 --- a/samples/bot-file-upload/csharp/TeamsFileUpload.slnLaunch.user +++ b/samples/bot-file-upload/csharp/TeamsFileUpload.slnLaunch.user @@ -1,14 +1,33 @@ [ + { + "Name": "Microsoft 365 Agents Playground (browser)", + "Projects": [ + { + "Path": "M365Agent\\M365Agent.atkproj", + "Name": "M365Agent\\M365Agent.atkproj", + "Action": "StartWithoutDebugging", + "DebugTarget": "Microsoft 365 Agents Playground (browser)" + }, + { + "Path": "TeamsFileUpload\\TeamsFileUpload.csproj", + "Name": "TeamsFileUpload\\TeamsFileUpload.csproj", + "Action": "Start", + "DebugTarget": "Microsoft 365 Agents Playground" + } + ] + }, { "Name": "Microsoft Teams (browser)", "Projects": [ { - "Path": "M365Agent\\M365Agent.ttkproj", + "Path": "M365Agent\\M365Agent.atkproj", + "Name": "M365Agent\\M365Agent.atkproj", "Action": "StartWithoutDebugging", "DebugTarget": "Microsoft Teams (browser)" }, { "Path": "TeamsFileUpload\\TeamsFileUpload.csproj", + "Name": "TeamsFileUpload\\TeamsFileUpload.csproj", "Action": "Start", "DebugTarget": "Start Project" } @@ -17,15 +36,17 @@ { "Name": "Microsoft Teams (browser) (skip update app)", "Projects": [ + { + "Path": "M365Agent\\M365Agent.atkproj", + "Name": "M365Agent\\M365Agent.atkproj", + "Action": "StartWithoutDebugging", + "DebugTarget": "Microsoft Teams (browser) (skip update app)" + }, { "Path": "TeamsFileUpload\\TeamsFileUpload.csproj", + "Name": "TeamsFileUpload\\TeamsFileUpload.csproj", "Action": "Start", "DebugTarget": "Start Project" - }, - { - "Path": "M365Agent\\M365Agent.ttkproj", - "Action": "StartWithoutDebugging", - "DebugTarget": "Microsoft Teams (browser) (skip update app)" } ] } diff --git a/samples/bot-file-upload/csharp/TeamsFileUpload.slnx b/samples/bot-file-upload/csharp/TeamsFileUpload.slnx new file mode 100644 index 0000000000..7eb6f73c87 --- /dev/null +++ b/samples/bot-file-upload/csharp/TeamsFileUpload.slnx @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/samples/bot-file-upload/csharp/TeamsFileUpload/.gitignore b/samples/bot-file-upload/csharp/TeamsFileUpload/.gitignore index 7466c01f9c..77c7154916 100644 --- a/samples/bot-file-upload/csharp/TeamsFileUpload/.gitignore +++ b/samples/bot-file-upload/csharp/TeamsFileUpload/.gitignore @@ -5,6 +5,7 @@ env/.env.*.user env/.env.local appsettings.Development.json .deployment +appsettings.Playground.json # User-specific files *.user @@ -22,4 +23,8 @@ bld/ [Ll]og/ # Notification local store -.notification.localstore.json \ No newline at end of file +.notification.localstore.json +.notification.playgroundstore.json + +# devTools +devTools/ \ No newline at end of file diff --git a/samples/bot-file-upload/csharp/TeamsFileUpload/AdapterWithErrorHandler.cs b/samples/bot-file-upload/csharp/TeamsFileUpload/AdapterWithErrorHandler.cs deleted file mode 100644 index 3fc4ae8b0e..0000000000 --- a/samples/bot-file-upload/csharp/TeamsFileUpload/AdapterWithErrorHandler.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Microsoft.Bot.Builder.Integration.AspNet.Core; -using Microsoft.Bot.Builder.TraceExtensions; -using Microsoft.Bot.Connector.Authentication; -using Microsoft.Extensions.Logging; - -namespace Microsoft.BotBuilderSamples -{ - public class AdapterWithErrorHandler : CloudAdapter - { - public AdapterWithErrorHandler(BotFrameworkAuthentication auth, ILogger logger) - : base(auth, logger) - { - OnTurnError = async (turnContext, exception) => - { - // Log any unhandled exception from the application. - // NOTE: In production environments, consider logging to Azure Application Insights - // for monitoring and diagnostics. Visit https://aka.ms/bottelemetry to see how - // to add telemetry capture to your bot. - logger.LogError(exception, $"[OnTurnError] Unhandled error: {exception.Message}"); - - // Uncomment the line below for local debugging to send an error message to the user. - // await context.SendActivityAsync($"Sorry, it looks like something went wrong. Exception Caught: {exception.Message}"); - - // Send a trace activity, which will be displayed in the Bot Framework Emulator - await turnContext.TraceActivityAsync("OnTurnError Trace", exception.Message, "https://www.botframework.com/schemas/error", "TurnError"); - }; - } - } -} diff --git a/samples/bot-file-upload/csharp/TeamsFileUpload/Bots/TeamsFileUploadBot.cs b/samples/bot-file-upload/csharp/TeamsFileUpload/Bots/TeamsFileUploadBot.cs deleted file mode 100644 index 5007fc348a..0000000000 --- a/samples/bot-file-upload/csharp/TeamsFileUpload/Bots/TeamsFileUploadBot.cs +++ /dev/null @@ -1,214 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Bot.Builder; -using Microsoft.Bot.Builder.Teams; -using Microsoft.Bot.Connector.Authentication; -using Microsoft.Bot.Schema; -using Microsoft.Bot.Schema.Teams; -using Microsoft.Extensions.Configuration; -using Newtonsoft.Json.Linq; - -namespace Microsoft.BotBuilderSamples.Bots -{ - public class TeamsFileUploadBot : TeamsActivityHandler - { - // Private fields for the HttpClient and bot credentials. - private readonly IHttpClientFactory _clientFactory; - private static HttpClient _client; - private static string microsoftAppId; - private static string microsoftAppPassword; - - // Constructor that initializes the bot with the configuration and client factory. - public TeamsFileUploadBot(IHttpClientFactory clientFactory, IConfiguration configuration) - { - _clientFactory = clientFactory; - - // Initialize static variables only once. - if (_client == null) - { - _client = _clientFactory.CreateClient(); - } - - microsoftAppId = configuration["MicrosoftAppId"]; - microsoftAppPassword = configuration["MicrosoftAppPassword"]; - } - - // Handles incoming message activities and processes file uploads or inline images. - protected override async Task OnMessageActivityAsync(ITurnContext turnContext, CancellationToken cancellationToken) - { - bool messageWithFileDownloadInfo = turnContext.Activity.Attachments?[0].ContentType == FileDownloadInfo.ContentType; - - if (messageWithFileDownloadInfo) - { - // Process file download. - var file = turnContext.Activity.Attachments[0]; - var fileDownload = JObject.FromObject(file.Content).ToObject(); - string filePath = Path.Combine("Files", file.Name); - - var response = await _client.GetAsync(fileDownload.DownloadUrl); - await SaveFileAsync(filePath, response); - - var reply = MessageFactory.Text($"{file.Name} received and saved."); - reply.TextFormat = "xml"; - await turnContext.SendActivityAsync(reply, cancellationToken); - } - else if (turnContext.Activity.Attachments?[0].ContentType.Contains("image/*") == true) - { - // Process inline image. - await ProcessInlineImage(turnContext, cancellationToken); - } - else - { - // Send a default file. - string filename = "teams-logo.png"; - string filePath = Path.Combine("Files", filename); - long fileSize = new FileInfo(filePath).Length; - await SendFileCardAsync(turnContext, filename, fileSize, cancellationToken); - } - } - - // Helper method to save file to disk. - private async Task SaveFileAsync(string filePath, HttpResponseMessage response) - { - using (var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None)) - { - await response.Content.CopyToAsync(fileStream); - } - } - - // Processes inline image attachments and saves them. - private async Task ProcessInlineImage(ITurnContext turnContext, CancellationToken cancellationToken) - { - var attachment = turnContext.Activity.Attachments[0]; - var token = await new MicrosoftAppCredentials(microsoftAppId, microsoftAppPassword).GetTokenAsync(); - _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); - - var responseMessage = await _client.GetAsync(attachment.ContentUrl); - - // Save the inline image to Files directory. - var filePath = Path.Combine("Files", "ImageFromUser.png"); - await SaveFileAsync(filePath, responseMessage); - - // Create reply with image. - var reply = MessageFactory.Text($"Attachment of {attachment.ContentType} type and size of {responseMessage.Content.Headers.ContentLength} bytes received."); - reply.Attachments = new List { GetInlineAttachment() }; - await turnContext.SendActivityAsync(reply, cancellationToken); - } - - // Creates an inline attachment for the image that was received. - private static Attachment GetInlineAttachment() - { - var imagePath = Path.Combine("Files", "ImageFromUser.png"); - var imageData = Convert.ToBase64String(File.ReadAllBytes(imagePath)); - - return new Attachment - { - Name = @"ImageFromUser.png", - ContentType = "image/png", - ContentUrl = $"data:image/png;base64,{imageData}", - }; - } - - // Sends a file consent card to the user. - private async Task SendFileCardAsync(ITurnContext turnContext, string filename, long filesize, CancellationToken cancellationToken) - { - var consentContext = new Dictionary - { - { "filename", filename }, - }; - - var fileCard = new FileConsentCard - { - Description = "This is the file I want to send you", - SizeInBytes = filesize, - AcceptContext = consentContext, - DeclineContext = consentContext, - }; - - var asAttachment = new Attachment - { - Content = fileCard, - ContentType = FileConsentCard.ContentType, - Name = filename, - }; - - var replyActivity = turnContext.Activity.CreateReply(); - replyActivity.Attachments = new List { asAttachment }; - await turnContext.SendActivityAsync(replyActivity, cancellationToken); - } - - // Handles file consent acceptance. - protected override async Task OnTeamsFileConsentAcceptAsync(ITurnContext turnContext, FileConsentCardResponse fileConsentCardResponse, CancellationToken cancellationToken) - { - try - { - JToken context = JObject.FromObject(fileConsentCardResponse.Context); - string filePath = Path.Combine("Files", context["filename"].ToString()); - long fileSize = new FileInfo(filePath).Length; - - using (var fileStream = File.OpenRead(filePath)) - { - var fileContent = new StreamContent(fileStream); - fileContent.Headers.ContentLength = fileSize; - fileContent.Headers.ContentRange = new ContentRangeHeaderValue(0, fileSize - 1, fileSize); - await _client.PutAsync(fileConsentCardResponse.UploadInfo.UploadUrl, fileContent, cancellationToken); - } - - await FileUploadCompletedAsync(turnContext, fileConsentCardResponse, cancellationToken); - } - catch (Exception e) - { - await FileUploadFailedAsync(turnContext, e.ToString(), cancellationToken); - } - } - - // Handles file consent decline. - protected override async Task OnTeamsFileConsentDeclineAsync(ITurnContext turnContext, FileConsentCardResponse fileConsentCardResponse, CancellationToken cancellationToken) - { - JToken context = JObject.FromObject(fileConsentCardResponse.Context); - var reply = MessageFactory.Text($"Declined. We won't upload file {context["filename"]}."); - reply.TextFormat = "xml"; - await turnContext.SendActivityAsync(reply, cancellationToken); - } - - // Sends a reply indicating that file upload is completed. - private async Task FileUploadCompletedAsync(ITurnContext turnContext, FileConsentCardResponse fileConsentCardResponse, CancellationToken cancellationToken) - { - var downloadCard = new FileInfoCard - { - UniqueId = fileConsentCardResponse.UploadInfo.UniqueId, - FileType = fileConsentCardResponse.UploadInfo.FileType, - }; - - var asAttachment = new Attachment - { - Content = downloadCard, - ContentType = FileInfoCard.ContentType, - Name = fileConsentCardResponse.UploadInfo.Name, - ContentUrl = fileConsentCardResponse.UploadInfo.ContentUrl, - }; - - var reply = MessageFactory.Text($"File uploaded. Your file {fileConsentCardResponse.UploadInfo.Name} is ready to download"); - reply.TextFormat = "xml"; - reply.Attachments = new List { asAttachment }; - - await turnContext.SendActivityAsync(reply, cancellationToken); - } - - // Sends a reply indicating that file upload has failed. - private async Task FileUploadFailedAsync(ITurnContext turnContext, string error, CancellationToken cancellationToken) - { - var reply = MessageFactory.Text($"File upload failed. Error:
{error}
"); - reply.TextFormat = "xml"; - await turnContext.SendActivityAsync(reply, cancellationToken); - } - } -} diff --git a/samples/bot-file-upload/csharp/TeamsFileUpload/Config.cs b/samples/bot-file-upload/csharp/TeamsFileUpload/Config.cs new file mode 100644 index 0000000000..08d1c137f2 --- /dev/null +++ b/samples/bot-file-upload/csharp/TeamsFileUpload/Config.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace TeamsFileUpload +{ + public class ConfigOptions + { + public TeamsConfigOptions Teams { get; set; } + } + + public class TeamsConfigOptions + { + public string BotType { get; set; } + public string ClientId { get; set; } + public string ClientSecret { get; set; } + public string TenantId { get; set; } + } +} \ No newline at end of file diff --git a/samples/bot-file-upload/csharp/TeamsFileUpload/Controllers/BotController.cs b/samples/bot-file-upload/csharp/TeamsFileUpload/Controllers/BotController.cs deleted file mode 100644 index 72962bc121..0000000000 --- a/samples/bot-file-upload/csharp/TeamsFileUpload/Controllers/BotController.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Bot.Builder; -using Microsoft.Bot.Builder.Integration.AspNet.Core; - -namespace Microsoft.BotBuilderSamples.Controllers -{ - // This ASP.NET Controller handles incoming HTTP requests. - // Dependency Injection will provide the `IBot` and `IBotFrameworkHttpAdapter` implementations at runtime. - // Multiple different `IBot` implementations can be used for different endpoints by specifying a more specific type - // in the constructor. - [Route("api/messages")] - [ApiController] - public class BotController : ControllerBase - { - private readonly IBotFrameworkHttpAdapter _adapter; - private readonly IBot _bot; - - // Constructor that initializes the bot and adapter via dependency injection. - public BotController(IBotFrameworkHttpAdapter adapter, IBot bot) - { - _adapter = adapter; - _bot = bot; - } - - // HTTP POST method to receive requests from users. - // This method delegates the processing of the HTTP POST to the adapter. - // The adapter invokes the bot with the request and response. - [HttpPost] - public async Task PostAsync() - { - await _adapter.ProcessAsync(Request, Response, _bot); - } - } -} diff --git a/samples/bot-file-upload/csharp/TeamsFileUpload/Controllers/Controller.cs b/samples/bot-file-upload/csharp/TeamsFileUpload/Controllers/Controller.cs new file mode 100644 index 0000000000..c4e2b868c8 --- /dev/null +++ b/samples/bot-file-upload/csharp/TeamsFileUpload/Controllers/Controller.cs @@ -0,0 +1,359 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Teams.Api.Activities; +using Microsoft.Teams.Apps; +using Microsoft.Teams.Apps.Activities; +using Microsoft.Teams.Apps.Activities.Invokes; +using Microsoft.Teams.Apps.Annotations; +using Microsoft.Teams.Api; +using System.Net.Http.Headers; +using FileConsentActivity = Microsoft.Teams.Api.Activities.Invokes.FileConsentActivity; +using FileConsentCardResponse = Microsoft.Teams.Api.FileConsentCardResponse; +using FileUploadInfo = Microsoft.Teams.Api.FileUploadInfo; +using FileConsentCard = TeamsFileUpload.Models.FileConsentCard; +using FileDownloadInfo = TeamsFileUpload.Models.FileDownloadInfo; +using FileInfoCard = TeamsFileUpload.Models.FileInfoCard; + +namespace TeamsFileUpload.Controllers +{ + [TeamsController] + public class Controller + { + private readonly IHttpClientFactory _httpClientFactory; + private readonly string _filesPath = Path.Combine(Environment.CurrentDirectory, "Files"); + + /// + /// Initializes a new instance of the Controller class. + /// + /// The HTTP client factory for making HTTP requests. + public Controller(IHttpClientFactory httpClientFactory) + { + _httpClientFactory = httpClientFactory; + + // Ensure Files directory exists + if (!Directory.Exists(_filesPath)) + { + Directory.CreateDirectory(_filesPath); + } + } + + /// + /// Handles incoming messages from Teams users. + /// Processes file attachments (downloads and inline images) or sends a file consent card if no attachment is present. + /// + /// The message activity received from Teams. + /// The Teams client for sending responses. + /// Logger for tracking message processing. + [Message] + public async Task OnMessage([Context] MessageActivity activity, [Context] IContext.Client client, [Context] Microsoft.Teams.Common.Logging.ILogger log) + { + log.Info("Message received"); + + // Check if message contains actual file attachments (not text/html which is just message content) + bool hasFileAttachment = activity.Attachments != null && + activity.Attachments.Count > 0 && + activity.Attachments[0].ContentType?.Value != "text/html"; + + if (hasFileAttachment) + { + var attachment = activity.Attachments[0]; + var contentTypeValue = attachment.ContentType?.Value ?? attachment.ContentType?.ToString() ?? ""; + + log.Info($"Received attachment with ContentType: {contentTypeValue}"); + + // Handle file downloads (Teams sends files with this content type) + if (contentTypeValue == "application/vnd.microsoft.teams.file.download.info") + { + await ProcessFileDownload(client, attachment, log); + } + // Handle inline images + else if (contentTypeValue.StartsWith("image/")) + { + await ProcessInlineImage(activity, client, attachment, log); + } + else + { + log.Info($"Unsupported attachment type: {contentTypeValue}"); + await client.Send($"File attachment received but type '{contentTypeValue}' not supported for processing."); + } + } + else + { + // Send a file consent card to upload a file + await SendFileConsentCard(client, "teams-logo.png", log); + } + } + + /// + /// Handles file consent card responses from users. + /// Routes to appropriate handler based on whether user accepted or declined the file upload. + /// + /// The file consent activity containing user's response. + /// The Teams client for sending responses. + /// Logger for tracking consent processing. + [FileConsent] + public async Task OnFileConsent([Context] FileConsentActivity activity, [Context] IContext.Client client, [Context] Microsoft.Teams.Common.Logging.ILogger log) + { + log.Info("File consent invoke received"); + + var fileConsentResponse = activity.Value; + + if (fileConsentResponse?.Action == "accept") + { + await OnFileConsentAccept(fileConsentResponse, client, log); + } + else if (fileConsentResponse?.Action == "decline") + { + await OnFileConsentDecline(fileConsentResponse, client, log); + } + } + + /// + /// Processes inline image attachments received in Teams messages. + /// Downloads the image, saves it locally, and sends it back as a base64-encoded inline attachment. + /// + /// The message activity containing the image. + /// The Teams client for sending responses. + /// The image attachment to process. + /// Logger for tracking image processing. + private async Task ProcessInlineImage(MessageActivity activity, IContext.Client client, Attachment attachment, Microsoft.Teams.Common.Logging.ILogger log) + { + try + { + var httpClient = _httpClientFactory.CreateClient(); + + // Download the image from Teams + var response = await httpClient.GetAsync(attachment.ContentUrl); + response.EnsureSuccessStatusCode(); + + // Save the image locally + var fileName = $"ImageFromUser_{DateTime.Now.Ticks}.png"; + var filePath = Path.Combine(_filesPath, fileName); + + using (var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None)) + { + await response.Content.CopyToAsync(fileStream); + } + + log.Info($"Saved image to {filePath}"); + + // Read the saved image and send it back as inline attachment + var imageData = Convert.ToBase64String(File.ReadAllBytes(filePath)); + var inlineAttachment = new Attachment + { + Name = fileName, + ContentType = new ContentType("image/png"), + ContentUrl = $"data:image/png;base64,{imageData}" + }; + + var replyMessage = new MessageActivity($"Received and saved your image. File size: {response.Content.Headers.ContentLength} bytes"); + replyMessage.Attachments = new List { inlineAttachment }; + await client.Send(replyMessage); + } + catch (Exception ex) + { + log.Error($"Error processing inline image: {ex.Message}"); + await client.Send($"Error processing image: {ex.Message}"); + } + } + + /// + /// Processes file download attachments received in Teams messages. + /// Downloads the file from Teams and saves it to the local Files directory. + /// + /// The Teams client for sending responses. + /// The file attachment containing download information. + /// Logger for tracking file download processing. + private async Task ProcessFileDownload(IContext.Client client, Attachment attachment, Microsoft.Teams.Common.Logging.ILogger log) + { + try + { + var fileDownloadInfo = System.Text.Json.JsonSerializer.Deserialize( + System.Text.Json.JsonSerializer.Serialize(attachment.Content)); + + if (fileDownloadInfo != null) + { + var httpClient = _httpClientFactory.CreateClient(); + var response = await httpClient.GetAsync(fileDownloadInfo.DownloadUrl); + response.EnsureSuccessStatusCode(); + + var fileName = attachment.Name ?? $"download_{DateTime.Now.Ticks}"; + var filePath = Path.Combine(_filesPath, fileName); + + using (var fileStream = new FileStream(filePath, FileMode.Create)) + { + await response.Content.CopyToAsync(fileStream); + } + + log.Info($"Downloaded file to {filePath}"); + await client.Send($"File {fileName} downloaded successfully!"); + } + } + catch (Exception ex) + { + log.Error($"Error downloading file: {ex.Message}"); + await client.Send($"Error downloading file: {ex.Message}"); + } + } + + /// + /// Sends a file consent card to the user requesting permission to upload a file. + /// Creates a sample file if it doesn't exist in the Files directory. + /// + /// The Teams client for sending the consent card. + /// The name of the file to be uploaded. + /// Logger for tracking consent card operations. + private async Task SendFileConsentCard(IContext.Client client, string fileName, Microsoft.Teams.Common.Logging.ILogger log) + { + try + { + var filePath = Path.Combine(_filesPath, fileName); + + // Create a sample file if it doesn't exist + if (!File.Exists(filePath)) + { + await File.WriteAllTextAsync(filePath, "Sample file content for Teams file upload demo."); + } + + var fileInfo = new FileInfo(filePath); + var fileSize = fileInfo.Length; + + var fileConsentCard = new FileConsentCard + { + Name = fileName, + Description = "This is the file I want to send you", + SizeInBytes = fileSize, + AcceptContext = new { fileName = fileName }, + DeclineContext = new { fileName = fileName } + }; + + var attachment = new Attachment + { + ContentType = new ContentType(FileConsentCard.ContentType), + Name = fileName, + Content = fileConsentCard + }; + + log.Info($"Sending file consent card for {fileName}"); + var message = new MessageActivity("Please accept the file"); + message.Attachments = new List { attachment }; + await client.Send(message); + } + catch (Exception ex) + { + log.Error($"Error sending file consent card: {ex.Message}"); + await client.Send($"Error: {ex.Message}"); + } + } + + /// + /// Handles the user accepting the file upload consent. + /// Uploads the file to Teams/OneDrive and sends appropriate response based on file type. + /// For images: sends inline base64-encoded attachment. For other files: sends OneDrive link. + /// + /// The file consent response containing upload information. + /// The Teams client for sending responses. + /// Logger for tracking file upload operations. + private async Task OnFileConsentAccept(FileConsentCardResponse response, IContext.Client client, Microsoft.Teams.Common.Logging.ILogger log) + { + try + { + var context = System.Text.Json.JsonSerializer.Deserialize>( + System.Text.Json.JsonSerializer.Serialize(response.Context)); + + var fileName = context?["fileName"] ?? "file.txt"; + var filePath = Path.Combine(_filesPath, fileName); + + if (!File.Exists(filePath)) + { + await client.Send($"File {fileName} not found."); + return; + } + + var fileData = await File.ReadAllBytesAsync(filePath); + var uploadInfo = response.UploadInfo; + + // Upload the file using PUT request + var httpClient = _httpClientFactory.CreateClient(); + var fileContent = new ByteArrayContent(fileData); + fileContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); + fileContent.Headers.ContentRange = new ContentRangeHeaderValue(0, fileData.Length - 1, fileData.Length); + + var uploadResponse = await httpClient.PutAsync(uploadInfo.UploadUrl, fileContent); + uploadResponse.EnsureSuccessStatusCode(); + + log.Info($"File {fileName} uploaded successfully"); + + // Extract file extension (without the dot) for FileType + var fileExtension = Path.GetExtension(fileName)?.TrimStart('.') ?? + uploadInfo.FileType ?? + "file"; + + // For images, send as inline attachment + var lowerFileName = fileName.ToLower(); + if (lowerFileName.EndsWith(".png") || lowerFileName.EndsWith(".jpg") || + lowerFileName.EndsWith(".jpeg") || lowerFileName.EndsWith(".gif")) + { + // Read the file and encode as base64 + var imageData = Convert.ToBase64String(fileData); + var mimeType = lowerFileName.EndsWith(".png") ? "image/png" : + lowerFileName.EndsWith(".gif") ? "image/gif" : "image/jpeg"; + + var imageAttachment = new Attachment + { + Name = fileName, + ContentType = new ContentType(mimeType), + ContentUrl = $"data:{mimeType};base64,{imageData}" + }; + + var successMessage = new MessageActivity($"File uploaded successfully. Your file {fileName} has been uploaded to OneDrive."); + successMessage.TextFormat = Microsoft.Teams.Api.TextFormat.Xml; + successMessage.Attachments = new List { imageAttachment }; + await client.Send(successMessage); + } + else + { + // For non-image files, just send confirmation with link + var successMessage = new MessageActivity($"File uploaded successfully. Your file {fileName} has been uploaded to OneDrive. Click the link to view or download."); + successMessage.TextFormat = Microsoft.Teams.Api.TextFormat.Xml; + await client.Send(successMessage); + } + } + catch (Exception ex) + { + log.Error($"Error uploading file: {ex.Message}"); + await client.Send($"Error uploading file: {ex.Message}"); + } + } + + /// + /// Handles the user declining the file upload consent. + /// Sends a confirmation message indicating the file will not be uploaded. + /// + /// The file consent response containing decline information. + /// The Teams client for sending responses. + /// Logger for tracking decline operations. + private async Task OnFileConsentDecline(FileConsentCardResponse response, IContext.Client client, Microsoft.Teams.Common.Logging.ILogger log) + { + try + { + var context = System.Text.Json.JsonSerializer.Deserialize>( + System.Text.Json.JsonSerializer.Serialize(response.Context)); + + var fileName = context?["fileName"] ?? "file"; + + log.Info($"File consent declined for {fileName}"); + + var declineMessage = new MessageActivity($"Declined. We won't upload file {fileName}."); + declineMessage.TextFormat = Microsoft.Teams.Api.TextFormat.Xml; + await client.Send(declineMessage); + } + catch (Exception ex) + { + log.Error($"Error in decline handler: {ex.Message}"); + await client.Send("You declined the file upload."); + } + } + } +} \ No newline at end of file diff --git a/samples/bot-file-upload/csharp/TeamsFileUpload/DeploymentTemplates/DeployUseExistResourceGroup/parameters-for-template-AzureBot-with-rg.json b/samples/bot-file-upload/csharp/TeamsFileUpload/DeploymentTemplates/DeployUseExistResourceGroup/parameters-for-template-AzureBot-with-rg.json deleted file mode 100644 index c2c03ef307..0000000000 --- a/samples/bot-file-upload/csharp/TeamsFileUpload/DeploymentTemplates/DeployUseExistResourceGroup/parameters-for-template-AzureBot-with-rg.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "azureBotId": { - "value": "" - }, - "azureBotSku": { - "value": "S1" - }, - "azureBotRegion": { - "value": "global" - }, - "botEndpoint": { - "value": "" - }, - "appType": { - "value": "MultiTenant" - }, - "appId": { - "value": "" - }, - "UMSIName": { - "value": "" - }, - "UMSIResourceGroupName": { - "value": "" - }, - "tenantId": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/bot-file-upload/csharp/TeamsFileUpload/DeploymentTemplates/DeployUseExistResourceGroup/parameters-for-template-BotApp-with-rg.json b/samples/bot-file-upload/csharp/TeamsFileUpload/DeploymentTemplates/DeployUseExistResourceGroup/parameters-for-template-BotApp-with-rg.json deleted file mode 100644 index c4b2909008..0000000000 --- a/samples/bot-file-upload/csharp/TeamsFileUpload/DeploymentTemplates/DeployUseExistResourceGroup/parameters-for-template-BotApp-with-rg.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appServiceName": { - "value": "" - }, - "existingAppServicePlanName": { - "value": "" - }, - "existingAppServicePlanLocation": { - "value": "" - }, - "newAppServicePlanName": { - "value": "" - }, - "newAppServicePlanLocation": { - "value": "" - }, - "newAppServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "appType": { - "value": "MultiTenant" - }, - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "UMSIName": { - "value": "" - }, - "UMSIResourceGroupName": { - "value": "" - }, - "tenantId": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/bot-file-upload/csharp/TeamsFileUpload/DeploymentTemplates/DeployUseExistResourceGroup/readme.md b/samples/bot-file-upload/csharp/TeamsFileUpload/DeploymentTemplates/DeployUseExistResourceGroup/readme.md deleted file mode 100644 index 628f0a9546..0000000000 --- a/samples/bot-file-upload/csharp/TeamsFileUpload/DeploymentTemplates/DeployUseExistResourceGroup/readme.md +++ /dev/null @@ -1,48 +0,0 @@ -# Usage -The BotApp must be deployed prior to AzureBot. - -Command line: -- az login -- az deployment group create --resource-group --template-file --parameters @ - -# parameters-for-template-BotApp-with-rg: - -- **appServiceName**:(required) The Name of the Bot App Service. - -- (choose an existingAppServicePlan or create a new AppServicePlan) - - **existingAppServicePlanName**: The name of the App Service Plan. - - **existingAppServicePlanLocation**: The location of the App Service Plan. - - **newAppServicePlanName**: The name of the App Service Plan. - - **newAppServicePlanLocation**: The location of the App Service Plan. - - **newAppServicePlanSku**: The SKU of the App Service Plan. Defaults to Standard values. - -- **appType**: Type of Bot Authentication. set as MicrosoftAppType in the Web App's Application Settings. **Allowed values are: MultiTenant(default), SingleTenant, UserAssignedMSI.** - -- **appId**:(required) Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings. - -- **appSecret**:(required for MultiTenant and SingleTenant) Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. - -- **UMSIName**:(required for UserAssignedMSI) The User-Assigned Managed Identity Resource used for the Bot's Authentication. - -- **UMSIResourceGroupName**:(required for UserAssignedMSI) The User-Assigned Managed Identity Resource Group used for the Bot's Authentication. - -- **tenantId**: The Azure AD Tenant ID to use as part of the Bot's Authentication. Only used for SingleTenant and UserAssignedMSI app types. Defaults to . - -MoreInfo: https://docs.microsoft.com/en-us/azure/bot-service/tutorial-provision-a-bot?view=azure-bot-service-4.0&tabs=userassigned%2Cnewgroup#create-an-identity-resource - - - -# parameters-for-template-AzureBot-with-rg: - -- **azureBotId**:(required) The globally unique and immutable bot ID. -- **azureBotSku**: The pricing tier of the Bot Service Registration. **Allowed values are: F0, S1(default)**. -- **azureBotRegion**: Specifies the location of the new AzureBot. **Allowed values are: global(default), westeurope**. -- **botEndpoint**: Use to handle client messages, Such as https://.azurewebsites.net/api/messages. - -- **appType**: Type of Bot Authentication. set as MicrosoftAppType in the Web App's Application Settings. **Allowed values are: MultiTenant(default), SingleTenant, UserAssignedMSI.** -- **appId**:(required) Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings. -- **UMSIName**:(required for UserAssignedMSI) The User-Assigned Managed Identity Resource used for the Bot's Authentication. -- **UMSIResourceGroupName**:(required for UserAssignedMSI) The User-Assigned Managed Identity Resource Group used for the Bot's Authentication. -- **tenantId**: The Azure AD Tenant ID to use as part of the Bot's Authentication. Only used for SingleTenant and UserAssignedMSI app types. Defaults to . - -MoreInfo: https://docs.microsoft.com/en-us/azure/bot-service/tutorial-provision-a-bot?view=azure-bot-service-4.0&tabs=userassigned%2Cnewgroup#create-an-identity-resource \ No newline at end of file diff --git a/samples/bot-file-upload/csharp/TeamsFileUpload/DeploymentTemplates/DeployUseExistResourceGroup/template-AzureBot-with-rg.json b/samples/bot-file-upload/csharp/TeamsFileUpload/DeploymentTemplates/DeployUseExistResourceGroup/template-AzureBot-with-rg.json deleted file mode 100644 index a8a960066f..0000000000 --- a/samples/bot-file-upload/csharp/TeamsFileUpload/DeploymentTemplates/DeployUseExistResourceGroup/template-AzureBot-with-rg.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "azureBotId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID." - } - }, - "azureBotSku": { - "type": "string", - "defaultValue": "S1", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Allowed values are: F0, S1(default)." - } - }, - "azureBotRegion": { - "type": "string", - "defaultValue": "global", - "metadata": { - "description": "Specifies the location of the new AzureBot. Allowed values are: global(default), westeurope." - } - }, - "botEndpoint": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Use to handle client messages, Such as https://.azurewebsites.net/api/messages." - } - }, - "appType": { - "type": "string", - "defaultValue": "MultiTenant", - "allowedValues": [ - "MultiTenant", - "SingleTenant", - "UserAssignedMSI" - ], - "metadata": { - "description": "Type of Bot Authentication. set as MicrosoftAppType in the Web App's Application Settings. Allowed values are: MultiTenant, SingleTenant, UserAssignedMSI. Defaults to \"MultiTenant\"." - } - }, - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "UMSIName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The User-Assigned Managed Identity Resource used for the Bot's Authentication." - } - }, - "UMSIResourceGroupName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The User-Assigned Managed Identity Resource Group used for the Bot's Authentication." - } - }, - "tenantId": { - "type": "string", - "defaultValue": "[subscription().tenantId]", - "metadata": { - "description": "The Azure AD Tenant ID to use as part of the Bot's Authentication. Only used for SingleTenant and UserAssignedMSI app types. Defaults to \"Subscription Tenant ID\"." - } - } - }, - "variables": { - "botEndpoint": "[if(empty(parameters('botEndpoint')), concat('https://', parameters('azureBotId'), '.azurewebsites.net/api/messages'), parameters('botEndpoint'))]", - "tenantId": "[if(empty(parameters('tenantId')), subscription().tenantId, parameters('tenantId'))]", - "msiResourceId": "[if(empty(parameters('UMSIName')), '', concat(subscription().id, '/resourceGroups/', parameters('UMSIResourceGroupName'), '/providers/', 'Microsoft.ManagedIdentity/userAssignedIdentities/', parameters('UMSIName')))]", - "appTypeDef": { - "MultiTenant": { - "tenantId": "", - "msiResourceId": "" - }, - "SingleTenant": { - "tenantId": "[variables('tenantId')]", - "msiResourceId": "" - }, - "UserAssignedMSI": { - "tenantId": "[variables('tenantId')]", - "msiResourceId": "[variables('msiResourceId')]" - } - }, - "appType": { - "tenantId": "[variables('appTypeDef')[parameters('appType')].tenantId]", - "msiResourceId": "[variables('appTypeDef')[parameters('appType')].msiResourceId]" - } - }, - "resources": [ - { - "apiVersion": "2021-05-01-preview", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('azureBotId')]", - "location": "[parameters('azureBotRegion')]", - "kind": "azurebot", - "sku": { - "name": "[parameters('azureBotSku')]" - }, - "properties": { - "displayName": "[parameters('azureBotId')]", - "iconUrl": "https://docs.botframework.com/static/devportal/client/images/bot-framework-default.png", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "msaAppTenantId": "[variables('appType').tenantId]", - "msaAppMSIResourceId": "[variables('appType').msiResourceId]", - "msaAppType": "[parameters('appType')]", - "luisAppIds": [], - "schemaTransformationVersion": "1.3", - "isCmekEnabled": false, - "isIsolated": false - }, - "dependsOn": [] - } - ] -} \ No newline at end of file diff --git a/samples/bot-file-upload/csharp/TeamsFileUpload/DeploymentTemplates/DeployUseExistResourceGroup/template-BotApp-with-rg.json b/samples/bot-file-upload/csharp/TeamsFileUpload/DeploymentTemplates/DeployUseExistResourceGroup/template-BotApp-with-rg.json deleted file mode 100644 index ce3bb6322a..0000000000 --- a/samples/bot-file-upload/csharp/TeamsFileUpload/DeploymentTemplates/DeployUseExistResourceGroup/template-BotApp-with-rg.json +++ /dev/null @@ -1,191 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appServiceName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App." - } - }, - "existingAppServicePlanName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Name of the existing App Service Plan used to create the Web App for the bot." - } - }, - "existingAppServicePlanLocation": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The location of the App Service Plan." - } - }, - "newAppServicePlanName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The name of the new App Service Plan." - } - }, - "newAppServicePlanLocation": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The location of the App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "appType": { - "type": "string", - "defaultValue": "MultiTenant", - "allowedValues": [ - "MultiTenant", - "SingleTenant", - "UserAssignedMSI" - ], - "metadata": { - "description": "Type of Bot Authentication. set as MicrosoftAppType in the Web App's Application Settings. Allowed values are: MultiTenant, SingleTenant, UserAssignedMSI. Defaults to \"MultiTenant\"." - } - }, - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Required for MultiTenant and SingleTenant app types. Defaults to \"\"." - } - }, - "UMSIName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The User-Assigned Managed Identity Resource used for the Bot's Authentication. Defaults to \"\"." - } - }, - "UMSIResourceGroupName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The User-Assigned Managed Identity Resource Group used for the Bot's Authentication. Defaults to \"\"." - } - }, - "tenantId": { - "type": "string", - "defaultValue": "[subscription().tenantId]", - "metadata": { - "description": "The Azure AD Tenant ID to use as part of the Bot's Authentication. Only used for SingleTenant and UserAssignedMSI app types. Defaults to \"Subscription Tenant ID\"." - } - } - }, - "variables": { - "tenantId": "[if(empty(parameters('tenantId')), subscription().tenantId, parameters('tenantId'))]", - "useExistingServicePlan": "[not(empty(parameters('existingAppServicePlanName')))]", - "servicePlanName": "[if(variables('useExistingServicePlan'), parameters('existingAppServicePlanName'), parameters('newAppServicePlanName'))]", - "servicePlanLocation": "[if(variables('useExistingServicePlan'), parameters('existingAppServicePlanLocation'), parameters('newAppServicePlanLocation'))]", - "msiResourceId": "[if(empty(parameters('UMSIName')), '', concat(subscription().id, '/resourceGroups/', parameters('UMSIResourceGroupName'), '/providers/', 'Microsoft.ManagedIdentity/userAssignedIdentities/', parameters('UMSIName')))]", - "appTypeDef": { - "MultiTenant": { - "tenantId": "", - "identity": { "type": "None" } - }, - "SingleTenant": { - "tenantId": "[variables('tenantId')]", - "identity": { "type": "None" } - }, - "UserAssignedMSI": { - "tenantId": "[variables('tenantId')]", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": { - "[variables('msiResourceId')]": {} - } - } - } - }, - "appType": { - "tenantId": "[variables('appTypeDef')[parameters('appType')].tenantId]", - "identity": "[variables('appTypeDef')[parameters('appType')].identity]" - } - }, - "resources": [ - { - "comments": "Create a new App Service Plan if no existing App Service Plan name was passed in.", - "type": "Microsoft.Web/serverfarms", - "condition": "[not(variables('useExistingServicePlan'))]", - "name": "[variables('servicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[parameters('newAppServicePlanLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "properties": { - "name": "[variables('servicePlanName')]" - } - }, - { - "comments": "Create a Web App using an App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2015-08-01", - "location": "[variables('servicePlanLocation')]", - "kind": "app", - "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" - ], - "name": "[parameters('appServiceName')]", - "identity": "[variables('appType').identity]", - "properties": { - "name": "[parameters('appServiceName')]", - "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", - "siteConfig": { - "appSettings": [ - { - "name": "WEBSITE_NODE_DEFAULT_VERSION", - "value": "10.14.1" - }, - { - "name": "MicrosoftAppType", - "value": "[parameters('appType')]" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - }, - { - "name": "MicrosoftAppTenantId", - "value": "[variables('appType').tenantId]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - }, - "webSocketsEnabled": true - } - } - } - ] -} \ No newline at end of file diff --git a/samples/bot-file-upload/csharp/TeamsFileUpload/DeploymentTemplates/DeployWithNewResourceGroup/parameters-for-template-AzureBot-new-rg.json b/samples/bot-file-upload/csharp/TeamsFileUpload/DeploymentTemplates/DeployWithNewResourceGroup/parameters-for-template-AzureBot-new-rg.json deleted file mode 100644 index 44f169e4d5..0000000000 --- a/samples/bot-file-upload/csharp/TeamsFileUpload/DeploymentTemplates/DeployWithNewResourceGroup/parameters-for-template-AzureBot-new-rg.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupName": { - "value": "" - }, - "groupLocation": { - "value": "" - }, - "azureBotId": { - "value": "" - }, - "azureBotSku": { - "value": "S1" - }, - "azureBotRegion": { - "value": "global" - }, - "botEndpoint": { - "value": "" - }, - "appType": { - "value": "MultiTenant" - }, - "appId": { - "value": "" - }, - "UMSIName": { - "value": "" - }, - "UMSIResourceGroupName": { - "value": "" - }, - "tenantId": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/bot-file-upload/csharp/TeamsFileUpload/DeploymentTemplates/DeployWithNewResourceGroup/parameters-for-template-BotApp-new-rg.json b/samples/bot-file-upload/csharp/TeamsFileUpload/DeploymentTemplates/DeployWithNewResourceGroup/parameters-for-template-BotApp-new-rg.json deleted file mode 100644 index 8abb03d597..0000000000 --- a/samples/bot-file-upload/csharp/TeamsFileUpload/DeploymentTemplates/DeployWithNewResourceGroup/parameters-for-template-BotApp-new-rg.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupName": { - "value": "" - }, - "groupLocation": { - "value": "" - }, - "appServiceName": { - "value": "" - }, - "appServicePlanName": { - "value": "" - }, - "appServicePlanLocation": { - "value": "" - }, - "appServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "appType": { - "value": "MultiTenant" - }, - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "UMSIName": { - "value": "" - }, - "UMSIResourceGroupName": { - "value": "" - }, - "tenantId": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/bot-file-upload/csharp/TeamsFileUpload/DeploymentTemplates/DeployWithNewResourceGroup/readme.md b/samples/bot-file-upload/csharp/TeamsFileUpload/DeploymentTemplates/DeployWithNewResourceGroup/readme.md deleted file mode 100644 index 23bf7a5a51..0000000000 --- a/samples/bot-file-upload/csharp/TeamsFileUpload/DeploymentTemplates/DeployWithNewResourceGroup/readme.md +++ /dev/null @@ -1,45 +0,0 @@ -# Usage -The BotApp must be deployed prior to AzureBot. - -Command line: -- az login -- az deployment sub create --template-file --location --parameters @ - -# parameters-for-template-BotApp-new-rg: - -- **groupName**:(required) Specifies the name of the new Resource Group. -- **groupLocation**:(required) Specifies the location of the new Resource Group. - -- **appServiceName**:(required) The location of the App Service Plan. -- **appServicePlanName**:(required) The name of the App Service Plan. -- **appServicePlanLocation**: The location of the App Service Plan. Defaults to use groupLocation. -- **appServicePlanSku**: The SKU of the App Service Plan. Defaults to Standard values. - -- **appType**: Type of Bot Authentication. set as MicrosoftAppType in the Web App's Application Settings. **Allowed values are: MultiTenant(default), SingleTenant, UserAssignedMSI.** -- **appId**:(required) Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings. -- **appSecret**:(required for MultiTenant and SingleTenant) Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. -- **UMSIName**:(required for UserAssignedMSI) The User-Assigned Managed Identity Resource used for the Bot's Authentication. -- **UMSIResourceGroupName**:(required for UserAssignedMSI) The User-Assigned Managed Identity Resource Group used for the Bot's Authentication. -- **tenantId**: The Azure AD Tenant ID to use as part of the Bot's Authentication. Only used for SingleTenant and UserAssignedMSI app types. Defaults to . - -MoreInfo: https://docs.microsoft.com/en-us/azure/bot-service/tutorial-provision-a-bot?view=azure-bot-service-4.0&tabs=userassigned%2Cnewgroup#create-an-identity-resource - - - -# parameters-for-template-AzureBot-new-rg: - -- **groupName**:(required) Specifies the name of the new Resource Group. -- **groupLocation**:(required) Specifies the location of the new Resource Group. - -- **azureBotId**:(required) The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable. -- **azureBotSku**: The pricing tier of the Bot Service Registration. **Allowed values are: F0, S1(default)**. -- **azureBotRegion**: Specifies the location of the new AzureBot. **Allowed values are: global(default), westeurope**. -- **botEndpoint**: Use to handle client messages, Such as https://.azurewebsites.net/api/messages. - -- **appType**: Type of Bot Authentication. set as MicrosoftAppType in the Web App's Application Settings. **Allowed values are: MultiTenant(default), SingleTenant, UserAssignedMSI.** -- **appId**:(required) Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings. -- **UMSIName**:(required for UserAssignedMSI) The User-Assigned Managed Identity Resource used for the Bot's Authentication. -- **UMSIResourceGroupName**:(required for UserAssignedMSI) The User-Assigned Managed Identity Resource Group used for the Bot's Authentication. -- **tenantId**: The Azure AD Tenant ID to use as part of the Bot's Authentication. Only used for SingleTenant and UserAssignedMSI app types. Defaults to . - -MoreInfo: https://docs.microsoft.com/en-us/azure/bot-service/tutorial-provision-a-bot?view=azure-bot-service-4.0&tabs=userassigned%2Cnewgroup#create-an-identity-resource \ No newline at end of file diff --git a/samples/bot-file-upload/csharp/TeamsFileUpload/DeploymentTemplates/DeployWithNewResourceGroup/template-AzureBot-new-rg.json b/samples/bot-file-upload/csharp/TeamsFileUpload/DeploymentTemplates/DeployWithNewResourceGroup/template-AzureBot-new-rg.json deleted file mode 100644 index ae073b7939..0000000000 --- a/samples/bot-file-upload/csharp/TeamsFileUpload/DeploymentTemplates/DeployWithNewResourceGroup/template-AzureBot-new-rg.json +++ /dev/null @@ -1,160 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupName": { - "type": "string", - "metadata": { - "description": "Specifies the name of the Resource Group." - } - }, - "groupLocation": { - "type": "string", - "metadata": { - "description": "Specifies the location of the Resource Group." - } - }, - "azureBotId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID." - } - }, - "azureBotSku": { - "type": "string", - "defaultValue": "S1", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "azureBotRegion": { - "type": "string", - "defaultValue": "global", - "metadata": { - "description": "" - } - }, - "botEndpoint": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Use to handle client messages, Such as https://.azurewebsites.net/api/messages." - } - }, - "appType": { - "type": "string", - "defaultValue": "MultiTenant", - "allowedValues": [ - "MultiTenant", - "SingleTenant", - "UserAssignedMSI" - ], - "metadata": { - "description": "Type of Bot Authentication. set as MicrosoftAppType in the Web App's Application Settings. Allowed values are: MultiTenant, SingleTenant, UserAssignedMSI. Defaults to \"MultiTenant\"." - } - }, - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "tenantId": { - "type": "string", - "defaultValue": "[subscription().tenantId]", - "metadata": { - "description": "The Azure AD Tenant ID to use as part of the Bot's Authentication. Only used for SingleTenant and UserAssignedMSI app types. Defaults to \"Subscription Tenant ID\"." - } - }, - "UMSIName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The User-Assigned Managed Identity Resource used for the Bot's Authentication." - } - }, - "UMSIResourceGroupName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The User-Assigned Managed Identity Resource Group used for the Bot's Authentication." - } - } - }, - "variables": { - "botEndpoint": "[if(empty(parameters('botEndpoint')), concat('https://', parameters('azureBotId'), '.azurewebsites.net/api/messages'), parameters('botEndpoint'))]", - "tenantId": "[if(empty(parameters('tenantId')), subscription().tenantId, parameters('tenantId'))]", - "msiResourceId": "[if(empty(parameters('UMSIName')), '', concat(subscription().id, '/resourceGroups/', parameters('UMSIResourceGroupName'), '/providers/', 'Microsoft.ManagedIdentity/userAssignedIdentities/', parameters('UMSIName')))]", - "appTypeDef": { - "MultiTenant": { - "tenantId": "", - "msiResourceId": "" - }, - "SingleTenant": { - "tenantId": "[variables('tenantId')]", - "msiResourceId": "" - }, - "UserAssignedMSI": { - "tenantId": "[variables('tenantId')]", - "msiResourceId": "[variables('msiResourceId')]" - } - }, - "appType": { - "tenantId": "[variables('appTypeDef')[parameters('appType')].tenantId]", - "msiResourceId": "[variables('appTypeDef')[parameters('appType')].msiResourceId]" - } - }, - "resources": [ - { - "name": "[parameters('groupName')]", - "type": "Microsoft.Resources/resourceGroups", - "apiVersion": "2018-05-01", - "location": "[parameters('groupLocation')]", - "properties": {} - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2018-05-01", - "name": "storageDeployment", - "resourceGroup": "[parameters('groupName')]", - "dependsOn": [ - "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" - ], - "properties": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": {}, - "variables": {}, - "resources": [ - { - "apiVersion": "2021-03-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('azureBotId')]", - "location": "[parameters('azureBotRegion')]", - "kind": "azurebot", - "sku": { - "name": "[parameters('azureBotSku')]" - }, - "properties": { - "name": "[parameters('azureBotId')]", - "displayName": "[parameters('azureBotId')]", - "iconUrl": "https://docs.botframework.com/static/devportal/client/images/bot-framework-default.png", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "msaAppTenantId": "[variables('appType').tenantId]", - "msaAppMSIResourceId": "[variables('appType').msiResourceId]", - "msaAppType": "[parameters('appType')]", - "luisAppIds": [], - "schemaTransformationVersion": "1.3", - "isCmekEnabled": false, - "isIsolated": false - } - } - ] - } - } - } - ] -} \ No newline at end of file diff --git a/samples/bot-file-upload/csharp/TeamsFileUpload/DeploymentTemplates/DeployWithNewResourceGroup/template-BotApp-new-rg.json b/samples/bot-file-upload/csharp/TeamsFileUpload/DeploymentTemplates/DeployWithNewResourceGroup/template-BotApp-new-rg.json deleted file mode 100644 index 560bbbc443..0000000000 --- a/samples/bot-file-upload/csharp/TeamsFileUpload/DeploymentTemplates/DeployWithNewResourceGroup/template-BotApp-new-rg.json +++ /dev/null @@ -1,213 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupName": { - "type": "string", - "metadata": { - "description": "Specifies the name of the Resource Group." - } - }, - "groupLocation": { - "type": "string", - "metadata": { - "description": "Specifies the location of the Resource Group." - } - }, - "appServiceName": { - "type": "string", - "metadata": { - "description": "The globally unique name of the Web App." - } - }, - "appServicePlanName": { - "type": "string", - "metadata": { - "description": "The name of the App Service Plan." - } - }, - "appServicePlanLocation": { - "type": "string", - "metadata": { - "description": "The location of the App Service Plan." - } - }, - "appServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "tenantId": { - "type": "string", - "defaultValue": "[subscription().tenantId]", - "metadata": { - "description": "The Azure AD Tenant ID to use as part of the Bot's Authentication. Only used for SingleTenant and UserAssignedMSI app types. Defaults to \"Subscription Tenant ID\"." - } - }, - "appType": { - "type": "string", - "defaultValue": "MultiTenant", - "allowedValues": [ - "MultiTenant", - "SingleTenant", - "UserAssignedMSI" - ], - "metadata": { - "description": "Type of Bot Authentication. set as MicrosoftAppType in the Web App's Application Settings. Allowed values are: MultiTenant, SingleTenant, UserAssignedMSI. Defaults to \"MultiTenant\"." - } - }, - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Required for MultiTenant and SingleTenant app types." - } - }, - "UMSIName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The User-Assigned Managed Identity Resource used for the Bot's Authentication." - } - }, - "UMSIResourceGroupName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The User-Assigned Managed Identity Resource Group used for the Bot's Authentication." - } - } - }, - "variables": { - "tenantId": "[if(empty(parameters('tenantId')), subscription().tenantId, parameters('tenantId'))]", - "appServicePlanName": "[parameters('appServicePlanName')]", - "resourcesLocation": "[if(empty(parameters('appServicePlanLocation')), parameters('groupLocation'), parameters('appServicePlanLocation'))]", - "appServiceName": "[parameters('appServiceName')]", - "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]", - "msiResourceId": "[if(empty(parameters('UMSIName')), '', concat(subscription().id, '/resourceGroups/', parameters('UMSIResourceGroupName'), '/providers/', 'Microsoft.ManagedIdentity/userAssignedIdentities/', parameters('UMSIName')))]", - "appTypeDef": { - "MultiTenant": { - "tenantId": "", - "identity": { "type": "None" } - }, - "SingleTenant": { - "tenantId": "[variables('tenantId')]", - "identity": { "type": "None" } - }, - "UserAssignedMSI": { - "tenantId": "[variables('tenantId')]", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": { - "[variables('msiResourceId')]": {} - } - } - } - }, - "appType": { - "tenantId": "[variables('appTypeDef')[parameters('appType')].tenantId]", - "identity": "[variables('appTypeDef')[parameters('appType')].identity]" - } - }, - "resources": [ - { - "name": "[parameters('groupName')]", - "type": "Microsoft.Resources/resourceGroups", - "apiVersion": "2018-05-01", - "location": "[parameters('groupLocation')]", - "properties": {} - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2018-05-01", - "name": "storageDeployment", - "resourceGroup": "[parameters('groupName')]", - "dependsOn": [ - "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" - ], - "properties": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": {}, - "variables": {}, - "resources": [ - { - "comments": "Create a new App Service Plan", - "type": "Microsoft.Web/serverfarms", - "name": "[variables('appServicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('resourcesLocation')]", - "sku": "[parameters('appServicePlanSku')]", - "properties": { - "name": "[variables('appServicePlanName')]" - } - }, - { - "comments": "Create a Web App using the new App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2015-08-01", - "location": "[variables('resourcesLocation')]", - "kind": "app", - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" - ], - "name": "[variables('appServiceName')]", - "identity": "[variables('appType').identity]", - "properties": { - "name": "[variables('appServiceName')]", - "serverFarmId": "[variables('appServicePlanName')]", - "siteConfig": { - "appSettings": [ - { - "name": "WEBSITE_NODE_DEFAULT_VERSION", - "value": "10.14.1" - }, - { - "name": "MicrosoftAppType", - "value": "[parameters('appType')]" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - }, - { - "name": "MicrosoftAppTenantId", - "value": "[variables('appType').tenantId]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - }, - "webSocketsEnabled": true - } - } - } - ], - "outputs": {} - } - } - } - ] -} \ No newline at end of file diff --git a/samples/bot-file-upload/csharp/TeamsFileUpload/Models/FileConsentCard.cs b/samples/bot-file-upload/csharp/TeamsFileUpload/Models/FileConsentCard.cs new file mode 100644 index 0000000000..c95ba861e1 --- /dev/null +++ b/samples/bot-file-upload/csharp/TeamsFileUpload/Models/FileConsentCard.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace TeamsFileUpload.Models +{ + /// + /// Represents a file consent card for requesting user permission to upload a file. + /// + public class FileConsentCard + { + /// + /// The content type identifier for file consent cards. + /// + public const string ContentType = "application/vnd.microsoft.teams.card.file.consent"; + + /// + /// Gets or sets the name of the file. + /// + public string Name { get; set; } + + /// + /// Gets or sets the description of the file. + /// + public string Description { get; set; } + + /// + /// Gets or sets the size of the file in bytes. + /// + public long SizeInBytes { get; set; } + + /// + /// Gets or sets the context data to include when user accepts. + /// + public object AcceptContext { get; set; } + + /// + /// Gets or sets the context data to include when user declines. + /// + public object DeclineContext { get; set; } + } +} diff --git a/samples/bot-file-upload/csharp/TeamsFileUpload/Models/FileDownloadInfo.cs b/samples/bot-file-upload/csharp/TeamsFileUpload/Models/FileDownloadInfo.cs new file mode 100644 index 0000000000..8521f4c075 --- /dev/null +++ b/samples/bot-file-upload/csharp/TeamsFileUpload/Models/FileDownloadInfo.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace TeamsFileUpload.Models +{ + /// + /// Contains information needed to download a file from Teams. + /// + public class FileDownloadInfo + { + /// + /// Gets or sets the URL to download the file from. + /// + public string DownloadUrl { get; set; } + + /// + /// Gets or sets the unique identifier for the file. + /// + public string UniqueId { get; set; } + + /// + /// Gets or sets the file type/extension. + /// + public string FileType { get; set; } + } +} diff --git a/samples/bot-file-upload/csharp/TeamsFileUpload/Models/FileInfoCard.cs b/samples/bot-file-upload/csharp/TeamsFileUpload/Models/FileInfoCard.cs new file mode 100644 index 0000000000..32f616427b --- /dev/null +++ b/samples/bot-file-upload/csharp/TeamsFileUpload/Models/FileInfoCard.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace TeamsFileUpload.Models +{ + /// + /// Represents a file info card containing metadata about an uploaded file. + /// + public class FileInfoCard + { + /// + /// The content type identifier for file info cards. + /// + public const string ContentType = "application/vnd.microsoft.teams.card.file.info"; + + /// + /// Gets or sets the unique identifier for the file. + /// + public string UniqueId { get; set; } + + /// + /// Gets or sets the file type/extension. + /// + public string FileType { get; set; } + } +} diff --git a/samples/bot-file-upload/csharp/TeamsFileUpload/Program.cs b/samples/bot-file-upload/csharp/TeamsFileUpload/Program.cs index c05099dda5..6b529b24d0 100644 --- a/samples/bot-file-upload/csharp/TeamsFileUpload/Program.cs +++ b/samples/bot-file-upload/csharp/TeamsFileUpload/Program.cs @@ -1,35 +1,50 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; +using TeamsFileUpload; +using TeamsFileUpload.Controllers; +using Azure.Core; +using Azure.Identity; +using Microsoft.Teams.Api.Auth; +using Microsoft.Teams.Apps; +using Microsoft.Teams.Apps.Extensions; +using Microsoft.Teams.Common.Http; +using Microsoft.Teams.Plugins.AspNetCore.Extensions; -namespace Microsoft.BotBuilderSamples +var builder = WebApplication.CreateBuilder(args); +var config = builder.Configuration.Get(); + +Func> createTokenFactory = async (string[] scopes, string? tenantId) => { - public class Program + var clientId = config.Teams.ClientId; + + var managedIdentityCredential = new ManagedIdentityCredential(clientId); + var tokenRequestContext = new TokenRequestContext(scopes, tenantId: tenantId); + var accessToken = await managedIdentityCredential.GetTokenAsync(tokenRequestContext); + + return new TokenResponse { - // The entry point of the application. - // This method is called when the application starts. - // It builds and runs the web host. - public static void Main(string[] args) + TokenType = "Bearer", + AccessToken = accessToken.Token, + }; +}; +var appBuilder = App.Builder(); + +if (config.Teams.BotType == "UserAssignedMsi") +{ + appBuilder.AddCredentials(new TokenCredentials( + config.Teams.ClientId ?? string.Empty, + async (tenantId, scopes) => { - CreateHostBuilder(args).Build().Run(); + return await createTokenFactory(scopes, tenantId); } - - // Configures the web host for the application. - // Adds logging services (Console and Debug) and sets up the Startup class. - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.ConfigureLogging((logging) => - { - // Add debug and console logging for local development. - logging.AddDebug(); - logging.AddConsole(); - }); - webBuilder.UseStartup(); - }); - } + )); } + +builder.Services.AddHttpClient(); +builder.Services.AddSingleton(); +builder.AddTeams(appBuilder); + +var app = builder.Build(); +app.UseTeams(); +app.Run(); \ No newline at end of file diff --git a/samples/bot-file-upload/csharp/TeamsFileUpload/Properties/launchSettings.json b/samples/bot-file-upload/csharp/TeamsFileUpload/Properties/launchSettings.json index d985e43c6f..3572a7a03f 100644 --- a/samples/bot-file-upload/csharp/TeamsFileUpload/Properties/launchSettings.json +++ b/samples/bot-file-upload/csharp/TeamsFileUpload/Properties/launchSettings.json @@ -1,13 +1,26 @@ { - "profiles": { - "Start Project": { - "commandName": "Project", - "dotnetRunMessages": true, - "applicationUrl": "https://localhost:7130;http://localhost:5130", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "hotReloadProfile": "aspnetcore" - } - } + "profiles": { + // Debug project within Microsoft 365 Agents Playground + "Microsoft 365 Agents Playground": { + "commandName": "Project", + "dotnetRunMessages": true, + "applicationUrl": "http://localhost:5130", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Playground", + "TEAMSFX_NOTIFICATION_STORE_FILENAME": ".notification.playgroundstore.json", + "UPDATE_TEAMS_APP": "false" + }, + "hotReloadProfile": "aspnetcore" + }, + // Debug project within Teams + "Start Project": { + "commandName": "Project", + "dotnetRunMessages": true, + "applicationUrl": "http://localhost:5130", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "hotReloadProfile": "aspnetcore" + }, + } } \ No newline at end of file diff --git a/samples/bot-file-upload/csharp/TeamsFileUpload/Startup.cs b/samples/bot-file-upload/csharp/TeamsFileUpload/Startup.cs deleted file mode 100644 index 84068bc5da..0000000000 --- a/samples/bot-file-upload/csharp/TeamsFileUpload/Startup.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Bot.Builder; -using Microsoft.Bot.Builder.Integration.AspNet.Core; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; - -using Microsoft.BotBuilderSamples.Bots; -using Microsoft.Extensions.Hosting; -using Microsoft.Bot.Connector.Authentication; - -namespace Microsoft.BotBuilderSamples -{ - public class Startup - { - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { - // Configure HttpClient and add necessary JSON settings for the bot - services.AddHttpClient() - .AddControllers() - .AddNewtonsoftJson(options => - { - options.SerializerSettings.MaxDepth = HttpHelper.BotMessageSerializerSettings.MaxDepth; - }); - - // Register the Bot Framework Authentication service (for authentication with the Bot Framework). - services.AddSingleton(); - - // Register the Bot Adapter with error handling enabled (helps in handling errors globally). - services.AddSingleton(); - - // The Bot needs an HttpClient to download and upload files, registered above. - // No need to register HttpClient again, as it's already added above. - - // Register the bot implementation (TeamsFileUploadBot) as a transient service. - services.AddTransient(); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - - app.UseDefaultFiles() - .UseStaticFiles() // Enable serving static files (like images, CSS, etc.) - .UseRouting() // Set up routing for controllers - .UseAuthorization() // Ensure authorization is handled - .UseEndpoints(endpoints => - { - // Map controller routes - endpoints.MapControllers(); - }); - - // Uncomment the line below to enable HTTPS redirection in production - // app.UseHttpsRedirection(); - } - } -} diff --git a/samples/bot-file-upload/csharp/TeamsFileUpload/TeamsFileUpload.csproj b/samples/bot-file-upload/csharp/TeamsFileUpload/TeamsFileUpload.csproj index 369cdb527d..0bcc6dc507 100644 --- a/samples/bot-file-upload/csharp/TeamsFileUpload/TeamsFileUpload.csproj +++ b/samples/bot-file-upload/csharp/TeamsFileUpload/TeamsFileUpload.csproj @@ -1,54 +1,36 @@ - + - net6.0 - latest + net10.0 + enable - - - + + + + + + - - Always + + + PreserveNewest + None + + + + PreserveNewest + None + - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - + PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - + - - - + \ No newline at end of file diff --git a/samples/bot-file-upload/csharp/TeamsFileUpload/appsettings.Development.json b/samples/bot-file-upload/csharp/TeamsFileUpload/appsettings.Development.json new file mode 100644 index 0000000000..f225c8442d --- /dev/null +++ b/samples/bot-file-upload/csharp/TeamsFileUpload/appsettings.Development.json @@ -0,0 +1,19 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + }, + "Microsoft.Teams": { + "Enable": "*", + "Level": "debug" + } + }, + "AllowedHosts": "*", + "Teams": { + "ClientId": "", + "ClientSecret": "", + "BotType": "", + "TenantId": "" + } +} \ No newline at end of file diff --git a/samples/bot-file-upload/csharp/TeamsFileUpload/appsettings.Playground.json b/samples/bot-file-upload/csharp/TeamsFileUpload/appsettings.Playground.json new file mode 100644 index 0000000000..0497106b3c --- /dev/null +++ b/samples/bot-file-upload/csharp/TeamsFileUpload/appsettings.Playground.json @@ -0,0 +1,19 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + }, + "Microsoft.Teams": { + "Enable": "*", + "Level": "debug" + } + }, + "AllowedHosts": "*", + "Teams": { + "ClientId": "", + "ClientSecret": "", + "TenantId": "", + "BotType": "" + } +} \ No newline at end of file diff --git a/samples/bot-file-upload/csharp/TeamsFileUpload/appsettings.json b/samples/bot-file-upload/csharp/TeamsFileUpload/appsettings.json index 289a79d660..9e3379db53 100644 --- a/samples/bot-file-upload/csharp/TeamsFileUpload/appsettings.json +++ b/samples/bot-file-upload/csharp/TeamsFileUpload/appsettings.json @@ -1,6 +1,18 @@ { - "MicrosoftAppType": "", - "MicrosoftAppId": "", - "MicrosoftAppPassword": "", - "MicrosoftAppTenantId": "" + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + }, + "Microsoft.Teams": { + "Enable": "*", + "Level": "debug" + } + }, + "AllowedHosts": "*", + "Teams": { + "ClientId": "", + "ClientSecret": "", + "BotType": "" + } } \ No newline at end of file diff --git a/samples/bot-file-upload/csharp/TeamsFileUpload/wwwroot/default.html b/samples/bot-file-upload/csharp/TeamsFileUpload/wwwroot/default.html deleted file mode 100644 index 1642b167a0..0000000000 --- a/samples/bot-file-upload/csharp/TeamsFileUpload/wwwroot/default.html +++ /dev/null @@ -1,292 +0,0 @@ - - - - - - - Teams File Upload Bot - - - - - -
-
-
-
File Upload Bot
-
-
-
-
-
Your bot is ready!
-
- You can now test your bot in Teams.
-
- Visit - Azure - Bot Service - to register your bot and add it to the
- Teams channel. The bot's endpoint URL typically looks - like this: -
-
https://your_bots_hostname/api/messages
-
-
-
- -
- - - \ No newline at end of file diff --git a/samples/bot-message-reaction/csharp/M365Agent/M365Agent.atkproj b/samples/bot-message-reaction/csharp/M365Agent/M365Agent.atkproj new file mode 100644 index 0000000000..124eb75046 --- /dev/null +++ b/samples/bot-message-reaction/csharp/M365Agent/M365Agent.atkproj @@ -0,0 +1,9 @@ + + + + b069b3bd-f6bc-cc40-82ab-3fcc2ea50fdf + + + + + \ No newline at end of file diff --git a/samples/bot-message-reaction/csharp/M365Agent/appPackage/manifest.json b/samples/bot-message-reaction/csharp/M365Agent/appPackage/manifest.json index 97594c8d70..d752486c45 100644 --- a/samples/bot-message-reaction/csharp/M365Agent/appPackage/manifest.json +++ b/samples/bot-message-reaction/csharp/M365Agent/appPackage/manifest.json @@ -1,6 +1,6 @@ { - "$schema": "https://developer.microsoft.com/json-schemas/teams/v1.19/MicrosoftTeams.schema.json", - "manifestVersion": "1.19", + "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.23/MicrosoftTeams.schema.json", + "manifestVersion": "1.23", "version": "1.0.0", "id": "${{TEAMS_APP_ID}}", "developer": { diff --git a/samples/bot-message-reaction/csharp/M365Agent/env/.env.dev b/samples/bot-message-reaction/csharp/M365Agent/env/.env.dev index 1943ce1cf5..df4f9da508 100644 --- a/samples/bot-message-reaction/csharp/M365Agent/env/.env.dev +++ b/samples/bot-message-reaction/csharp/M365Agent/env/.env.dev @@ -12,7 +12,4 @@ RESOURCE_SUFFIX= # Generated during provision, you can also add your own variables. BOT_ID= TEAMS_APP_ID= -BOT_AZURE_APP_SERVICE_RESOURCE_ID= -TEAMS_APP_TENANT_ID= -BOT_DOMAIN= -BOT_TENANT_ID= \ No newline at end of file +BOT_AZURE_APP_SERVICE_RESOURCE_ID= \ No newline at end of file diff --git a/samples/bot-message-reaction/csharp/M365Agent/env/.env.dev.user b/samples/bot-message-reaction/csharp/M365Agent/env/.env.dev.user new file mode 100644 index 0000000000..8afeed1c3c --- /dev/null +++ b/samples/bot-message-reaction/csharp/M365Agent/env/.env.dev.user @@ -0,0 +1,3 @@ +# This file includes environment variables that will not be committed to git by default. You can set these environment variables in your CI/CD system for your project. + +# Secrets. Keys prefixed with `SECRET_` will be masked in Microsoft 365 Agents Toolkit logs. \ No newline at end of file diff --git a/samples/bot-message-reaction/csharp/M365Agent/env/.env.local b/samples/bot-message-reaction/csharp/M365Agent/env/.env.local index a3329fccf4..86dfacc35a 100644 --- a/samples/bot-message-reaction/csharp/M365Agent/env/.env.local +++ b/samples/bot-message-reaction/csharp/M365Agent/env/.env.local @@ -7,10 +7,9 @@ APP_NAME_SUFFIX=local # Generated during provision, you can also add your own variables. BOT_ID= TEAMS_APP_ID= +TEAMS_APP_TENANT_ID= +BOT_OBJECT_ID= TEAMSFX_M365_USER_NAME= BOT_ENDPOINT= -BOT_DOMAIN= - -TEAMS_APP_TENANT_ID= -BOT_OBJECT_ID= \ No newline at end of file +BOT_DOMAIN= \ No newline at end of file diff --git a/samples/bot-message-reaction/csharp/M365Agent/env/.env.local.user b/samples/bot-message-reaction/csharp/M365Agent/env/.env.local.user new file mode 100644 index 0000000000..4d30df6f33 --- /dev/null +++ b/samples/bot-message-reaction/csharp/M365Agent/env/.env.local.user @@ -0,0 +1,5 @@ +# This file includes environment variables that will not be committed to git by default. You can set these environment variables in your CI/CD system for your project. + +# Secrets. Keys prefixed with `SECRET_` will be masked in Microsoft 365 Agents Toolkit logs. +SECRET_BOT_PASSWORD= +TEAMS_APP_UPDATE_TIME= \ No newline at end of file diff --git a/samples/bot-message-reaction/csharp/M365Agent/infra/azure.bicep b/samples/bot-message-reaction/csharp/M365Agent/infra/azure.bicep index 622703e047..658e412a21 100644 --- a/samples/bot-message-reaction/csharp/M365Agent/infra/azure.bicep +++ b/samples/bot-message-reaction/csharp/M365Agent/infra/azure.bicep @@ -43,15 +43,15 @@ resource webApp 'Microsoft.Web/sites@2021-02-01' = { value: '1' } { - name: 'BOT_ID' + name: 'Teams__ClientId' value: identity.properties.clientId } { - name: 'BOT_TENANT_ID' + name: 'Teams__TenantId' value: identity.properties.tenantId } { - name: 'BOT_TYPE' + name: 'Teams__BotType' value: 'UserAssignedMsi' } ] diff --git a/samples/bot-message-reaction/csharp/M365Agent/infra/botRegistration/readme.md b/samples/bot-message-reaction/csharp/M365Agent/infra/botRegistration/readme.md new file mode 100644 index 0000000000..d5416243cd --- /dev/null +++ b/samples/bot-message-reaction/csharp/M365Agent/infra/botRegistration/readme.md @@ -0,0 +1 @@ +The `azurebot.bicep` module is provided to help you create Azure Bot service when you don't use Azure to host your app. If you use Azure as infrastrcture for your app, `azure.bicep` under infra folder already leverages this module to create Azure Bot service for you. You don't need to deploy `azurebot.bicep` again. \ No newline at end of file diff --git a/samples/bot-message-reaction/csharp/M365Agent/launchSettings.json b/samples/bot-message-reaction/csharp/M365Agent/launchSettings.json index ff528e2eda..2af8ce7a8a 100644 --- a/samples/bot-message-reaction/csharp/M365Agent/launchSettings.json +++ b/samples/bot-message-reaction/csharp/M365Agent/launchSettings.json @@ -1,9 +1,19 @@ { "profiles": { + // Launch project within Microsoft 365 Agents Playground + "Microsoft 365 Agents Playground (browser)": { + "commandName": "Project", + "environmentVariables": { + "UPDATE_TEAMS_APP": "false", + "M365_AGENTS_PLAYGROUND_TARGET_SDK": "teams-ai-v2-dotnet" + }, + "launchTestTool": true, + "launchUrl": "http://localhost:56150", + }, // Launch project within Teams "Microsoft Teams (browser)": { "commandName": "Project", - "launchUrl": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&appTenantId=${{TEAMS_APP_TENANT_ID}}&login_hint=${{TEAMSFX_M365_USER_NAME}}" + "launchUrl": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&appTenantId=${{TEAMS_APP_TENANT_ID}}&login_hint=${{TEAMSFX_M365_USER_NAME}}", }, // Launch project within Teams without prepare app dependencies "Microsoft Teams (browser) (skip update app)": { @@ -11,11 +21,5 @@ "environmentVariables": { "UPDATE_TEAMS_APP": "false" }, "launchUrl": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&appTenantId=${{TEAMS_APP_TENANT_ID}}&login_hint=${{TEAMSFX_M365_USER_NAME}}" }, - // Launch project within Teams App Test Tool - "Teams App Test Tool (browser)": { - "commandName": "Project", - "launchTestTool": true, - "launchUrl": "http://localhost:56150" - } } } \ No newline at end of file diff --git a/samples/bot-message-reaction/csharp/M365Agent/m365agents.local.yml b/samples/bot-message-reaction/csharp/M365Agent/m365agents.local.yml index 00e53fa91b..17ba3f195d 100644 --- a/samples/bot-message-reaction/csharp/M365Agent/m365agents.local.yml +++ b/samples/bot-message-reaction/csharp/M365Agent/m365agents.local.yml @@ -1,7 +1,7 @@ -# yaml-language-server: $schema=https://aka.ms/teams-toolkit/v1.7/yaml.schema.json +# yaml-language-server: $schema=https://aka.ms/m365-agents-toolkits/v1.11/yaml.schema.json # Visit https://aka.ms/teamsfx-v5.0-guide for details on this file # Visit https://aka.ms/teamsfx-actions for details on actions -version: v1.7 +version: v1.11 provision: # Creates a Teams app @@ -20,6 +20,7 @@ provision: # The Microsoft Entra application's display name name: MessageReaction${{APP_NAME_SUFFIX}} generateClientSecret: true + generateServicePrincipal: true signInAudience: AzureADMultipleOrgs writeToEnvironmentFile: # The Microsoft Entra application's client id created for bot. @@ -34,9 +35,10 @@ provision: with: target: ../MessageReaction/appsettings.Development.json content: - BOT_TYPE: 'MultiTenant' - BOT_ID: ${{BOT_ID}} - BOT_PASSWORD: ${{SECRET_BOT_PASSWORD}} + Teams: + ClientId: ${{BOT_ID}} + ClientSecret: ${{SECRET_BOT_PASSWORD}} + TenantId: ${{TEAMS_APP_TENANT_ID}} # Create or update the bot registration on dev.botframework.com - uses: botFramework/create diff --git a/samples/bot-message-reaction/csharp/M365Agent/m365agents.yml b/samples/bot-message-reaction/csharp/M365Agent/m365agents.yml index 61c44fb2dd..692d13f974 100644 --- a/samples/bot-message-reaction/csharp/M365Agent/m365agents.yml +++ b/samples/bot-message-reaction/csharp/M365Agent/m365agents.yml @@ -1,7 +1,7 @@ -# yaml-language-server: $schema=https://aka.ms/teams-toolkit/v1.7/yaml.schema.json +# yaml-language-server: $schema=https://aka.ms/m365-agents-toolkits/v1.9/yaml.schema.json # Visit https://aka.ms/teamsfx-v5.0-guide for details on this file # Visit https://aka.ms/teamsfx-actions for details on actions -version: v1.7 +version: v1.9 environmentFolderPath: ./env @@ -71,14 +71,15 @@ provision: deploy: - uses: cli/runDotnetCommand with: - args: publish --configuration Release MessageReaction.csproj + args: publish --configuration Release --runtime win-x86 --self-contained + MessageReaction.csproj workingDirectory: ../MessageReaction # Deploy your application to Azure App Service using the zip deploy feature. # For additional details, refer to https://aka.ms/zip-deploy-to-app-services. - uses: azureAppService/zipDeploy with: # Deploy base folder - artifactFolder: bin/Release/net8.0/publish + artifactFolder: bin/Release/net10.0/win-x86/publish # The resource id of the cloud resource to be deployed to. # This key will be generated by arm/deploy action automatically. # You can replace it with your existing Azure Resource id diff --git a/samples/bot-message-reaction/csharp/MessageReaction.sln b/samples/bot-message-reaction/csharp/MessageReaction.sln deleted file mode 100644 index 5a137066ac..0000000000 --- a/samples/bot-message-reaction/csharp/MessageReaction.sln +++ /dev/null @@ -1,37 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.13.35806.99 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MessageReaction", "MessageReaction\MessageReaction.csproj", "{ABC85E03-2420-115A-7FC4-16638E84561F}" -EndProject -Project("{A9E3F50B-275E-4AF7-ADCE-8BE12D41E305}") = "M365Agent", "M365Agent\M365Agent.ttkproj", "{3DF7DBBF-6757-C96E-D6EC-3928332DA9E0}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8EC462FD-D22E-90A8-E5CE-7E832BA40C5D}" - ProjectSection(SolutionItems) = preProject - MessageReaction.slnLaunch.user = MessageReaction.slnLaunch.user - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {ABC85E03-2420-115A-7FC4-16638E84561F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {ABC85E03-2420-115A-7FC4-16638E84561F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {ABC85E03-2420-115A-7FC4-16638E84561F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {ABC85E03-2420-115A-7FC4-16638E84561F}.Release|Any CPU.Build.0 = Release|Any CPU - {3DF7DBBF-6757-C96E-D6EC-3928332DA9E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3DF7DBBF-6757-C96E-D6EC-3928332DA9E0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3DF7DBBF-6757-C96E-D6EC-3928332DA9E0}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {3DF7DBBF-6757-C96E-D6EC-3928332DA9E0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3DF7DBBF-6757-C96E-D6EC-3928332DA9E0}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {6883B0E0-F90A-405D-B6E5-A12F868A7E20} - EndGlobalSection -EndGlobal diff --git a/samples/bot-message-reaction/csharp/MessageReaction.slnLaunch.user b/samples/bot-message-reaction/csharp/MessageReaction.slnLaunch.user index 07caf9fb9b..c4f26309cc 100644 --- a/samples/bot-message-reaction/csharp/MessageReaction.slnLaunch.user +++ b/samples/bot-message-reaction/csharp/MessageReaction.slnLaunch.user @@ -1,46 +1,52 @@ [ { - "Name": "Microsoft Teams (browser)", + "Name": "Microsoft 365 Agents Playground (browser)", "Projects": [ { - "Path": "M365Agent\\M365Agent.ttkproj", + "Path": "M365Agent\\M365Agent.atkproj", + "Name": "M365Agent\\M365Agent.atkproj", "Action": "StartWithoutDebugging", - "DebugTarget": "Microsoft Teams (browser)" + "DebugTarget": "Microsoft 365 Agents Playground (browser)" }, { "Path": "MessageReaction\\MessageReaction.csproj", + "Name": "MessageReaction\\MessageReaction.csproj", "Action": "Start", - "DebugTarget": "Start Project" + "DebugTarget": "Microsoft 365 Agents Playground" } ] }, { - "Name": "Teams App Test Tool (browser)", + "Name": "Microsoft Teams (browser)", "Projects": [ { - "Path": "M365Agent\\M365Agent.ttkproj", + "Path": "M365Agent\\M365Agent.atkproj", + "Name": "M365Agent\\M365Agent.atkproj", "Action": "StartWithoutDebugging", - "DebugTarget": "Teams App Test Tool (browser)" + "DebugTarget": "Microsoft Teams (browser)" }, { "Path": "MessageReaction\\MessageReaction.csproj", + "Name": "MessageReaction\\MessageReaction.csproj", "Action": "Start", - "DebugTarget": "Teams App Test Tool" + "DebugTarget": "Start Project" } ] }, { "Name": "Microsoft Teams (browser) (skip update app)", "Projects": [ + { + "Path": "M365Agent\\M365Agent.atkproj", + "Name": "M365Agent\\M365Agent.atkproj", + "Action": "StartWithoutDebugging", + "DebugTarget": "Microsoft Teams (browser) (skip update app)" + }, { "Path": "MessageReaction\\MessageReaction.csproj", + "Name": "MessageReaction\\MessageReaction.csproj", "Action": "Start", "DebugTarget": "Start Project" - }, - { - "Path": "M365Agent\\M365Agent.ttkproj", - "Action": "StartWithoutDebugging", - "DebugTarget": "Microsoft Teams (browser) (skip update app)" } ] } diff --git a/samples/bot-message-reaction/csharp/MessageReaction.slnx b/samples/bot-message-reaction/csharp/MessageReaction.slnx new file mode 100644 index 0000000000..840f479b46 --- /dev/null +++ b/samples/bot-message-reaction/csharp/MessageReaction.slnx @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/samples/bot-message-reaction/csharp/MessageReaction/.gitignore b/samples/bot-message-reaction/csharp/MessageReaction/.gitignore index 41e1234dd7..77c7154916 100644 --- a/samples/bot-message-reaction/csharp/MessageReaction/.gitignore +++ b/samples/bot-message-reaction/csharp/MessageReaction/.gitignore @@ -5,7 +5,7 @@ env/.env.*.user env/.env.local appsettings.Development.json .deployment -appsettings.TestTool.json +appsettings.Playground.json # User-specific files *.user @@ -24,7 +24,7 @@ bld/ # Notification local store .notification.localstore.json -.notification.testtoolstore.json +.notification.playgroundstore.json # devTools devTools/ \ No newline at end of file diff --git a/samples/bot-message-reaction/csharp/MessageReaction/AdapterWithErrorHandler.cs b/samples/bot-message-reaction/csharp/MessageReaction/AdapterWithErrorHandler.cs deleted file mode 100644 index 2994dcb2d5..0000000000 --- a/samples/bot-message-reaction/csharp/MessageReaction/AdapterWithErrorHandler.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Microsoft.Bot.Builder.Integration.AspNet.Core; -using Microsoft.Bot.Builder.TraceExtensions; -using Microsoft.Bot.Connector.Authentication; -using Microsoft.Bot.Schema; - -namespace MessageReaction; - -public class AdapterWithErrorHandler : CloudAdapter -{ - public AdapterWithErrorHandler(BotFrameworkAuthentication auth, ILogger logger) - : base(auth, logger) - { - OnTurnError = async (turnContext, exception) => - { - // Log any leaked exception from the application. - // NOTE: In production environment, you should consider logging this to - // Azure Application Insights. Visit https://aka.ms/bottelemetry to see how - // to add telemetry capture to your bot. - logger.LogError(exception, $"[OnTurnError] unhandled error : {exception.Message}"); - - // Only send error message for user messages, not for other message types so the bot doesn't spam a channel or chat. - if (turnContext.Activity.Type == ActivityTypes.Message) - { - // Send a message to the user - await turnContext.SendActivityAsync("The bot encountered an error or bug."); - await turnContext.SendActivityAsync("To continue to run this bot, please fix the bot source code."); - - // Send a trace activity, which will be displayed in the Bot Framework Emulator - await turnContext.TraceActivityAsync("OnTurnError Trace", exception.Message, "https://www.botframework.com/schemas/error", "TurnError"); - } - }; - } -} \ No newline at end of file diff --git a/samples/bot-message-reaction/csharp/MessageReaction/Bot/MessageReactionBot.cs b/samples/bot-message-reaction/csharp/MessageReaction/Bot/MessageReactionBot.cs deleted file mode 100644 index e6f2ddbc26..0000000000 --- a/samples/bot-message-reaction/csharp/MessageReaction/Bot/MessageReactionBot.cs +++ /dev/null @@ -1,60 +0,0 @@ -using MessageReaction.Log; -using Microsoft.Bot.Builder; -using Microsoft.Bot.Builder.Teams; -using Microsoft.Bot.Schema; - -namespace MessageReaction.Bot -{ - public class MessageReactionBot : TeamsActivityHandler - { - private readonly ActivityLog _log; - - public MessageReactionBot(ActivityLog log) - { - _log = log; - } - - protected override async Task OnMessageActivityAsync(ITurnContext turnContext, CancellationToken cancellationToken) - { - await SendMessageAndLogActivityId(turnContext, $"echo: {turnContext.Activity.Text}", cancellationToken); - } - - protected override async Task OnReactionsAddedAsync(IList messageReactions, ITurnContext turnContext, CancellationToken cancellationToken) - { - await HandleReactionsAsync(messageReactions, turnContext, cancellationToken, "added"); - } - - protected override async Task OnReactionsRemovedAsync(IList messageReactions, ITurnContext turnContext, CancellationToken cancellationToken) - { - await HandleReactionsAsync(messageReactions, turnContext, cancellationToken, "removed"); - } - - private async Task HandleReactionsAsync(IList messageReactions, ITurnContext turnContext, CancellationToken cancellationToken, string action) - { - foreach (var reaction in messageReactions) - { - // The ReplyToId property of the inbound MessageReaction Activity will correspond to a Message Activity which - // had previously been sent from this bot. - var activity = await _log.FindAsync(turnContext.Activity.ReplyToId); - if (activity == null) - { - // If we had sent the message from the error handler we wouldn't have recorded the Activity Id and so we - // shouldn't expect to see it in the log. - await SendMessageAndLogActivityId(turnContext, $"Activity {turnContext.Activity.ReplyToId} not found in the log.", cancellationToken); - continue; - } - - await SendMessageAndLogActivityId(turnContext, $"You {action} '{reaction.Type}' regarding '{activity.Text}'", cancellationToken); - } - } - - private async Task SendMessageAndLogActivityId(ITurnContext turnContext, string text, CancellationToken cancellationToken) - { - // We need to record the Activity Id from the Activity just sent in order to understand what the reaction is a reaction too. - var replyActivity = MessageFactory.Text(text); - var resourceResponse = await turnContext.SendActivityAsync(replyActivity, cancellationToken); - await _log.AppendAsync(resourceResponse.Id, replyActivity); - } - } -} - diff --git a/samples/bot-message-reaction/csharp/MessageReaction/Config.cs b/samples/bot-message-reaction/csharp/MessageReaction/Config.cs index f5cf68cd98..5c4773c544 100644 --- a/samples/bot-message-reaction/csharp/MessageReaction/Config.cs +++ b/samples/bot-message-reaction/csharp/MessageReaction/Config.cs @@ -2,9 +2,14 @@ namespace MessageReaction { public class ConfigOptions { - public string BOT_ID { get; set; } - public string BOT_PASSWORD { get; set; } - public string BOT_TYPE { get; set; } - public string BOT_TENANT_ID { get; set; } + public TeamsConfigOptions Teams { get; set; } } -} + + public class TeamsConfigOptions + { + public string BotType { get; set; } + public string ClientId { get; set; } + public string ClientSecret { get; set; } + public string TenantId { get; set; } + } +} \ No newline at end of file diff --git a/samples/bot-message-reaction/csharp/MessageReaction/Controllers/BotController.cs b/samples/bot-message-reaction/csharp/MessageReaction/Controllers/BotController.cs deleted file mode 100644 index 72d5cf93f9..0000000000 --- a/samples/bot-message-reaction/csharp/MessageReaction/Controllers/BotController.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.Bot.Builder; -using Microsoft.Bot.Builder.Integration.AspNet.Core; - -namespace MessageReaction.Controllers; - -// This ASP Controller is created to handle a request. Dependency Injection will provide the Adapter and IBot -// implementation at runtime. Multiple different IBot implementations running at different endpoints can be -// achieved by specifying a more specific type for the bot constructor argument. -[Route("api/messages")] -[ApiController] -public class BotController : ControllerBase -{ - private readonly IBotFrameworkHttpAdapter Adapter; - private readonly IBot Bot; - - public BotController(IBotFrameworkHttpAdapter adapter, IBot bot) - { - Adapter = adapter; - Bot = bot; - } - - [HttpPost, HttpGet] - public async Task PostAsync() - { - // Delegate the processing of the HTTP POST to the adapter. - // The adapter will invoke the bot. - await Adapter.ProcessAsync(Request, Response, Bot); - } -} \ No newline at end of file diff --git a/samples/bot-message-reaction/csharp/MessageReaction/Controllers/Controller.cs b/samples/bot-message-reaction/csharp/MessageReaction/Controllers/Controller.cs new file mode 100644 index 0000000000..84f59dc4de --- /dev/null +++ b/samples/bot-message-reaction/csharp/MessageReaction/Controllers/Controller.cs @@ -0,0 +1,86 @@ +using Microsoft.Teams.Api.Activities; +using Microsoft.Teams.Apps; +using Microsoft.Teams.Apps.Activities; +using Microsoft.Teams.Apps.Annotations; +using System.Collections.Concurrent; + +namespace MessageReaction.Controllers +{ + [TeamsController] + public class Controller() + { + // Store sent messages in memory (activityId -> messageText) + private static readonly ConcurrentDictionary _messageLog = new(); + + [Message] + public async Task OnMessage([Context] MessageActivity activity, [Context] IContext.Client client, [Context] Microsoft.Teams.Common.Logging.ILogger log) + { + log.Info("hit!"); + await client.Typing(); + var response = await client.Send($"you said '{activity.Text}'"); + + // Store the sent message for later reference + if (response?.Id != null) + { + _messageLog[response.Id] = $"you said '{activity.Text}'"; + } + } + + [Conversation.MembersAdded] + public async Task OnMembersAdded(IContext context) + { + var welcomeText = "How can I help you today?"; + foreach (var member in context.Activity.MembersAdded) + { + if (member.Id != context.Activity.Recipient.Id) + { + var response = await context.Send(welcomeText); + + // Store the sent message + if (response?.Id != null) + { + _messageLog[response.Id] = welcomeText; + } + } + } + } + + [Message.ReactionAdded] + public async Task OnReactionAdded(IContext context) + { + var reactionsAdded = context.Activity.ReactionsAdded; + if (reactionsAdded != null && reactionsAdded.Count > 0) + { + foreach (var reaction in reactionsAdded) + { + var replyToId = context.Activity.ReplyToId; + var originalMessage = _messageLog.TryGetValue(replyToId ?? "", out var msg) + ? msg + : replyToId; + + var message = $"You reacted with '{reaction.Type}' to the following message: '{originalMessage}'"; + await context.Send(message); + } + } + } + + [Message.ReactionRemoved] + public async Task OnReactionRemoved(IContext context) + { + var reactionsRemoved = context.Activity.ReactionsRemoved; + if (reactionsRemoved != null && reactionsRemoved.Count > 0) + { + foreach (var reaction in reactionsRemoved) + { + var replyToId = context.Activity.ReplyToId; + var originalMessage = _messageLog.TryGetValue(replyToId ?? "", out var msg) + ? msg + : replyToId; + + var message = $"You removed the reaction '{reaction.Type}' from the following message: '{originalMessage}'"; + await context.Send(message); + } + } + } + } +} \ No newline at end of file diff --git a/samples/bot-message-reaction/csharp/MessageReaction/Log/ActivityLog.cs b/samples/bot-message-reaction/csharp/MessageReaction/Log/ActivityLog.cs deleted file mode 100644 index 439f9a6296..0000000000 --- a/samples/bot-message-reaction/csharp/MessageReaction/Log/ActivityLog.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Microsoft.Bot.Builder; -using Microsoft.Bot.Schema; - -namespace MessageReaction.Log -{ - /// - /// Manages the logging of activities. - /// - public class ActivityLog - { - private readonly IStorage _storage; - - /// - /// Initializes a new instance of the class. - /// - /// The storage to use for logging activities. - public ActivityLog(IStorage storage) - { - _storage = storage ?? throw new ArgumentNullException(nameof(storage)); - } - - /// - /// Appends an activity to the log. - /// - /// The ID of the activity. - /// The activity to log. - /// A task that represents the asynchronous operation. - /// Thrown when activityId or activity is null. - public async Task AppendAsync(string activityId, Activity activity) - { - if (string.IsNullOrEmpty(activityId)) - { - throw new ArgumentNullException(nameof(activityId)); - } - - if (activity == null) - { - throw new ArgumentNullException(nameof(activity)); - } - - var data = new Dictionary { { activityId, activity } }; - await _storage.WriteAsync(data); - } - - /// - /// Finds an activity in the log by its ID. - /// - /// The ID of the activity to find. - /// The activity if found; otherwise, null. - /// Thrown when activityId is null. - public async Task FindAsync(string activityId) - { - if (string.IsNullOrEmpty(activityId)) - { - throw new ArgumentNullException(nameof(activityId)); - } - - var activities = await _storage.ReadAsync(new[] { activityId }); - return activities.TryGetValue(activityId, out var activity) ? (Activity)activity : null; - } - } -} \ No newline at end of file diff --git a/samples/bot-message-reaction/csharp/MessageReaction/MessageReaction.csproj b/samples/bot-message-reaction/csharp/MessageReaction/MessageReaction.csproj index a4a8d975a1..d7886df2e7 100644 --- a/samples/bot-message-reaction/csharp/MessageReaction/MessageReaction.csproj +++ b/samples/bot-message-reaction/csharp/MessageReaction/MessageReaction.csproj @@ -1,14 +1,29 @@ - net8.0 + net10.0 enable - - - + + + + + - + + + + + PreserveNewest + None + + + + PreserveNewest + None + + + \ No newline at end of file diff --git a/samples/bot-message-reaction/csharp/MessageReaction/Program.cs b/samples/bot-message-reaction/csharp/MessageReaction/Program.cs index 12f6347ab7..8cea3959b8 100644 --- a/samples/bot-message-reaction/csharp/MessageReaction/Program.cs +++ b/samples/bot-message-reaction/csharp/MessageReaction/Program.cs @@ -1,55 +1,46 @@ using MessageReaction; -using MessageReaction.Bot; -using MessageReaction.Log; -using Microsoft.Bot.Builder; -using Microsoft.Bot.Builder.Integration.AspNet.Core; -using Microsoft.Bot.Connector.Authentication; +using MessageReaction.Controllers; +using Azure.Core; +using Azure.Identity; +using Microsoft.Teams.Api.Auth; +using Microsoft.Teams.Apps; +using Microsoft.Teams.Apps.Extensions; +using Microsoft.Teams.Common.Http; +using Microsoft.Teams.Plugins.AspNetCore.Extensions; var builder = WebApplication.CreateBuilder(args); - -builder.Services.AddControllers(); -builder.Services.AddHttpClient("WebClient", client => client.Timeout = TimeSpan.FromSeconds(600)); -builder.Services.AddHttpContextAccessor(); - -// Create the Bot Framework Authentication to be used with the Bot Adapter. var config = builder.Configuration.Get(); -builder.Configuration["MicrosoftAppType"] = config.BOT_TYPE; -builder.Configuration["MicrosoftAppId"] = config.BOT_ID; -builder.Configuration["MicrosoftAppPassword"] = config.BOT_PASSWORD; -builder.Configuration["MicrosoftAppTenantId"] = config.BOT_TENANT_ID; -builder.Services.AddSingleton(); - -// Create the storage we'll be using for User and Conversation state. (Memory is great for testing purposes.) -builder.Services.AddSingleton(); -// Create the ActivityLog. -// Used to store sent activities in order to correlate Message Reactions with -// the previously sent activity. -builder.Services.AddSingleton(); - -// Create the Bot Framework Adapter with error handling enabled. -builder.Services.AddSingleton(); +Func> createTokenFactory = async (string[] scopes, string? tenantId) => +{ + var clientId = config.Teams.ClientId; -// Create the bot as a transient. In this case the ASP Controller is expecting an IBot. -builder.Services.AddTransient(); + var managedIdentityCredential = new ManagedIdentityCredential(clientId); + var tokenRequestContext = new TokenRequestContext(scopes, tenantId: tenantId); + var accessToken = await managedIdentityCredential.GetTokenAsync(tokenRequestContext); -var app = builder.Build(); + return new TokenResponse + { + TokenType = "Bearer", + AccessToken = accessToken.Token, + }; +}; +var appBuilder = App.Builder(); -if (app.Environment.IsDevelopment()) +if (config.Teams.BotType == "UserAssignedMsi") { - app.UseDeveloperExceptionPage(); + appBuilder.AddCredentials(new TokenCredentials( + config.Teams.ClientId ?? string.Empty, + async (tenantId, scopes) => + { + return await createTokenFactory(scopes, tenantId); + } + )); } -app.UseDefaultFiles(); -app.UseStaticFiles(); - -app.UseRouting(); -app.UseAuthentication(); -app.UseAuthorization(); - -app.UseEndpoints(endpoints => -{ - endpoints.MapControllers(); -}); +builder.Services.AddSingleton(); +builder.AddTeams(appBuilder); +var app = builder.Build(); +app.UseTeams(); app.Run(); \ No newline at end of file diff --git a/samples/bot-message-reaction/csharp/MessageReaction/Properties/launchSettings.json b/samples/bot-message-reaction/csharp/MessageReaction/Properties/launchSettings.json index 9e9986ff50..3572a7a03f 100644 --- a/samples/bot-message-reaction/csharp/MessageReaction/Properties/launchSettings.json +++ b/samples/bot-message-reaction/csharp/MessageReaction/Properties/launchSettings.json @@ -1,23 +1,24 @@ { "profiles": { - // Debug project within Teams - "Start Project": { + // Debug project within Microsoft 365 Agents Playground + "Microsoft 365 Agents Playground": { "commandName": "Project", "dotnetRunMessages": true, - "applicationUrl": "http://localhost:3978", + "applicationUrl": "http://localhost:5130", "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" + "ASPNETCORE_ENVIRONMENT": "Playground", + "TEAMSFX_NOTIFICATION_STORE_FILENAME": ".notification.playgroundstore.json", + "UPDATE_TEAMS_APP": "false" }, "hotReloadProfile": "aspnetcore" }, - // Debug project within Teams App Test Tool - "Teams App Test Tool": { + // Debug project within Teams + "Start Project": { "commandName": "Project", "dotnetRunMessages": true, - "applicationUrl": "http://localhost:3978", + "applicationUrl": "http://localhost:5130", "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "TestTool", - "TEAMSFX_NOTIFICATION_STORE_FILENAME": ".notification.testtoolstore.json" + "ASPNETCORE_ENVIRONMENT": "Development" }, "hotReloadProfile": "aspnetcore" }, diff --git a/samples/bot-message-reaction/csharp/MessageReaction/appsettings.Development.json b/samples/bot-message-reaction/csharp/MessageReaction/appsettings.Development.json index 1e428a3c2c..f225c8442d 100644 --- a/samples/bot-message-reaction/csharp/MessageReaction/appsettings.Development.json +++ b/samples/bot-message-reaction/csharp/MessageReaction/appsettings.Development.json @@ -1,5 +1,19 @@ { - "BOT_TYPE": "", - "BOT_ID": "", - "BOT_PASSWORD": "" + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + }, + "Microsoft.Teams": { + "Enable": "*", + "Level": "debug" + } + }, + "AllowedHosts": "*", + "Teams": { + "ClientId": "", + "ClientSecret": "", + "BotType": "", + "TenantId": "" + } } \ No newline at end of file diff --git a/samples/bot-message-reaction/csharp/MessageReaction/appsettings.Playground.json b/samples/bot-message-reaction/csharp/MessageReaction/appsettings.Playground.json new file mode 100644 index 0000000000..0497106b3c --- /dev/null +++ b/samples/bot-message-reaction/csharp/MessageReaction/appsettings.Playground.json @@ -0,0 +1,19 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + }, + "Microsoft.Teams": { + "Enable": "*", + "Level": "debug" + } + }, + "AllowedHosts": "*", + "Teams": { + "ClientId": "", + "ClientSecret": "", + "TenantId": "", + "BotType": "" + } +} \ No newline at end of file diff --git a/samples/bot-message-reaction/csharp/MessageReaction/appsettings.TestTool.json b/samples/bot-message-reaction/csharp/MessageReaction/appsettings.TestTool.json deleted file mode 100644 index 1a3d21d69a..0000000000 --- a/samples/bot-message-reaction/csharp/MessageReaction/appsettings.TestTool.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" - } - }, - "AllowedHosts": "*", - "BOT_ID": "", - "BOT_PASSWORD": "" -} \ No newline at end of file diff --git a/samples/bot-message-reaction/csharp/MessageReaction/appsettings.json b/samples/bot-message-reaction/csharp/MessageReaction/appsettings.json index 9578f3c646..9e3379db53 100644 --- a/samples/bot-message-reaction/csharp/MessageReaction/appsettings.json +++ b/samples/bot-message-reaction/csharp/MessageReaction/appsettings.json @@ -2,13 +2,17 @@ "Logging": { "LogLevel": { "Default": "Information", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" + "Microsoft.AspNetCore": "Warning" + }, + "Microsoft.Teams": { + "Enable": "*", + "Level": "debug" } }, "AllowedHosts": "*", - "BOT_ID": "", - "BOT_PASSWORD": "", - "BOT_TYPE": "", - "BOT_TENANT_ID": "" -} + "Teams": { + "ClientId": "", + "ClientSecret": "", + "BotType": "" + } +} \ No newline at end of file diff --git a/samples/bot-message-reaction/csharp/MessageReaction/wwwroot/default.htm b/samples/bot-message-reaction/csharp/MessageReaction/wwwroot/default.htm deleted file mode 100644 index bb939b1fb2..0000000000 --- a/samples/bot-message-reaction/csharp/MessageReaction/wwwroot/default.htm +++ /dev/null @@ -1,292 +0,0 @@ - - - - - - - Teams Message Reaction Bot - - - - - -
-
-
-
Teams Message Reaction Bot
-
-
-
-
-
Your bot is ready!
-
- You can now test your bot in Teams.
-
- Visit - Azure - Bot Service - to register your bot and add it to the
- Teams channel. The bot's endpoint URL typically looks - like this: -
-
https://your_bots_hostname/api/messages
-
-
-
- -
- - - \ No newline at end of file diff --git a/samples/bot-message-reaction/csharp/README.md b/samples/bot-message-reaction/csharp/README.md index 6151e7651f..664c949e44 100644 --- a/samples/bot-message-reaction/csharp/README.md +++ b/samples/bot-message-reaction/csharp/README.md @@ -1,6 +1,6 @@ --- page_type: sample -description: This sample app demonstrates how to use message reactions in Microsoft Teams with a bot built on the Bot Framework. +description: This sample app demonstrates how to use message reactions in Microsoft Teams SDK. products: - office-teams - office @@ -15,11 +15,13 @@ urlFragment: officedev-microsoft-teams-samples-bot-message-reaction-csharp # Teams Message Reactions Bot C# Sample -This sample app demonstrates the implementation of message reactions in Microsoft Teams using the Bot Framework. The bot responds dynamically to reactions, supporting personal, group, and team scopes, and is compatible with adaptive cards. It can be run locally with .NET SDK and tunneling solutions or deployed to Azure for broader use. +This sample app demonstrates the implementation of message reactions in Microsoft Teams using the **Microsoft Teams SDK v2.0** (Teams AI Library). The bot responds dynamically to reactions, supporting personal, group, and team scopes. ## Included Features -* Bots -* Adaptive Cards +* Microsoft Teams SDK v2.0 (Teams AI Library) +* Message Reaction Events +* Bot Framework +* Multi-scope support (Personal, Group Chat, Team) ## Interaction with bot ![bot-message-reaction ](MessageReaction/Images/bot-message-reaction.gif) @@ -32,8 +34,9 @@ Please find below demo manifest which is deployed on Microsoft Azure and you can ## Prerequisites - Microsoft Teams is installed and you have an account -- [.NET SDK](https://dotnet.microsoft.com/download) version 6.0 +- [.NET SDK](https://dotnet.microsoft.com/download) version 10.0 or higher - [dev tunnel](https://learn.microsoft.com/en-us/azure/developer/dev-tunnels/get-started?tabs=windows) or [ngrok](https://ngrok.com/) latest version or equivalent tunnelling solution +- [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/) version 17.14 or higher (for Microsoft 365 Agents Toolkit) ## Run the app (Using Microsoft 365 Agents Toolkit for Visual Studio) @@ -49,37 +52,23 @@ The simplest way to run this sample in Teams is to use Microsoft 365 Agents Tool 1. In the opened web browser, select Add button to install the app in Teams > If you do not have permission to upload custom apps (uploading), Microsoft 365 Agents Toolkit will recommend creating and using a Microsoft 365 Developer Program account - a free program to get your own dev environment sandbox that includes Teams. -## Setup - -> Note these instructions are for running the sample on your local machine, the tunnelling solution is required because the Teams service needs to call into the bot. - -1) Run ngrok - point to port 3978 - - ```bash - ngrok http 3978 --host-header="localhost:3978" - ``` +### Register your app with Azure AD. - Alternatively, you can also use the `dev tunnels`. Please follow [Create and host a dev tunnel](https://learn.microsoft.com/en-us/azure/developer/dev-tunnels/get-started?tabs=windows) and host the tunnel with anonymous user access command as shown below: - - ```bash - devtunnel host -p 3978 --allow-anonymous - ``` -1) Register a new application in the [Microsoft Entra ID – App Registrations](https://go.microsoft.com/fwlink/?linkid=2083908) portal. - - A) Select **New Registration** and on the *register an application page*, set following values: + 1. Register a new application in the [Microsoft Entra ID – App Registrations](https://go.microsoft.com/fwlink/?linkid=2083908) portal. + 2. Select **New Registration** and on the *register an application page*, set following values: * Set **name** to your app name. * Choose the **supported account types** (any account type will work) * Leave **Redirect URI** empty. * Choose **Register**. - B) On the overview page, copy and save the **Application (client) ID, Directory (tenant) ID**. You'll need those later when updating your Teams application manifest and in the appsettings.json. - C) Navigate to **API Permissions**, and make sure to add the following permissions: + 3. On the overview page, copy and save the **Application (client) ID, Directory (tenant) ID**. You'll need those later when updating your Teams application manifest and in the appsettings.json. + 4. Navigate to **API Permissions**, and make sure to add the follow permissions: Select Add a permission * Select Add a permission * Select Microsoft Graph -\> Delegated permissions. * `User.Read` (enabled by default) * Click on Add permissions. Please make sure to grant the admin consent for the required permissions. -1) Setup for Bot +### Setup for Bot In Azure portal, create a [Azure Bot resource](https://docs.microsoft.com/azure/bot-service/bot-service-quickstart-registration). - For bot handle, make up a name. @@ -90,6 +79,22 @@ The simplest way to run this sample in Teams is to use Microsoft 365 Agents Tool - Ensure that you've [enabled the Teams Channel](https://learn.microsoft.com/azure/bot-service/channel-connect-teams?view=azure-bot-service-4.0) - In Settings/Configuration/Messaging endpoint, enter the current `https` URL you were given by running the tunneling application. Append with the path `/api/messages` +## Setup + +> Note these instructions are for running the sample on your local machine, the tunnelling solution is required because the Teams service needs to call into the bot. + +1) Run ngrok - point to port 5130 (default port for the application) + + ```bash + ngrok http 5130 --host-header="localhost:5130" + ``` + + Alternatively, you can also use the `dev tunnels`. Please follow [Create and host a dev tunnel](https://learn.microsoft.com/en-us/azure/developer/dev-tunnels/get-started?tabs=windows) and host the tunnel with anonymous user access command as shown below: + + ```bash + devtunnel host -p 5130 --allow-anonymous + ``` + 1) Clone the repository ```bash @@ -97,26 +102,24 @@ The simplest way to run this sample in Teams is to use Microsoft 365 Agents Tool ``` 1) If you are using Visual Studio - - Launch Visual Studio + - Launch Visual Studio 2022 (version 17.14 or higher) - File -> Open -> Project/Solution - - Navigate to `samples/bot-message-reaction/csharp/MessageReaction` folder - - Select `MessageReaction.csproj` file + - Navigate to `samples/bot-message-reaction/csharp` folder + - Select `MessageReaction.slnx` file (or open the `MessageReaction.csproj` in the MessageReaction folder) - Press `F5` to run the project -1) Update the `appsettings.json` configuration for the bot to use the BOT_ID, BOT_PASSWORD, BOT_TENANT_ID generated in Step 2 (App Registration creation). (Note the App Password is referred to as the "client secret" in the azure portal and you can always create a new client secret anytime.) - - Also, set BOT_TYPE in the `appsettings.json`. (**Allowed values are: MultiTenant(default), SingleTenant, UserAssignedMSI**) +1) Update the `appsettings.Development.json` configuration in the `MessageReaction` folder for the bot to use the ClientId, ClientSecret, BotType, TenantId generated in Step 2 (App Registration creation). (Note the App Password is referred to as the "client secret" in the azure portal and you can always create a new client secret anytime.) + - Also, set BotType in the `appsettings.Development.json`. (**Allowed values are: MultiTenant(default), SingleTenant, UserAssignedMSI**) 1) Run your bot, either from Visual Studio with `F5` or using `dotnet run` in the appropriate folder. 1) __*This step is specific to Teams.*__ - - **Edit** the `manifest.json` contained in the `appPackage` folder to replace your Microsoft App Id (that was created when you registered your bot earlier) *everywhere* you see the place holder string `<>` (depending on the scenario the Microsoft App Id may occur multiple times in the `manifest.json`) - - **Edit** the `manifest.json` for `validDomains` with base Url domain. E.g. if you are using ngrok it would be `https://1234.ngrok-free.app` then your domain-name will be `1234.ngrok-free.app` and if you are using dev tunnels then your domain will be like: `12345.devtunnels.ms`. - - **Zip** up the contents of the `appPackage` folder to create a `manifest.zip` (Make sure that zip file does not contains any subfolder otherwise you will get error while uploading your .zip package) + - **Edit** the `manifest.json` contained in the `M365Agent/appPackage` folder to replace your Microsoft App Id (that was created when you registered your bot earlier) *everywhere* you see the place holder string `${{BOT_ID}}` or `${{TEAMS_APP_ID}}` + - **Edit** the `manifest.json` for `validDomains` with base Url domain. Replace `${{BOT_DOMAIN}}` with your tunnel domain. E.g. if you are using ngrok it would be `https://1234.ngrok-free.app` then your domain-name will be `1234.ngrok-free.app` and if you are using dev tunnels then your domain will be like: `12345.devtunnels.ms`. + - **Zip** up the contents of the `M365Agent/appPackage` folder to create a `manifest.zip` (Make sure that zip file does not contains any subfolder otherwise you will get error while uploading your .zip package) - **Upload** the `manifest.zip` to Teams (In Teams Apps/Manage your apps click "Upload an app". Browse to and Open the .zip file. At the next dialog, click the Add button.) - Add the app in personal/groupchat/team scope (supported scopes) - -**Note**: If you are facing any issue in your app, please uncomment [this](https://github.com/OfficeDev/Microsoft-Teams-Samples/blob/main/samples/bot-message-reaction/csharp/AdapterWithErrorHandler.cs#L24) line and put your debugger for local debug. - + ## Running the sample Message the bot and it will respond with an 'Echo: [your message]'. Add a message reaction to the bots response, and the bot will reply accordingly. @@ -146,8 +149,7 @@ To learn more about deploying a bot to Azure, see [Deploy your bot to Azure](htt ## Further reading -- [Bot Framework Documentation](https://docs.botframework.com) -- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) +- [Teams SDK (Teams AI Library)](https://learn.microsoft.com/en-us/microsoftteams/platform/teams-ai-library/) - [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) - [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) - [Teams Message Reaction Events](https://docs.microsoft.com/en-us/microsoftteams/platform/bots/how-to/conversations/subscribe-to-conversation-events?tabs=dotnet#message-reaction-events) diff --git a/samples/bot-message-reaction/csharp/assets/sample.json b/samples/bot-message-reaction/csharp/assets/sample.json index e0195fdc68..5195344d09 100644 --- a/samples/bot-message-reaction/csharp/assets/sample.json +++ b/samples/bot-message-reaction/csharp/assets/sample.json @@ -3,10 +3,10 @@ "name": "officedev-microsoft-teams-samples-bot-message-reaction-csharp", "source": "officeDev", "title": "Message Reactions Bot", - "shortDescription": "This sample app demonstrates how to use message reactions in Microsoft Teams with a bot built on the Bot Framework.", + "shortDescription": "This sample app demonstrates how to use message reactions in Microsoft Teams SDK.", "url": "https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/bot-message-reaction/csharp", "longDescription": [ - "This sample bot, built on the Bot Framework, responds to message reactions in Microsoft Teams. It showcases features such as adaptive cards and supports multiple scopes, including personal, group, and team interactions." + "This sample bot, built on the Teams SDK, responds to message reactions in Microsoft Teams. It showcases features such as adaptive cards and supports multiple scopes, including personal, group, and team interactions." ], "creationDateTime": "2019-12-12", "updateDateTime": "2024-10-10",