diff --git a/.github/workflows/build-complete-samples.yml b/.github/workflows/build-complete-samples.yml
index 9e357bec34..6f3b73aa8c 100644
--- a/.github/workflows/build-complete-samples.yml
+++ b/.github/workflows/build-complete-samples.yml
@@ -66,7 +66,7 @@ jobs:
- project_path: 'samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/JoinTeamByQR.csproj'
name: 'bot-join-team-using-qr-code'
- version: '6.0.x'
+ version: '10.0.x'
- project_path: 'samples/bot-people-picker-adaptive-card/csharp/PeoplePicker/PeoplePicker.csproj'
name: 'bot-people-picker-adaptive-card'
diff --git a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR.sln b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR.sln
deleted file mode 100644
index a867f45034..0000000000
--- a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR.sln
+++ /dev/null
@@ -1,37 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
-VisualStudioVersion = 17.10.34814.14
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JoinTeamByQR", "JoinTeamByQR\JoinTeamByQR.csproj", "{BEE3FB6B-5F07-491F-9669-23B0C1CA4A30}"
-EndProject
-Project("{A9E3F50B-275E-4AF7-ADCE-8BE12D41E305}") = "M365Agent", "M365Agent\M365Agent.ttkproj", "{F1CD3DEF-6506-4F5C-8B08-660EC9EF739F}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3E13AC70-54EF-4634-9DA6-7A4F95B8972C}"
- ProjectSection(SolutionItems) = preProject
- JoinTeamByQR.slnLaunch.user = JoinTeamByQR.slnLaunch.user
- EndProjectSection
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {BEE3FB6B-5F07-491F-9669-23B0C1CA4A30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {BEE3FB6B-5F07-491F-9669-23B0C1CA4A30}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {BEE3FB6B-5F07-491F-9669-23B0C1CA4A30}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {BEE3FB6B-5F07-491F-9669-23B0C1CA4A30}.Release|Any CPU.Build.0 = Release|Any CPU
- {F1CD3DEF-6506-4F5C-8B08-660EC9EF739F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {F1CD3DEF-6506-4F5C-8B08-660EC9EF739F}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {F1CD3DEF-6506-4F5C-8B08-660EC9EF739F}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
- {F1CD3DEF-6506-4F5C-8B08-660EC9EF739F}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {F1CD3DEF-6506-4F5C-8B08-660EC9EF739F}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {4D7C6DCC-3A5C-42E6-B6E8-2E80E5342C91}
- EndGlobalSection
-EndGlobal
diff --git a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR.slnLaunch.user b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR.slnLaunch.user
index 03e2948955..dae2bb37c8 100644
--- a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR.slnLaunch.user
+++ b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR.slnLaunch.user
@@ -1,31 +1,52 @@
[
{
- "Name": "Microsoft Teams (browser)",
+ "Name": "Microsoft 365 Agents Playground (browser)",
"Projects": [
+ {
+ "Path": "M365Agent\\M365Agent.atkproj",
+ "Name": "M365Agent\\M365Agent.atkproj",
+ "Action": "StartWithoutDebugging",
+ "DebugTarget": "Microsoft 365 Agents Playground (browser)"
+ },
{
"Path": "JoinTeamByQR\\JoinTeamByQR.csproj",
+ "Name": "JoinTeamByQR\\JoinTeamByQR.csproj",
"Action": "Start",
- "DebugTarget": "Start Project"
- },
+ "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": "JoinTeamByQR\\JoinTeamByQR.csproj",
+ "Name": "JoinTeamByQR\\JoinTeamByQR.csproj",
+ "Action": "Start",
+ "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": "JoinTeamByQR\\JoinTeamByQR.csproj",
+ "Name": "JoinTeamByQR\\JoinTeamByQR.csproj",
"Action": "Start",
"DebugTarget": "Start Project"
- },
- {
- "Path": "M365Agent\\M365Agent.ttkproj",
- "Action": "StartWithoutDebugging",
- "DebugTarget": "Microsoft Teams (browser) (skip update app)"
}
]
}
diff --git a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR.slnx b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR.slnx
new file mode 100644
index 0000000000..de527050ef
--- /dev/null
+++ b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR.slnx
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/.gitignore b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/.gitignore
index 7466c01f9c..77c7154916 100644
--- a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/.gitignore
+++ b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/.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-join-team-using-qr-code/csharp/JoinTeamByQR/AdapterWithErrorHandler.cs b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/AdapterWithErrorHandler.cs
deleted file mode 100644
index 5732ff8fd6..0000000000
--- a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/AdapterWithErrorHandler.cs
+++ /dev/null
@@ -1,29 +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 JoinTeamByQR
-{
- public class AdapterWithErrorHandler : CloudAdapter
- {
- public AdapterWithErrorHandler(BotFrameworkAuthentication botFrameworkAuthentication, ILogger logger)
- : base(botFrameworkAuthentication, logger)
- {
- OnTurnError = async (turnContext, exception) =>
- {
- // Log any leaked exception from the application.
- logger.LogError(exception, $"[OnTurnError] unhandled error : {exception.Message}");
-
- // Uncomment below commented line for local debugging..
- // await turnContext.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");
- };
- }
- }
-}
\ No newline at end of file
diff --git a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Bots/ActivityBot.cs b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Bots/ActivityBot.cs
deleted file mode 100644
index f3cf46bf3d..0000000000
--- a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Bots/ActivityBot.cs
+++ /dev/null
@@ -1,128 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-using JoinTeamByQR.helper;
-using JoinTeamByQR.Models;
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.Bot.Builder;
-using Microsoft.Bot.Builder.Dialogs;
-using Microsoft.Bot.Builder.Teams;
-using Microsoft.Bot.Schema;
-using Microsoft.Bot.Schema.Teams;
-using Microsoft.Extensions.Configuration;
-using Newtonsoft.Json.Linq;
-using System;
-using System.Collections.Concurrent;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace JoinTeamByQR.Bots
-{
- public class ActivityBot : TeamsActivityHandler where T : Dialog
- {
- protected readonly BotState ConversationState;
- protected readonly Dialog Dialog;
- private readonly string _applicationBaseUrl;
- private readonly ConcurrentDictionary _Token;
-
- public ActivityBot(IConfiguration configuration, ConversationState conversationState, T dialog, ConcurrentDictionary token)
- {
- _applicationBaseUrl = configuration["ApplicationBaseUrl"] ?? throw new NullReferenceException("ApplicationBaseUrl");
- ConversationState = conversationState;
- Dialog = dialog;
- _Token = token;
- }
-
- ///
- /// Handle request from bot.
- ///
- /// The turn context.
- /// The cancellation token.
- /// A task that represents the work queued to execute.
- public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
- {
- await base.OnTurnAsync(turnContext, cancellationToken);
-
- // Save any state changes that might have occurred during the turn.
- await ConversationState.SaveChangesAsync(turnContext, false, cancellationToken);
- }
-
- ///
- /// Handle when a message is addressed to the bot
- ///
- /// Context object containing information cached for a single turn of conversation with a user.
- /// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
- /// A task that represents the work queued to execute.
- ///
- /// For more information on bot messaging in Teams, see the documentation
- /// https://docs.microsoft.com/en-us/microsoftteams/platform/bots/how-to/conversations/conversation-basics?tabs=dotnet#receive-a-message .
- ///
- protected override async Task OnMessageActivityAsync(ITurnContext turnContext, CancellationToken cancellationToken)
- {
- var userCommand = turnContext.Activity.Text.ToLower().Trim();
-
- if (userCommand == "generate" || userCommand == "logout"|| userCommand == "login")
- {
- // Run the Dialog with the new message Activity.
- await Dialog.RunAsync(turnContext, ConversationState.CreateProperty(nameof(DialogState)), cancellationToken);
- }
- else
- {
- await turnContext.SendActivityAsync(MessageFactory.Text("Type 'generate' to get card with generate action."));
- }
-
- return;
- }
-
- ///
- /// Handle task module is fetch.
- ///
- /// The turn context.
- /// The task module invoke request value payload.
- /// The cancellation token.
- /// A Task Module Response for the request.
- protected override Task OnTeamsTaskModuleFetchAsync(ITurnContext turnContext, TaskModuleRequest taskModuleRequest, CancellationToken cancellationToken)
- {
- var asJobject = JObject.FromObject(taskModuleRequest.Data);
- var buttonType = (string)asJobject.ToObject>()?.Id;
-
- var taskModuleResponse = new TaskModuleResponse();
- if (buttonType == "generate")
- {
- taskModuleResponse.Task = new TaskModuleContinueResponse
- {
- Type = "continue",
- Value = new TaskModuleTaskInfo()
- {
- Url = _applicationBaseUrl + "/generate",
- Height = 350,
- Width = 350,
- Title = "Join a Team",
- },
- };
- }
-
- return Task.FromResult(taskModuleResponse);
- }
-
- ///
- /// Handle task module is submit.
- ///
- /// The turn context.
- /// The task module invoke request value payload.
- /// The cancellation token.
- /// A Task Module Response for the request.
- protected override async Task OnTeamsTaskModuleSubmitAsync(ITurnContext turnContext, TaskModuleRequest taskModuleRequest, CancellationToken cancellationToken)
- {
- var teamInfo = JObject.FromObject(taskModuleRequest.Data);
- var teamId = (string)teamInfo.ToObject>()?.TeamId;
- var userId = (string)teamInfo.ToObject>()?.UserId;
- var token = string.Empty;
- _Token.TryGetValue("Token", out token);
-
- JoinTeamHelper.AddUserToTeam(token, teamId, userId);
- await turnContext.SendActivityAsync("User added to team successfully");
- return null;
- }
- }
-}
\ No newline at end of file
diff --git a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Bots/AuthBot.cs b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Bots/AuthBot.cs
deleted file mode 100644
index 412eb1c56d..0000000000
--- a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Bots/AuthBot.cs
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.Bot.Builder;
-using Microsoft.Bot.Builder.Dialogs;
-using Microsoft.Bot.Schema;
-using Microsoft.Extensions.Configuration;
-
-namespace JoinTeamByQR.Bots
-{
- public class AuthBot : ActivityBot where T : Dialog
- {
- public AuthBot(IConfiguration configuration, ConversationState conversationState, T dialog, ConcurrentDictionary token)
- : base(configuration,conversationState, dialog, token)
- {
- }
-
- ///
- /// Invoked when bot (like a user) are added to the conversation.
- ///
- /// A list of all the members added to the conversation.
- /// Context object containing information cached for a single turn of conversation with a user.
- /// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
- /// A task that represents the work queued to execute.
- ///
- protected override async Task OnMembersAddedAsync(IList membersAdded, ITurnContext turnContext, CancellationToken cancellationToken)
- {
- foreach (var member in turnContext.Activity.MembersAdded)
- {
- if (member.Id != turnContext.Activity.Recipient.Id)
- {
- await turnContext.SendActivityAsync(MessageFactory.Text($"Hello and welcome! With this sample your bot can generate QR code for the selected team and the user will be able to join the team by scanning QR code. Please type generate to begin."), cancellationToken);
- }
- }
- }
-
- ///
- /// Invoked when a tokens/response event is received.
- ///
- /// Context object containing information cached for a single turn of conversation with a user.
- /// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
- /// A task that represents the work queued to execute.
- ///
- protected override async Task OnTokenResponseEventAsync(ITurnContext turnContext, CancellationToken cancellationToken)
- {
- // Run the Dialog with the new Token Response Event Activity.
- await Dialog.RunAsync(turnContext, ConversationState.CreateProperty(nameof(DialogState)), cancellationToken);
- }
-
- ///
- /// Invoked when a signIn verify state activity is received from the connector.
- ///
- /// Context object containing information cached for a single turn of conversation with a user.
- /// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
- /// A task that represents the work queued to execute.
- ///
- protected override async Task OnTeamsSigninVerifyStateAsync(ITurnContext turnContext, CancellationToken cancellationToken)
- {
- // Run the Dialog with the new Teams Signin Verify State Activity.
- await Dialog.RunAsync(turnContext, ConversationState.CreateProperty(nameof(DialogState)), cancellationToken);
- }
- }
-}
\ No newline at end of file
diff --git a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Config.cs b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Config.cs
new file mode 100644
index 0000000000..bdc23b97d1
--- /dev/null
+++ b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Config.cs
@@ -0,0 +1,17 @@
+namespace JoinTeamByQR
+{
+ 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; }
+ public string ConnectionName { get; set; }
+ public string ApplicationBaseUrl { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Controllers/BotController.cs b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Controllers/BotController.cs
deleted file mode 100644
index b26f717929..0000000000
--- a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Controllers/BotController.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-//
-// Generated with Bot Builder V4 SDK Template for Visual Studio EchoBot v4.14.0
-
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.Bot.Builder;
-using Microsoft.Bot.Builder.Integration.AspNet.Core;
-using System.Threading.Tasks;
-
-namespace JoinTeamByQR.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 CloudAdapter Adapter;
- private readonly IBot Bot;
-
- public BotController(CloudAdapter 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-join-team-using-qr-code/csharp/JoinTeamByQR/Controllers/Controller.cs b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Controllers/Controller.cs
new file mode 100644
index 0000000000..132802a078
--- /dev/null
+++ b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Controllers/Controller.cs
@@ -0,0 +1,114 @@
+using Microsoft.Teams.Api.Activities.Invokes;
+using Microsoft.Teams.Apps;
+using Microsoft.Teams.Apps.Activities.Invokes;
+using Microsoft.Teams.Apps.Annotations;
+using Microsoft.Teams.Common;
+using System.Collections.Concurrent;
+using System.Text.Json;
+using JoinTeamByQR.Helpers;
+using TaskModuleResponse = Microsoft.Teams.Api.TaskModules.Response;
+using TaskModuleSize = Microsoft.Teams.Api.TaskModules.Size;
+using ILogger = Microsoft.Teams.Common.Logging.ILogger;
+
+namespace JoinTeamByQR.Controllers
+{
+ [TeamsController]
+ public class Controller
+ {
+ private readonly ConcurrentDictionary _tokenCache;
+ private readonly ConfigOptions _config;
+
+ public Controller(ConfigOptions config, ConcurrentDictionary tokenCache)
+ {
+ _config = config;
+ _tokenCache = tokenCache;
+ }
+
+ [TaskFetch]
+ public TaskModuleResponse OnTaskFetch(
+ [Context] Tasks.FetchActivity activity,
+ [Context] ILogger log)
+ {
+ var data = activity.Value?.Data as JsonElement?;
+ string? dialogType = null;
+
+ if (data != null && data.Value.TryGetProperty("opendialogtype", out var dialogTypeElement) && dialogTypeElement.ValueKind == JsonValueKind.String)
+ {
+ dialogType = dialogTypeElement.GetString();
+ }
+
+ log.Info($"[TASK_FETCH] Dialog type: {dialogType}");
+
+ if (dialogType == "qr_generator")
+ {
+ var taskInfo = new Microsoft.Teams.Api.TaskModules.TaskInfo
+ {
+ Title = "QR Code Generator",
+ Width = new Union(500),
+ Height = new Union(600),
+ Url = $"{_config.Teams.ApplicationBaseUrl}/generate.html"
+ };
+
+ return new TaskModuleResponse(new Microsoft.Teams.Api.TaskModules.ContinueTask(taskInfo));
+ }
+
+ return new TaskModuleResponse(new Microsoft.Teams.Api.TaskModules.MessageTask("Unknown dialog type"));
+ }
+
+ [TaskSubmit]
+ public async Task OnTaskSubmit(
+ [Context] Tasks.SubmitActivity activity,
+ [Context] IContext.Client client,
+ [Context] ILogger log)
+ {
+ var data = activity.Value?.Data as JsonElement?;
+
+ if (data != null)
+ {
+ log.Info($"[TASK_SUBMIT] Received data: {data}");
+
+ // Check if this is a team join request from QR scan
+ if (data.Value.TryGetProperty("teamId", out var teamIdElement) &&
+ data.Value.TryGetProperty("userId", out var userIdElement))
+ {
+ var teamId = teamIdElement.GetString();
+ var userId = userIdElement.GetString();
+
+ if (!string.IsNullOrEmpty(teamId) && !string.IsNullOrEmpty(userId))
+ {
+ // Get token and add user to team
+ if (_tokenCache.TryGetValue("Token", out var token) && !string.IsNullOrEmpty(token))
+ {
+ try
+ {
+ await JoinTeamHelper.AddUserToTeam(token, teamId, userId);
+
+ // Send a message to the chat confirming the user was added
+ await client.Send("User added successfully to the team!");
+
+ return new TaskModuleResponse(new Microsoft.Teams.Api.TaskModules.MessageTask("Successfully added to the team!"));
+ }
+ catch (Exception ex)
+ {
+ log.Error($"Error adding user to team: {ex.Message}");
+
+ // Send error message to chat
+ await client.Send($"Failed to add user to team: {ex.Message}");
+
+ return new TaskModuleResponse(new Microsoft.Teams.Api.TaskModules.MessageTask($"Error joining team: {ex.Message}"));
+ }
+ }
+ else
+ {
+ await client.Send("Authentication token not found. Please sign in first using '/signin'.");
+ return new TaskModuleResponse(new Microsoft.Teams.Api.TaskModules.MessageTask("Authentication token not found. Please sign in first."));
+ }
+ }
+ }
+ }
+
+ // Return null to close the dialog
+ return null;
+ }
+ }
+}
diff --git a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Controllers/TeamDetailController.cs b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Controllers/TeamDetailController.cs
index 33f7c05fa3..47739041bd 100644
--- a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Controllers/TeamDetailController.cs
+++ b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Controllers/TeamDetailController.cs
@@ -1,41 +1,51 @@
-using JoinTeamByQR.helper;
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using JoinTeamByQR.Helpers;
using JoinTeamByQR.Models;
using Microsoft.AspNetCore.Mvc;
-using Microsoft.Extensions.Configuration;
using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Threading.Tasks;
namespace JoinTeamByQR.Controllers
{
///
- /// Class for sharepoint file upload
+ /// Controller for team operations
///
- [Route("getTeams")]
+ [Route("api/teams")]
+ [ApiController]
public class TeamDetailController : ControllerBase
{
- private readonly ConcurrentDictionary _Token;
- public readonly IConfiguration _configuration;
- public TeamDetailController(
- ConcurrentDictionary token,IConfiguration configuration)
+ private readonly ConcurrentDictionary _tokenCache;
+
+ public TeamDetailController(ConcurrentDictionary tokenCache)
{
- _Token = token;
- _configuration = configuration;
+ _tokenCache = tokenCache;
}
///
- /// This endpoint is called lto get list of all teams of the organization.
+ /// This endpoint is called to get list of all teams of the organization.
///
[HttpGet]
- public async Task> GetAllTeamsAsync()
+ public async Task>> GetAllTeamsAsync()
{
var token = string.Empty;
- _Token.TryGetValue("Token", out token);
-
- var teamData = await JoinTeamHelper.GetAllTeams(token);
+ // Try to get token from cache
+ // First try with a general token key, or implement user-specific token retrieval
+ if (_tokenCache.TryGetValue("Token", out token) && !string.IsNullOrEmpty(token))
+ {
+ try
+ {
+ var teamData = await JoinTeamHelper.GetAllTeams(token);
+ return Ok(teamData);
+ }
+ catch (Exception ex)
+ {
+ return StatusCode(500, $"Error getting teams: {ex.Message}");
+ }
+ }
- return teamData;
+ return Unauthorized(new { message = "Authentication token not found. Please sign in first by sending '/signin' to the bot." });
}
}
}
diff --git a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Dialogs/LogoutDialog.cs b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Dialogs/LogoutDialog.cs
deleted file mode 100644
index 47b72d164d..0000000000
--- a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Dialogs/LogoutDialog.cs
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.Bot.Builder;
-using Microsoft.Bot.Builder.Dialogs;
-using Microsoft.Bot.Builder.Integration.AspNet.Core;
-using Microsoft.Bot.Schema;
-using Microsoft.Bot.Connector.Authentication;
-
-namespace JoinTeamByQR.Dialogs
-{
- public class LogoutDialog : ComponentDialog
- {
- public LogoutDialog(string id, string connectionName)
- : base(id)
- {
- ConnectionName = connectionName;
- }
-
- protected string ConnectionName { get; }
-
- // Called when the dialog is started and pushed onto the parent's dialog stack.
- protected override async Task OnBeginDialogAsync(DialogContext innerDc, object options, CancellationToken cancellationToken = default(CancellationToken))
- {
- var result = await InterruptAsync(innerDc, cancellationToken);
- if (result != null)
- {
- return result;
- }
-
- return await base.OnBeginDialogAsync(innerDc, options, cancellationToken);
- }
-
- // Called when the dialog is _continued_, where it is the active dialog and the user replies with a new activity.
- protected override async Task OnContinueDialogAsync(DialogContext innerDc, CancellationToken cancellationToken = default(CancellationToken))
- {
- var result = await InterruptAsync(innerDc, cancellationToken);
- if (result != null)
- {
- return result;
- }
-
- return await base.OnContinueDialogAsync(innerDc, cancellationToken);
- }
-
- private async Task InterruptAsync(DialogContext innerDc, CancellationToken cancellationToken = default(CancellationToken))
- {
- if (innerDc.Context.Activity.Type == ActivityTypes.Message)
- {
- var text = innerDc.Context.Activity.Text.ToLowerInvariant();
-
- // Allow logout anywhere in the command
- if (text.IndexOf("logout") >= 0)
- {
- // Use the UserTokenClient for CloudAdapter authentication operations
- var userTokenClient = innerDc.Context.TurnState.Get();
- if (userTokenClient != null)
- {
- await userTokenClient.SignOutUserAsync(innerDc.Context.Activity.From.Id, ConnectionName, innerDc.Context.Activity.ChannelId, cancellationToken);
- }
-
- await innerDc.Context.SendActivityAsync(MessageFactory.Text("You have been signed out."), cancellationToken);
- return await innerDc.CancelAllDialogsAsync(cancellationToken);
- }
- }
-
- return null;
- }
- }
-}
\ No newline at end of file
diff --git a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Dialogs/MainDialog.cs b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Dialogs/MainDialog.cs
deleted file mode 100644
index af6aba64c7..0000000000
--- a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Dialogs/MainDialog.cs
+++ /dev/null
@@ -1,126 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Threading;
-using System.Threading.Tasks;
-using AdaptiveCards;
-using JoinTeamByQR.Models;
-using Microsoft.Bot.Builder;
-using Microsoft.Bot.Builder.Dialogs;
-using Microsoft.Bot.Schema;
-using Microsoft.Extensions.Configuration;
-
-namespace JoinTeamByQR.Dialogs
-{
- public class MainDialog : LogoutDialog
- {
- public readonly IConfiguration _configuration;
- private readonly ConcurrentDictionary _Token;
-
- public MainDialog(IConfiguration configuration,ConcurrentDictionary token)
- : base(nameof(MainDialog), configuration["ConnectionName"])
- {
- _configuration = configuration;
- _Token = token;
-
- AddDialog(new TokenExchangeOAuthPrompt(
- nameof(TokenExchangeOAuthPrompt),
- new OAuthPromptSettings
- {
- ConnectionName = ConnectionName,
- Text = "Please login",
- Title = "Sign In",
- Timeout = 1000 * 60 * 1, // User has 5 minutes to login (1000 * 60 * 5)
-
- //EndOnInvalidMessage = true
- }));
-
- AddDialog(new TextPrompt(nameof(TextPrompt)));
-
- AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[]
- {
- PromptStepAsync,
- LoginStepAsync
- }));
-
- // The initial child Dialog to run.
- InitialDialogId = nameof(WaterfallDialog);
- }
-
- // Method to invoke oauth flow.
- private async Task PromptStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
- {
- return await stepContext.BeginDialogAsync(nameof(TokenExchangeOAuthPrompt), null, cancellationToken);
- }
-
- // Invoked after success of prompt step async.
- private async Task LoginStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
- {
- // Get the token from the previous step. Note that we could also have gotten the
- // token directly from the prompt itself. There is an example of this in the next method.
- var tokenResponse = (TokenResponse)stepContext.Result;
-
- if (tokenResponse != null)
- {
- if (stepContext.Context.Activity.Text!= null)
- {
- if (stepContext.Context.Activity.Text.ToLower().Trim() == "generate")
- {
-
- //Set the Last Dialog in Conversation Data
- _Token.AddOrUpdate("Token", tokenResponse.Token, (key, newValue) => tokenResponse.Token);
- await stepContext.Context.SendActivityAsync(MessageFactory.Attachment(GetAdaptiveCardForTaskModule()), cancellationToken);
-
- return await stepContext.EndDialogAsync();
- }
- }
- }
-
- await stepContext.Context.SendActivityAsync(MessageFactory.Text("Login successful"), cancellationToken);
-
- return await stepContext.EndDialogAsync();
- }
-
- ///
- /// Sample Adaptive card for Meeting Start event.
- ///
- private Attachment GetAdaptiveCardForTaskModule()
- {
- AdaptiveCard card = new AdaptiveCard(new AdaptiveSchemaVersion("1.2"))
- {
- Body = new List
- {
- new AdaptiveTextBlock
- {
- Text = "Generate QR for team",
- Weight = AdaptiveTextWeight.Bolder,
- Spacing = AdaptiveSpacing.Medium,
- }
- },
- Actions = new List
- {
- new AdaptiveSubmitAction
- {
- Title = "Generate QR code",
- Data = new AdaptiveCardAction
- {
- MsteamsCardAction = new CardAction
- {
- Type = "task/fetch",
- },
- Id = "generate"
- },
- },
- },
- };
-
- return new Attachment()
- {
- ContentType = AdaptiveCard.ContentType,
- Content = card,
- };
- }
- }
-}
\ No newline at end of file
diff --git a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Dialogs/TokenExchangeOAuthPrompt.cs b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Dialogs/TokenExchangeOAuthPrompt.cs
deleted file mode 100644
index cbc584be83..0000000000
--- a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Dialogs/TokenExchangeOAuthPrompt.cs
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-using System;
-using System.Net;
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.Bot.Builder;
-using Microsoft.Bot.Builder.Dialogs;
-using Microsoft.Bot.Schema;
-
-namespace JoinTeamByQR.Dialogs
-{
- public class TokenExchangeOAuthPrompt : OAuthPrompt
- {
- private readonly OAuthPromptSettings _settings;
-
- public TokenExchangeOAuthPrompt(string dialogId, OAuthPromptSettings settings, PromptValidator validator = null)
- : base(dialogId, settings, validator)
- {
- _settings = settings;
- }
-
- public override async Task ContinueDialogAsync(DialogContext dc, CancellationToken cancellationToken = default)
- {
- // If the token was successfully exchanged already, it should be cached in TurnState along with the TokenExchangeInvokeRequest
- TokenResponse cachedTokenResponse = dc.Context.TurnState.Get(nameof(TokenResponse));
-
- if (cachedTokenResponse != null)
- {
- var tokenExchangeRequest = dc.Context.TurnState.Get(nameof(TokenExchangeInvokeRequest));
- if (tokenExchangeRequest == null)
- {
- throw new InvalidOperationException("TokenResponse is present in TurnState, but TokenExchangeInvokeRequest is missing.");
- }
-
- var result = new PromptRecognizerResult();
-
- var exchangeResponse = new TokenExchangeInvokeResponse
- {
- Id = tokenExchangeRequest.Id,
- ConnectionName = _settings.ConnectionName,
- };
-
- await dc.Context.SendActivityAsync(
- new Activity
- {
- Type = ActivityTypesEx.InvokeResponse,
- Value = new InvokeResponse
- {
- Status = (int)HttpStatusCode.OK,
- Body = exchangeResponse,
- },
- }, cancellationToken).ConfigureAwait(false);
-
- result.Succeeded = true;
- result.Value = new TokenResponse
- {
- ChannelId = cachedTokenResponse.ChannelId,
- ConnectionName = cachedTokenResponse.ConnectionName,
- Token = cachedTokenResponse.Token,
- };
-
- return await dc.EndDialogAsync(result.Value, cancellationToken).ConfigureAwait(false);
- }
-
- return await base.ContinueDialogAsync(dc, cancellationToken).ConfigureAwait(false);
- }
- }
-}
\ No newline at end of file
diff --git a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/helper/JoinTeamHelper.cs b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Helpers/JoinTeamHelper.cs
similarity index 65%
rename from samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/helper/JoinTeamHelper.cs
rename to samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Helpers/JoinTeamHelper.cs
index b9448666e3..1b33ca0b2e 100644
--- a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/helper/JoinTeamHelper.cs
+++ b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Helpers/JoinTeamHelper.cs
@@ -1,17 +1,18 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
+// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using JoinTeamByQR.Models;
using Microsoft.Graph;
-using System;
-using System.Collections.Generic;
-using System.Threading.Tasks;
-
-namespace JoinTeamByQR.helper
+namespace JoinTeamByQR.Helpers
{
+ ///
+ /// Helper class for joining team operations.
+ ///
public class JoinTeamHelper
{
- // Helper method to get list of all teams of organization.
+ ///
+ /// Helper method to get list of all teams of organization.
+ ///
public static async Task> GetAllTeams(string tokenResponse)
{
if (tokenResponse == null)
@@ -26,14 +27,16 @@ public static async Task> GetAllTeams(string tokenResponse)
return teamData;
}
- catch (ServiceException ex)
+ catch (ServiceException)
{
- throw ex;
+ throw;
}
}
- // Helper method to add user to the team.
- public static void AddUserToTeam(string tokenResponse, string teamId, string userId)
+ ///
+ /// Helper method to add user to the team.
+ ///
+ public static async Task AddUserToTeam(string tokenResponse, string teamId, string userId)
{
if (string.IsNullOrEmpty(tokenResponse))
{
@@ -51,7 +54,7 @@ public static void AddUserToTeam(string tokenResponse, string teamId, string use
}
var client = new SimpleGraphClient(tokenResponse);
- client.AddUserToTeam(teamId,userId);
+ await client.AddUserToTeam(teamId, userId);
}
}
-}
\ No newline at end of file
+}
diff --git a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Helpers/SimpleGraphClient.cs b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Helpers/SimpleGraphClient.cs
new file mode 100644
index 0000000000..ecfd32cb7c
--- /dev/null
+++ b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Helpers/SimpleGraphClient.cs
@@ -0,0 +1,147 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using Azure.Core;
+using JoinTeamByQR.Models;
+using Microsoft.Graph;
+using Microsoft.Graph.Models;
+using Microsoft.Graph.Models.ODataErrors;
+
+namespace JoinTeamByQR.Helpers
+{
+ ///
+ /// This class is a wrapper for the Microsoft Graph API
+ /// See: https://developer.microsoft.com/en-us/graph
+ ///
+ public class SimpleGraphClient
+ {
+ private readonly string _token;
+
+ public SimpleGraphClient(string token)
+ {
+ if (string.IsNullOrWhiteSpace(token))
+ {
+ throw new ArgumentNullException(nameof(token));
+ }
+
+ _token = token;
+ }
+
+ ///
+ /// Get list of all teams present in the organization.
+ ///
+ public async Task> GetAllTeams()
+ {
+ var teamData = new List();
+ var graphClient = GetAuthenticatedClient();
+
+ try
+ {
+ var groups = await graphClient.Groups.GetAsync(requestConfiguration =>
+ {
+ requestConfiguration.QueryParameters.Filter = "resourceProvisioningOptions/Any(x:x eq 'Team')";
+ });
+
+ foreach (var team in groups?.Value ?? new List())
+ {
+ var teamDetails = new TeamData
+ {
+ TeamId = team.Id,
+ TeamName = team.DisplayName
+ };
+
+ teamData.Add(teamDetails);
+ }
+
+ return teamData.Take(5).ToList();
+ }
+ catch (ODataError odataError)
+ {
+ var errorCode = odataError.Error?.Code ?? "Unknown";
+ var errorMessage = odataError.Error?.Message ?? odataError.Message;
+
+ Console.WriteLine($"[GRAPH_ERROR] Code: {errorCode}, Message: {errorMessage}");
+
+ // Provide more specific error messages
+ if (errorCode == "Authorization_RequestDenied" || errorMessage?.Contains("Insufficient privileges") == true)
+ {
+ throw new Exception($"Insufficient permissions to read groups/teams. Please ensure your OAuth connection has 'Group.Read.All' or 'Team.ReadBasic.All' scope configured. Error: {errorMessage}");
+ }
+ else if (errorCode == "InvalidAuthenticationToken")
+ {
+ throw new Exception($"Invalid or expired authentication token. Please sign out and sign in again. Error: {errorMessage}");
+ }
+
+ throw new Exception($"Graph API error ({errorCode}): {errorMessage}");
+ }
+ }
+
+ ///
+ /// Add the user in the team.
+ ///
+ public async Task AddUserToTeam(string teamId, string userId)
+ {
+ var graphClient = GetAuthenticatedClient();
+
+ try
+ {
+ var conversationMember = new AadUserConversationMember
+ {
+ Roles = new List()
+ {
+ "owner"
+ },
+ AdditionalData = new Dictionary()
+ {
+ {"user@odata.bind", $"https://graph.microsoft.com/v1.0/users('{userId}')"}
+ }
+ };
+
+ await graphClient.Teams[teamId].Members.PostAsync(conversationMember);
+ }
+ catch (ODataError odataError)
+ {
+ var errorCode = odataError.Error?.Code ?? "Unknown";
+ var errorMessage = odataError.Error?.Message ?? odataError.Message;
+
+ Console.WriteLine($"[GRAPH_ERROR] Code: {errorCode}, Message: {errorMessage}");
+
+ if (errorCode == "Authorization_RequestDenied" || errorMessage?.Contains("Insufficient privileges") == true)
+ {
+ throw new Exception($"Insufficient permissions to add members to teams. Please ensure your OAuth connection has 'TeamMember.ReadWrite.All' scope configured. Error: {errorMessage}");
+ }
+
+ throw new Exception($"Graph API error ({errorCode}): {errorMessage}");
+ }
+ }
+
+ ///
+ /// Get an Authenticated Microsoft Graph client using the token issued to the user.
+ ///
+ private GraphServiceClient GetAuthenticatedClient()
+ {
+ var tokenCredential = new MyAccessTokenProvider(_token);
+ return new GraphServiceClient(tokenCredential);
+ }
+
+ public class MyAccessTokenProvider : TokenCredential
+ {
+ private readonly string _token;
+
+ public MyAccessTokenProvider(string token)
+ {
+ _token = token;
+ }
+
+ public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken)
+ {
+ return new AccessToken(_token, DateTimeOffset.UtcNow.AddHours(1));
+ }
+
+ public override ValueTask GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken)
+ {
+ return new ValueTask(new AccessToken(_token, DateTimeOffset.UtcNow.AddHours(1)));
+ }
+ }
+ }
+}
diff --git a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Images/1.create-remainder.png b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Images/1.create-remainder.png
new file mode 100644
index 0000000000..e5fd2b20c9
Binary files /dev/null and b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Images/1.create-remainder.png differ
diff --git a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Images/2.addtask-modules.png b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Images/2.addtask-modules.png
new file mode 100644
index 0000000000..f9a22d59f9
Binary files /dev/null and b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Images/2.addtask-modules.png differ
diff --git a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Images/3.successfully-created.png b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Images/3.successfully-created.png
new file mode 100644
index 0000000000..32de6045d0
Binary files /dev/null and b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Images/3.successfully-created.png differ
diff --git a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Images/4.reminder-card.png b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Images/4.reminder-card.png
new file mode 100644
index 0000000000..c450fa92a1
Binary files /dev/null and b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Images/4.reminder-card.png differ
diff --git a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Images/Copilot_CreateReminder.png b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Images/Copilot_CreateReminder.png
new file mode 100644
index 0000000000..8b3fff2d6b
Binary files /dev/null and b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Images/Copilot_CreateReminder.png differ
diff --git a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Images/Copilot_DailyTaskReminder_App.png b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Images/Copilot_DailyTaskReminder_App.png
new file mode 100644
index 0000000000..1d97af06c9
Binary files /dev/null and b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Images/Copilot_DailyTaskReminder_App.png differ
diff --git a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Images/Copilot_Install_App.png b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Images/Copilot_Install_App.png
new file mode 100644
index 0000000000..19cc6d000d
Binary files /dev/null and b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Images/Copilot_Install_App.png differ
diff --git a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Images/Copilot_ReminderSet_Successfully.png b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Images/Copilot_ReminderSet_Successfully.png
new file mode 100644
index 0000000000..ceda48c395
Binary files /dev/null and b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Images/Copilot_ReminderSet_Successfully.png differ
diff --git a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Images/Copilot_Schedule_Reminder.png b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Images/Copilot_Schedule_Reminder.png
new file mode 100644
index 0000000000..5708ddea20
Binary files /dev/null and b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Images/Copilot_Schedule_Reminder.png differ
diff --git a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Images/Copilot_Task_Reminder.png b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Images/Copilot_Task_Reminder.png
new file mode 100644
index 0000000000..bb4290c64c
Binary files /dev/null and b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Images/Copilot_Task_Reminder.png differ
diff --git a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Images/DailyTaskReminder.gif b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Images/DailyTaskReminder.gif
new file mode 100644
index 0000000000..7cff5fb2b5
Binary files /dev/null and b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Images/DailyTaskReminder.gif differ
diff --git a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/JoinTeamByQR.csproj b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/JoinTeamByQR.csproj
index da5a73fc72..254d6c6a0a 100644
--- a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/JoinTeamByQR.csproj
+++ b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/JoinTeamByQR.csproj
@@ -1,27 +1,31 @@
- net6.0
- latest
+ net10.0
+ enable
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
- Always
+
+
+
+
+ PreserveNewest
+ None
+
+
+
+ PreserveNewest
+ None
-
-
-
-
-
-
+
\ No newline at end of file
diff --git a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Models/AdaptiveCardAction.cs b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Models/AdaptiveCardAction.cs
deleted file mode 100644
index 327965e272..0000000000
--- a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Models/AdaptiveCardAction.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-//
-// Copyright (c) Microsoft. All rights reserved.
-//
-
-namespace JoinTeamByQR.Models
-{
- using Microsoft.Bot.Schema;
- using Newtonsoft.Json;
-
- ///
- /// Adaptive card action model class.
- ///
- public class AdaptiveCardAction
- {
- ///
- /// Gets or sets Ms Teams card action type.
- ///
- [JsonProperty("msteams")]
- public CardAction MsteamsCardAction { get; set; }
-
- ///
- /// Gets or sets id value of turncontext activity.
- ///
- [JsonProperty("id")]
- public string Id { get; set; }
- }
-}
diff --git a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Models/CardTaskFetchValue.cs b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Models/CardTaskFetchValue.cs
index 28e7d8ddc8..2a392a0726 100644
--- a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Models/CardTaskFetchValue.cs
+++ b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Models/CardTaskFetchValue.cs
@@ -1,23 +1,13 @@
-//
-// Copyright (c) Microsoft. All rights reserved.
-//
-
-using Newtonsoft.Json;
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
namespace JoinTeamByQR.Models
{
///
- /// Card task fetch value model class.
+ /// Task module fetch value.
///
public class CardTaskFetchValue
{
- [JsonProperty("type")]
- public object Type { get; set; } = "task/fetch";
-
- [JsonProperty("id")]
public object Id { get; set; }
-
- [JsonProperty("data")]
- public T Data { get; set; }
}
}
diff --git a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Models/ResponseData.cs b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Models/ResponseData.cs
index 4dd96820f6..9308999818 100644
--- a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Models/ResponseData.cs
+++ b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Models/ResponseData.cs
@@ -1,6 +1,5 @@
-//
-// Copyright (c) Microsoft. All rights reserved.
-//
/// Class with taskmodule response model.
///
- public class ResponseData
+ public class ResponseData
{
[JsonProperty("teamId")]
- public object TeamId { get; set; }
+ public string TeamId { get; set; }
[JsonProperty("userId")]
- public object UserId { get; set; }
+ public string UserId { get; set; }
}
-}
\ No newline at end of file
+}
diff --git a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Models/TeamData.cs b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Models/TeamData.cs
index 63af0cc2d3..aa85124cb7 100644
--- a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Models/TeamData.cs
+++ b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Models/TeamData.cs
@@ -1,6 +1,5 @@
-//
-// Copyright (c) Microsoft. All rights reserved.
-//
public class TeamData
{
- public string teamId { get; set; }
+ public string TeamId { get; set; }
- public string teamName { get; set; }
+ public string TeamName { get; set; }
}
}
diff --git a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Models/TokenState.cs b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Models/TokenState.cs
deleted file mode 100644
index c4d6a49e40..0000000000
--- a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Models/TokenState.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-//
-// Copyright (c) Microsoft. All rights reserved.
-//
- /// Token state model class.
- ///
- public class TokenState
- {
- public string AccessToken { get; set; }
- }
-}
diff --git a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Pages/generate.cshtml.cs b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Pages/generate.cshtml.cs
deleted file mode 100644
index 0e0c0bc4ee..0000000000
--- a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Pages/generate.cshtml.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using Microsoft.AspNetCore.Mvc.RazorPages;
-
-namespace JoinTeamByQR.Pages
-{
- public class generateModel : PageModel
- {
- public void OnGet()
- {
- }
- }
-}
diff --git a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Program.cs b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Program.cs
index c2ecd1218a..505e56421c 100644
--- a/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Program.cs
+++ b/samples/bot-join-team-using-qr-code/csharp/JoinTeamByQR/Program.cs
@@ -1,25 +1,259 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-//
-// Generated with Bot Builder V4 SDK Template for Visual Studio EchoBot v4.14.0
+using JoinTeamByQR;
+using JoinTeamByQR.Controllers;
+using Azure.Core;
+using Azure.Identity;
+using Microsoft.Teams.Api.Auth;
+using Microsoft.Teams.Apps;
+using Microsoft.Teams.Apps.Activities;
+using Microsoft.Teams.Apps.Events;
+using Microsoft.Teams.Apps.Extensions;
+using Microsoft.Teams.Common.Http;
+using Microsoft.Teams.Plugins.AspNetCore.Extensions;
+using System.Collections.Concurrent;
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.Extensions.Hosting;
+var builder = WebApplication.CreateBuilder(args);
-namespace JoinTeamByQR
+// Get configuration from appsettings.json / appsettings.Development.json
+var config = builder.Configuration.Get() ?? new ConfigOptions();
+
+// Create a global dictionary for token cache
+builder.Services.AddSingleton>();
+
+// Register configuration as singleton
+builder.Services.AddSingleton(config);
+
+Func> createTokenFactory = async (string[] scopes, string tenantId) =>
{
- public class Program
+ var clientId = config.Teams.ClientId;
+
+ if (config.Teams.BotType == "UserAssignedMsi")
+ {
+ var managedIdentityCredential = new ManagedIdentityCredential(clientId);
+ var tokenRequestContext = new TokenRequestContext(scopes, tenantId: tenantId);
+ var accessToken = await managedIdentityCredential.GetTokenAsync(tokenRequestContext);
+
+ return new TokenResponse
+ {
+ TokenType = "Bearer",
+ AccessToken = accessToken.Token,
+ };
+ }
+ else
{
- public static void Main(string[] args)
+ // For ClientSecret authentication
+ var credential = new ClientSecretCredential(
+ config.Teams.TenantId,
+ config.Teams.ClientId,
+ config.Teams.ClientSecret
+ );
+
+ var tokenRequestContext = new TokenRequestContext(scopes, tenantId: tenantId);
+ var accessToken = await credential.GetTokenAsync(tokenRequestContext);
+
+ return new TokenResponse
+ {
+ 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);
}
+ ));
+}
+else if (config.Teams.BotType == "ClientSecret")
+{
+ appBuilder.AddCredentials(new TokenCredentials(
+ config.Teams.ClientId ?? string.Empty,
+ async (tenantId, scopes) =>
+ {
+ return await createTokenFactory(scopes, tenantId);
+ }
+ ));
+}
+
+// Add OAuth for user authentication
+appBuilder.AddOAuth(config.Teams.ConnectionName);
+
+// Register the controller for TaskFetch/TaskSubmit handling
+builder.Services.AddSingleton();
+builder.AddTeams(appBuilder);
+
+var app = builder.Build();
+
+// Enable serving static files
+app.UseDefaultFiles();
+app.UseStaticFiles();
+
+// Enable routing
+app.UseRouting();
+
+var teams = app.UseTeams();
+
+// Get token cache from DI
+var tokenCache = app.Services.GetRequiredService>();
+
+// Handle sign-in message command
+teams.OnMessage("/signin", async context =>
+{
+ if (context.IsSignedIn)
+ {
+ await context.Send("You are already signed in!");
+ return;
+ }
+
+ await context.SignIn();
+});
+
+// Handle sign-out message command
+teams.OnMessage("/signout", async context =>
+{
+ if (!context.IsSignedIn)
+ {
+ await context.Send("You are not signed in!");
+ return;
+ }
+
+ // Clear token from cache
+ var userId = context.Activity.From.Id;
+ tokenCache.TryRemove($"Token_{userId}", out _);
+ tokenCache.TryRemove("Token", out _);
+
+ await context.SignOut();
+ await context.Send("You have been signed out!");
+});
+
+// Handle 'generate' message command
+teams.OnMessage("generate", async context =>
+{
+ if (!context.IsSignedIn)
+ {
+ await context.Send("Please sign in first by typing '/signin'");
+ return;
+ }
- public static IHostBuilder CreateHostBuilder(string[] args) =>
- Host.CreateDefaultBuilder(args)
- .ConfigureWebHostDefaults(webBuilder =>
+ // Create Adaptive Card as raw JSON object for maximum compatibility
+ var cardContent = new Dictionary
+ {
+ { "type", "AdaptiveCard" },
+ { "$schema", "http://adaptivecards.io/schemas/adaptive-card.json" },
+ { "version", "1.4" },
+ { "body", new List