diff --git a/.github/workflows/build-complete-samples.yml b/.github/workflows/build-complete-samples.yml index 9e357bec34..c26f51179a 100644 --- a/.github/workflows/build-complete-samples.yml +++ b/.github/workflows/build-complete-samples.yml @@ -34,7 +34,7 @@ jobs: - project_path: 'samples/app-checkin-location/csharp/AppCheckinLocation/AppCheckinLocation.csproj' name: 'app-checkin-location' - version: '6.0.x' + version: '10.0.x' - project_path: 'samples/app-installation-using-qr-code/csharp/QRAppInstallation/QRAppInstallation.csproj' name: 'app-installation-using-qr-code' diff --git a/samples/app-checkin-location/csharp/AppCheckinLocation.sln b/samples/app-checkin-location/csharp/AppCheckinLocation.sln deleted file mode 100644 index b53d012805..0000000000 --- a/samples/app-checkin-location/csharp/AppCheckinLocation.sln +++ /dev/null @@ -1,37 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.11.35327.3 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppCheckinLocation", "AppCheckinLocation\AppCheckinLocation.csproj", "{BFDF6B3D-BB62-42DD-88EF-528FFE9FCBAD}" -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", "{5D189F39-4203-4965-B996-947F7A8EA57E}" - ProjectSection(SolutionItems) = preProject - AppCheckinLocation.slnLaunch.user = AppCheckinLocation.slnLaunch.user - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {BFDF6B3D-BB62-42DD-88EF-528FFE9FCBAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BFDF6B3D-BB62-42DD-88EF-528FFE9FCBAD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BFDF6B3D-BB62-42DD-88EF-528FFE9FCBAD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BFDF6B3D-BB62-42DD-88EF-528FFE9FCBAD}.Release|Any CPU.Build.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 = {8F7F570D-74C3-4CF3-AACD-361A3D3F8E37} - EndGlobalSection -EndGlobal diff --git a/samples/app-checkin-location/csharp/AppCheckinLocation.slnLaunch.user b/samples/app-checkin-location/csharp/AppCheckinLocation.slnLaunch.user index 8aba718afc..c104d6bb7a 100644 --- a/samples/app-checkin-location/csharp/AppCheckinLocation.slnLaunch.user +++ b/samples/app-checkin-location/csharp/AppCheckinLocation.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": "AppCheckinLocation\\AppCheckinLocation.csproj", + "Name": "AppCheckinLocation\\AppCheckinLocation.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": "AppCheckinLocation\\AppCheckinLocation.csproj", + "Name": "AppCheckinLocation\\AppCheckinLocation.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": "AppCheckinLocation\\AppCheckinLocation.csproj", + "Name": "AppCheckinLocation\\AppCheckinLocation.csproj", "Action": "Start", "DebugTarget": "Start Project" - }, - { - "Path": "M365Agent\\M365Agent.ttkproj", - "Action": "StartWithoutDebugging", - "DebugTarget": "Microsoft Teams (browser) (skip update app)" } ] } diff --git a/samples/app-checkin-location/csharp/AppCheckinLocation.slnx b/samples/app-checkin-location/csharp/AppCheckinLocation.slnx new file mode 100644 index 0000000000..5b174f95fe --- /dev/null +++ b/samples/app-checkin-location/csharp/AppCheckinLocation.slnx @@ -0,0 +1,6 @@ + + + + + + diff --git a/samples/app-checkin-location/csharp/AppCheckinLocation/.gitignore b/samples/app-checkin-location/csharp/AppCheckinLocation/.gitignore index 7466c01f9c..89b80db704 100644 --- a/samples/app-checkin-location/csharp/AppCheckinLocation/.gitignore +++ b/samples/app-checkin-location/csharp/AppCheckinLocation/.gitignore @@ -22,4 +22,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/app-checkin-location/csharp/AppCheckinLocation/AdapterWithErrorHandler.cs b/samples/app-checkin-location/csharp/AppCheckinLocation/AdapterWithErrorHandler.cs deleted file mode 100644 index da746e2cd4..0000000000 --- a/samples/app-checkin-location/csharp/AppCheckinLocation/AdapterWithErrorHandler.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Threading.Tasks; -using Microsoft.Bot.Builder; -using Microsoft.Bot.Connector; -using Microsoft.Bot.Builder.Integration.AspNet.Core; -using Microsoft.Bot.Schema; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; -using System.Net.Http; - -namespace AppCheckinLocation -{ - public class AdapterWithErrorHandler : CloudAdapter - { - public AdapterWithErrorHandler(IConfiguration configuration, IHttpClientFactory httpClientFactory, ILogger logger, ConversationState conversationState = default) - : base(configuration, httpClientFactory, 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}"); - - // Send a trace activity, which will be displayed in the Bot Framework Emulator - await SendTraceActivityAsync(turnContext, exception); - - // Uncomment below commented line for local debugging. - // await turnContext.SendActivityAsync($"Sorry, it looks like something went wrong. Exception Caught: {exception.Message}"); - }; - } - - private static async Task SendTraceActivityAsync(ITurnContext turnContext, Exception exception) - { - // Only send a trace activity if we're talking to the Bot Framework Emulator - if (turnContext.Activity.ChannelId == Channels.Emulator) - { - Activity traceActivity = new Activity(ActivityTypes.Trace) - { - Label = "TurnError", - Name = "OnTurnError Trace", - Value = exception.Message, - ValueType = "https://www.botframework.com/schemas/error", - }; - - // Send a trace activity - await turnContext.SendActivityAsync(traceActivity); - } - } - } -} diff --git a/samples/app-checkin-location/csharp/AppCheckinLocation/AppCheckinLocation.csproj b/samples/app-checkin-location/csharp/AppCheckinLocation/AppCheckinLocation.csproj index 8960c930af..d7886df2e7 100644 --- a/samples/app-checkin-location/csharp/AppCheckinLocation/AppCheckinLocation.csproj +++ b/samples/app-checkin-location/csharp/AppCheckinLocation/AppCheckinLocation.csproj @@ -1,25 +1,29 @@ - + - net6.0 - latest + net10.0 + enable - - - - + + + + + - - - Always + + + + + PreserveNewest + None + + + + PreserveNewest + None - - - - - - + \ No newline at end of file diff --git a/samples/app-checkin-location/csharp/AppCheckinLocation/Bots/ActivityBot.cs b/samples/app-checkin-location/csharp/AppCheckinLocation/Bots/ActivityBot.cs deleted file mode 100644 index 267b0e8829..0000000000 --- a/samples/app-checkin-location/csharp/AppCheckinLocation/Bots/ActivityBot.cs +++ /dev/null @@ -1,356 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -// Generated with Bot Builder V4 SDK Template for Visual Studio v4.14.0 - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using AdaptiveCards; -using AppCheckinLocation.Models; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Bot.Builder; -using Microsoft.Bot.Builder.Teams; -using Microsoft.Bot.Schema; -using Microsoft.Bot.Schema.Teams; -using Microsoft.Extensions.Configuration; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace AppCheckinLocation.Bots -{ - /// - /// Bot Activity handler class. - /// - public class ActivityBot : TeamsActivityHandler - { - private readonly string _applicationBaseUrl; - private readonly IWebHostEnvironment _env; - protected readonly BotState _conversationState; - protected readonly IStatePropertyAccessor _UserDetail; - - public ActivityBot(IConfiguration configuration, IWebHostEnvironment env, ConversationState conversationState) - { - _conversationState = conversationState; - _env = env; - _applicationBaseUrl = configuration["ApplicationBaseUrl"] ?? throw new NullReferenceException("ApplicationBaseUrl"); - _UserDetail = conversationState.CreateProperty(nameof(UserLocationDetail)); - } - - /// - /// Handle when a message is addressed to the bot. - /// - /// The turn context. - /// The cancellation token. - /// A task that represents the work queued to execute. - protected override async Task OnMessageActivityAsync(ITurnContext turnContext, CancellationToken cancellationToken) - { - if(turnContext.Activity.Text.ToLower().Trim() == "viewcheckin") - { - var currentUserDetail = await this._UserDetail.GetAsync(turnContext, () => new UserLocationDetail()); - List userDetailsList = new List(); - - if (currentUserDetail.UserDetails == null) - { - await turnContext.SendActivityAsync(MessageFactory.Text("No last check in found")); - } - else - { - await turnContext.SendActivityAsync(MessageFactory.Attachment(GetAdaptiveCardForUserLastCheckIn(currentUserDetail.UserDetails)), cancellationToken); - } - } - else - { - await turnContext.SendActivityAsync(MessageFactory.Attachment(GetAdaptiveCardForTaskModule()), cancellationToken); - } - } - - /// - /// 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); - } - - /// - /// 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 you can checkin your location (use command 'checkin') and view your checked in location(use command 'viewcheckin')."), cancellationToken); - } - } - } - - /// - /// 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 == "checkin") - { - taskModuleResponse.Task = new TaskModuleContinueResponse - { - Type = "continue", - Value = new TaskModuleTaskInfo() - { - Url = _applicationBaseUrl + "/" + "CheckIn", - Height = 350, - Width = 350, - Title = "Check in details", - }, - }; - } - else if (buttonType == "viewLocation") - { - var latitude = (double)asJobject.ToObject>()?.Latitude; - var longitude = (double)asJobject.ToObject>()?.Longitude; - taskModuleResponse.Task = new TaskModuleContinueResponse - { - Type = "continue", - Value = new TaskModuleTaskInfo() - { - Url = _applicationBaseUrl + "/" + "ViewLocation?latitude="+ latitude+"&longitude="+longitude, - Height = 350, - Width = 350, - Title = "View location", - }, - }; - } - - 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 locationInfo = JObject.FromObject(taskModuleRequest.Data); - var latitude = (double)locationInfo.ToObject>()?.Latitude; - var longitude = (double)locationInfo.ToObject>()?.Longitude; - string user = turnContext.Activity.From.Name; - string time = turnContext.Activity.LocalTimestamp.ToString(); - - UserDetail userDetails = new UserDetail { - CheckInTime = time, - UserName = user, - Longitude = longitude, - Latitude = latitude, - UserId = turnContext.Activity.From.AadObjectId - }; - - await SaveUserDetailsAsync(turnContext,userDetails); - await turnContext.SendActivityAsync(MessageFactory.Attachment(GetAdaptiveCardForUserLocation(time, user, latitude, longitude)), cancellationToken); - - return null; - } - - /// - /// Sample Adaptive card for check in button. - /// - private Attachment GetAdaptiveCardForTaskModule() - { - AdaptiveCard card = new AdaptiveCard(new AdaptiveSchemaVersion("1.2")) - { - Body = new List - { - new AdaptiveTextBlock - { - Text = "Please click on check in", - Weight = AdaptiveTextWeight.Bolder, - Spacing = AdaptiveSpacing.Medium, - } - }, - Actions = new List - { - new AdaptiveSubmitAction - { - Title = "Check in", - Data = new AdaptiveCardAction - { - MsteamsCardAction = new CardAction - { - Type = "task/fetch", - }, - Id="checkin" - }, - } - }, - }; - - return new Attachment() - { - ContentType = AdaptiveCard.ContentType, - Content = card, - }; - } - - /// - /// Sample Adaptive card for user current location info. - /// - private Attachment GetAdaptiveCardForUserLocation(string time, string user, double latitude, double longitude) - { - AdaptiveCard card = new AdaptiveCard(new AdaptiveSchemaVersion("1.2")) - { - Body = new List - { - new AdaptiveTextBlock - { - Text = $"User name :{user}", - Weight = AdaptiveTextWeight.Bolder, - Spacing = AdaptiveSpacing.Medium, - Wrap = true - }, - new AdaptiveTextBlock - { - Text = $"Check in time: {time}", - Weight = AdaptiveTextWeight.Bolder, - Spacing = AdaptiveSpacing.Medium, - Wrap = true - } - }, - Actions = new List - { - new AdaptiveSubmitAction - { - Title = "View location", - Data = new AdaptiveCardAction - { - MsteamsCardAction = new CardAction - { - Type = "task/fetch", - }, - Id = "viewLocation", - Latitude = latitude, - Longitude = longitude - }, - } - }, - }; - - return new Attachment() - { - ContentType = AdaptiveCard.ContentType, - Content = card, - }; - } - - /// - /// Sample Adaptive card for user's last check in's. - /// - private List GetAdaptiveCardForUserLastCheckIn(List userDetails) - { - List attachmentList = new List(); - - foreach (var user in userDetails) - { - AdaptiveCard card = new AdaptiveCard(new AdaptiveSchemaVersion("1.2")) - { - Body = new List - { - new AdaptiveTextBlock - { - Text = $"User name :{user.UserName}", - Weight = AdaptiveTextWeight.Bolder, - Spacing = AdaptiveSpacing.Medium, - Wrap = true - }, - new AdaptiveTextBlock - { - Text = $"Check in time: {user.CheckInTime}", - Weight = AdaptiveTextWeight.Bolder, - Spacing = AdaptiveSpacing.Medium, - Wrap = true - } - }, - Actions = new List - { - new AdaptiveSubmitAction - { - Title = "View location", - Data = new AdaptiveCardAction - { - MsteamsCardAction = new CardAction - { - Type = "task/fetch", - }, - Id = "viewLocation", - Latitude = user.Latitude, - Longitude = user.Longitude - }, - } - }, - }; - - Attachment attachment = new Attachment() - { - ContentType = AdaptiveCard.ContentType, - Content = card, - }; - - attachmentList.Add(attachment); - } - - return attachmentList; - } - - // Save user details in json file. - private async Task SaveUserDetailsAsync(ITurnContext turnContext, UserDetail userDetails) - { - var currentUserDetail = await this._UserDetail.GetAsync(turnContext, () => new UserLocationDetail()); - List userDetailsList = new List(); - if (currentUserDetail.UserDetails == null) - { - userDetailsList.Add(userDetails); - currentUserDetail.UserDetails = userDetailsList; - await this._UserDetail.SetAsync(turnContext, currentUserDetail); - } - else if (currentUserDetail.UserDetails.Count == 10) - { - currentUserDetail.UserDetails.RemoveAt(0); - userDetailsList = currentUserDetail.UserDetails; - userDetailsList.Add(userDetails); - currentUserDetail.UserDetails = userDetailsList; - await this._UserDetail.SetAsync(turnContext, currentUserDetail); - } - else - { - userDetailsList = currentUserDetail.UserDetails; - userDetailsList.Add(userDetails); - currentUserDetail.UserDetails = userDetailsList; - await this._UserDetail.SetAsync(turnContext, currentUserDetail); - } - } - } -} \ No newline at end of file diff --git a/samples/app-checkin-location/csharp/AppCheckinLocation/Config.cs b/samples/app-checkin-location/csharp/AppCheckinLocation/Config.cs new file mode 100644 index 0000000000..2594790b29 --- /dev/null +++ b/samples/app-checkin-location/csharp/AppCheckinLocation/Config.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace AppCheckinLocation +{ + public class ConfigOptions + { + public string ApplicationBaseUrl { 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/app-checkin-location/csharp/AppCheckinLocation/Controllers/BotController.cs b/samples/app-checkin-location/csharp/AppCheckinLocation/Controllers/BotController.cs deleted file mode 100644 index f5f3f859a9..0000000000 --- a/samples/app-checkin-location/csharp/AppCheckinLocation/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 AppCheckinLocation.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); - } - } -} diff --git a/samples/app-checkin-location/csharp/AppCheckinLocation/Controllers/Controller.cs b/samples/app-checkin-location/csharp/AppCheckinLocation/Controllers/Controller.cs new file mode 100644 index 0000000000..b201be3b61 --- /dev/null +++ b/samples/app-checkin-location/csharp/AppCheckinLocation/Controllers/Controller.cs @@ -0,0 +1,347 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using AppCheckinLocation.Models; +using Microsoft.Extensions.Configuration; +using Microsoft.Teams.Api.Activities; +using Microsoft.Teams.Apps; +using Microsoft.Teams.Apps.Activities; +using Microsoft.Teams.Apps.Annotations; +using Microsoft.Teams.Cards; +using System.Collections.Concurrent; +using System.Text.Json; + +namespace AppCheckinLocation.Controllers +{ + /// + /// Handles location check-in functionality with task modules + /// + [TeamsController] + public class Controller + { + private readonly string _applicationBaseUrl; + private static readonly ConcurrentDictionary _userLocationData = new(); + + public Controller(IConfiguration configuration) + { + _applicationBaseUrl = configuration["ApplicationBaseUrl"] ?? throw new NullReferenceException("ApplicationBaseUrl"); + } + + /// + /// Handles conversation members added event + /// Sends welcome message with check-in instructions + /// + [Conversation.MembersAdded] + public async Task OnMembersAdded([Context] ConversationUpdateActivity activity, [Context] IContext.Client client, [Context] Microsoft.Teams.Common.Logging.ILogger log) + { + foreach (var member in activity.MembersAdded) + { + if (member.Id != activity.Recipient.Id) + { + await client.Send("Hello and welcome! With this sample you can checkin your location (use command 'checkin') and view your checked in location(use command 'viewcheckin')."); + } + } + } + + /// + /// Handles incoming messages + /// + [Message] + public async Task OnMessage([Context] MessageActivity activity, [Context] IContext.Client client, [Context] Microsoft.Teams.Common.Logging.ILogger log) + { + if (string.IsNullOrEmpty(activity.Text)) + { + var card = GetAdaptiveCardForTaskModule(); + if (card != null) + { + await client.Send(card); + } + return; + } + + var text = activity.Text.ToLower().Trim(); + if (text == "viewcheckin") + { + var userId = activity.From.AadObjectId ?? activity.From.Id; + if (_userLocationData.TryGetValue(userId, out var currentUserDetail) && currentUserDetail.UserDetails != null) + { + var attachments = GetAdaptiveCardForUserLastCheckIn(currentUserDetail.UserDetails); + foreach (var attachment in attachments) + { + if (attachment != null) + { + await client.Send(attachment); + } + } + } + else + { + await client.Send("No last check in found"); + } + } + else + { + var card = GetAdaptiveCardForTaskModule(); + if (card != null) + { + await client.Send(card); + } + } + } + + /// + /// Handles task/fetch invoke - Opens task module for check-in or location view + /// + [Microsoft.Teams.Apps.Activities.Invokes.Invoke("task/fetch")] + public object OnTaskFetch([Context] InvokeActivity activity, [Context] Microsoft.Teams.Common.Logging.ILogger log) + { + if (activity.Value == null) + { + return new + { + task = new + { + type = "continue", + value = new + { + url = $"{_applicationBaseUrl}/CheckIn", + height = 350, + width = 350, + title = "Check in details" + } + } + }; + } + + var valueJson = JsonSerializer.Serialize(activity.Value); + var taskData = JsonSerializer.Deserialize(valueJson); + JsonElement dataProperty = taskData; + if (taskData.TryGetProperty("data", out var dataNode)) + { + dataProperty = dataNode; + } + string? buttonType = null; + if (dataProperty.TryGetProperty("id", out var idProperty)) + { + buttonType = idProperty.GetString(); + } + + if (buttonType == "checkin") + { + return new + { + task = new + { + type = "continue", + value = new + { + url = $"{_applicationBaseUrl}/CheckIn", + height = 350, + width = 350, + title = "Check in details" + } + } + }; + } + else if (buttonType == "viewLocation") + { + var latitude = dataProperty.GetProperty("latitude").GetDouble(); + var longitude = dataProperty.GetProperty("longitude").GetDouble(); + return new + { + task = new + { + type = "continue", + value = new + { + url = $"{_applicationBaseUrl}/ViewLocation?latitude={latitude}&longitude={longitude}", + height = 350, + width = 350, + title = "View location" + } + } + }; + } + return new { task = new { type = "continue" } }; + } + + /// + /// Handles task/submit invoke - Saves location data and sends confirmation + /// + [Microsoft.Teams.Apps.Activities.Invokes.Invoke("task/submit")] + public async Task OnTaskSubmit([Context] InvokeActivity activity, [Context] IContext.Client client, [Context] Microsoft.Teams.Common.Logging.ILogger log) + { + var activityJson = JsonSerializer.Serialize(activity); + var activityElement = JsonSerializer.Deserialize(activityJson); + if (!activityElement.TryGetProperty("value", out var valueElement)) + { + await client.Send("Error: No location data received"); + return new { }; + } + var taskData = valueElement; + var latitude = latElement.GetDouble(); + var longitude = lonElement.GetDouble(); + var user = activity.From?.Name ?? "User"; + var time = activity.LocalTimestamp?.ToString() ?? DateTime.Now.ToString(); + var userId = activity.From?.AadObjectId ?? activity.From?.Id ?? "unknown"; + var userDetails = new UserDetail + { + CheckInTime = time, + UserName = user, + Longitude = longitude, + Latitude = latitude, + UserId = userId + }; + SaveUserDetails(userId, userDetails); + await client.Send(GetAdaptiveCardForUserLocation(time, user, latitude, longitude)); + return null; + } + + /// + /// Adaptive card for check-in button + /// + private dynamic? GetAdaptiveCardForTaskModule() + { + var cardJson = @"{ + ""$schema"": ""http://adaptivecards.io/schemas/adaptive-card.json"", + ""type"": ""AdaptiveCard"", + ""version"": ""1.4"", + ""body"": [ + { + ""type"": ""TextBlock"", + ""text"": ""Please click on check in"", + ""weight"": ""Bolder"", + ""spacing"": ""Medium"" + } + ], + ""actions"": [ + { + ""type"": ""Action.Submit"", + ""title"": ""Check in"", + ""data"": { + ""msteams"": { + ""type"": ""task/fetch"" + }, + ""id"": ""checkin"" + } + } + ] + }"; + return AdaptiveCard.Deserialize(cardJson); + } + + /// + /// Adaptive card for user's current location info + /// + private dynamic? GetAdaptiveCardForUserLocation(string time, string user, double latitude, double longitude) + { + var cardJson = $@"{{ + ""$schema"": ""http://adaptivecards.io/schemas/adaptive-card.json"", + ""type"": ""AdaptiveCard"", + ""version"": ""1.4"", + ""body"": [ + {{ + ""type"": ""TextBlock"", + ""text"": ""User name: {user}"", + ""weight"": ""Bolder"", + ""spacing"": ""Medium"", + ""wrap"": true + }}, + {{ + ""type"": ""TextBlock"", + ""text"": ""Check in time: {time}"", + ""weight"": ""Bolder"", + ""spacing"": ""Medium"", + ""wrap"": true + }} + ], + ""actions"": [ + {{ + ""type"": ""Action.Submit"", + ""title"": ""View location"", + ""data"": {{ + ""msteams"": {{ + ""type"": ""task/fetch"" + }}, + ""id"": ""viewLocation"", + ""latitude"": {latitude}, + ""longitude"": {longitude} + }} + }} + ] + }}"; + return AdaptiveCard.Deserialize(cardJson); + } + + /// + /// Adaptive cards for user's last check-ins + /// + private List GetAdaptiveCardForUserLastCheckIn(List userDetails) + { + var cardList = new List(); + + foreach (var user in userDetails) + { + var cardJson = $@"{{ + ""$schema"": ""http://adaptivecards.io/schemas/adaptive-card.json"", + ""type"": ""AdaptiveCard"", + ""version"": ""1.4"", + ""body"": [ + {{ + ""type"": ""TextBlock"", + ""text"": ""User name: {user.UserName}"", + ""weight"": ""Bolder"", + ""spacing"": ""Medium"", + ""wrap"": true + }}, + {{ + ""type"": ""TextBlock"", + ""text"": ""Check in time: {user.CheckInTime}"", + ""weight"": ""Bolder"", + ""spacing"": ""Medium"", + ""wrap"": true + }} + ], + ""actions"": [ + {{ + ""type"": ""Action.Submit"", + ""title"": ""View location"", + ""data"": {{ + ""msteams"": {{ + ""type"": ""task/fetch"" + }}, + ""id"": ""viewLocation"", + ""latitude"": {user.Latitude}, + ""longitude"": {user.Longitude} + }} + }} + ] + }}"; + cardList.Add(AdaptiveCard.Deserialize(cardJson)); + } + return cardList; + } + + /// + /// Saves user check-in details (up to 10 check-ins per user) + /// + private void SaveUserDetails(string userId, UserDetail userDetails) + { + var userLocationDetail = _userLocationData.GetOrAdd(userId, _ => new UserLocationDetail { UserDetails = new List() }); + + if (userLocationDetail.UserDetails == null) + { + userLocationDetail.UserDetails = new List { userDetails }; + } + else if (userLocationDetail.UserDetails.Count == 10) + { + userLocationDetail.UserDetails.RemoveAt(0); + userLocationDetail.UserDetails.Add(userDetails); + } + else + { + userLocationDetail.UserDetails.Add(userDetails); + } + } + } +} \ No newline at end of file diff --git a/samples/app-checkin-location/csharp/AppCheckinLocation/Models/AdaptiveCardAction.cs b/samples/app-checkin-location/csharp/AppCheckinLocation/Models/AdaptiveCardAction.cs index 141bcfb356..c4bb53c4dd 100644 --- a/samples/app-checkin-location/csharp/AppCheckinLocation/Models/AdaptiveCardAction.cs +++ b/samples/app-checkin-location/csharp/AppCheckinLocation/Models/AdaptiveCardAction.cs @@ -1,39 +1,32 @@ -// +// // Copyright (c) Microsoft. All rights reserved. // +using System.Text.Json.Serialization; + namespace AppCheckinLocation.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; } + [JsonPropertyName("msteams")] + public TaskAction MsteamsCardAction { get; set; } - /// - /// Gets or sets id value of turncontext activity. - /// - [JsonProperty("id")] + [JsonPropertyName("id")] public string Id { get; set; } - /// - /// Gets or sets id value of longitude. - /// - [JsonProperty("longitude")] + [JsonPropertyName("longitude")] public double Longitude { get; set; } - /// - /// Gets or sets id value of longitude. - /// - [JsonProperty("latitude")] + [JsonPropertyName("latitude")] public double Latitude { get; set; } } + + public class TaskAction + { + [JsonPropertyName("type")] + public string Type { get; set; } + } } diff --git a/samples/app-checkin-location/csharp/AppCheckinLocation/Models/CardTaskFetchValue.cs b/samples/app-checkin-location/csharp/AppCheckinLocation/Models/CardTaskFetchValue.cs index b1fea4bf46..9d1a2399b4 100644 --- a/samples/app-checkin-location/csharp/AppCheckinLocation/Models/CardTaskFetchValue.cs +++ b/samples/app-checkin-location/csharp/AppCheckinLocation/Models/CardTaskFetchValue.cs @@ -1,8 +1,8 @@ -// +// // Copyright (c) Microsoft. All rights reserved. // -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace AppCheckinLocation.Models { @@ -11,13 +11,13 @@ namespace AppCheckinLocation.Models /// public class CardTaskFetchValue { - [JsonProperty("type")] + [JsonPropertyName("type")] public object Type { get; set; } = "task/fetch"; - [JsonProperty("id")] + [JsonPropertyName("id")] public object Id { get; set; } - [JsonProperty("data")] + [JsonPropertyName("data")] public T Data { get; set; } } } diff --git a/samples/app-checkin-location/csharp/AppCheckinLocation/Models/LocationDetails.cs b/samples/app-checkin-location/csharp/AppCheckinLocation/Models/LocationDetails.cs index 264aaf52f7..2f58f14fa3 100644 --- a/samples/app-checkin-location/csharp/AppCheckinLocation/Models/LocationDetails.cs +++ b/samples/app-checkin-location/csharp/AppCheckinLocation/Models/LocationDetails.cs @@ -1,8 +1,8 @@ -// +// // Copyright (c) Microsoft. All rights reserved. // -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace AppCheckinLocation.Models { @@ -11,10 +11,10 @@ namespace AppCheckinLocation.Models /// public class LocationDetails { - [JsonProperty("latitude")] + [JsonPropertyName("latitude")] public object Latitude { get; set; } - [JsonProperty("longitude")] + [JsonPropertyName("longitude")] public object Longitude { get; set; } } } diff --git a/samples/app-checkin-location/csharp/AppCheckinLocation/Models/UserDetail.cs b/samples/app-checkin-location/csharp/AppCheckinLocation/Models/UserDetail.cs index 5fbb4b4166..7819e16a25 100644 --- a/samples/app-checkin-location/csharp/AppCheckinLocation/Models/UserDetail.cs +++ b/samples/app-checkin-location/csharp/AppCheckinLocation/Models/UserDetail.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) Microsoft. All rights reserved. // diff --git a/samples/app-checkin-location/csharp/AppCheckinLocation/Models/UserLocationDetail.cs b/samples/app-checkin-location/csharp/AppCheckinLocation/Models/UserLocationDetail.cs index 62a91151a6..1755488d9c 100644 --- a/samples/app-checkin-location/csharp/AppCheckinLocation/Models/UserLocationDetail.cs +++ b/samples/app-checkin-location/csharp/AppCheckinLocation/Models/UserLocationDetail.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) Microsoft. All rights reserved. // @@ -6,11 +6,11 @@ namespace AppCheckinLocation.Models { + /// + /// User location detail model class. + /// public class UserLocationDetail { - /// - /// User location detail model class. - /// public List UserDetails { get; set; } } } diff --git a/samples/app-checkin-location/csharp/AppCheckinLocation/Program.cs b/samples/app-checkin-location/csharp/AppCheckinLocation/Program.cs index 25ff2d6ed4..b991401ce3 100644 --- a/samples/app-checkin-location/csharp/AppCheckinLocation/Program.cs +++ b/samples/app-checkin-location/csharp/AppCheckinLocation/Program.cs @@ -1,25 +1,52 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// 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.Hosting; -using Microsoft.Extensions.Hosting; +using AppCheckinLocation; +using AppCheckinLocation.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 AppCheckinLocation +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 { - 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); } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); - } + )); } + +builder.Services.AddRazorPages(); +builder.Services.AddSingleton(); +builder.AddTeams(appBuilder); + +var app = builder.Build(); +app.UseStaticFiles(); +app.MapRazorPages(); +app.UseTeams(); +app.Run(); \ No newline at end of file diff --git a/samples/app-checkin-location/csharp/AppCheckinLocation/Properties/launchSettings.json b/samples/app-checkin-location/csharp/AppCheckinLocation/Properties/launchSettings.json index 88d2fadc5a..3572a7a03f 100644 --- a/samples/app-checkin-location/csharp/AppCheckinLocation/Properties/launchSettings.json +++ b/samples/app-checkin-location/csharp/AppCheckinLocation/Properties/launchSettings.json @@ -1,13 +1,26 @@ -{ +{ "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:3978", + "applicationUrl": "http://localhost:5130", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, "hotReloadProfile": "aspnetcore" - } + }, } } \ No newline at end of file diff --git a/samples/app-checkin-location/csharp/AppCheckinLocation/Startup.cs b/samples/app-checkin-location/csharp/AppCheckinLocation/Startup.cs deleted file mode 100644 index 0d11a895a8..0000000000 --- a/samples/app-checkin-location/csharp/AppCheckinLocation/Startup.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -// -// Generated with Bot Builder V4 SDK Template for Visual Studio v4.14.0 - -using AppCheckinLocation.Bots; -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.Extensions.Hosting; - -namespace AppCheckinLocation -{ - 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) - { - services.AddControllers().AddNewtonsoftJson(); - - services.AddHttpClient().AddControllers().AddNewtonsoftJson(); - services.AddRazorPages(); - - // Create the Bot Framework Adapter with error handling enabled. - services.AddSingleton(); - - // Create the storage we'll be using for User and Conversation state. (Memory is great for testing purposes.) - services.AddSingleton(); - - // Create the Conversation state. (Used by the Dialog system itself.) - services.AddSingleton(); - - // Create the bot as a transient. In this case the ASP Controller is expecting an IBot. - 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(); - app.UseStaticFiles(); - app.UseWebSockets() - .UseRouting() - .UseAuthorization() - .UseEndpoints(endpoints => - { - endpoints.MapControllers(); - endpoints.MapRazorPages(); - }); - } - } -} diff --git a/samples/app-checkin-location/csharp/AppCheckinLocation/appsettings.Development.json b/samples/app-checkin-location/csharp/AppCheckinLocation/appsettings.Development.json index b49abfc201..f8b9500141 100644 --- a/samples/app-checkin-location/csharp/AppCheckinLocation/appsettings.Development.json +++ b/samples/app-checkin-location/csharp/AppCheckinLocation/appsettings.Development.json @@ -1,9 +1,19 @@ { - "Logging": { - "LogLevel": { - "Default": "Debug", - "System": "Information", - "Microsoft": "Information" - } - } - } + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + }, + "Microsoft.Teams": { + "Enable": "*", + "Level": "debug" + } + }, + "AllowedHosts": "*", + "Teams": { + "ClientId": "", + "ClientSecret": "", + "TenantId": "" + }, + "ApplicationBaseUrl": "" +} \ No newline at end of file diff --git a/samples/app-checkin-location/csharp/AppCheckinLocation/appsettings.Playground.json b/samples/app-checkin-location/csharp/AppCheckinLocation/appsettings.Playground.json new file mode 100644 index 0000000000..0497106b3c --- /dev/null +++ b/samples/app-checkin-location/csharp/AppCheckinLocation/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/app-checkin-location/csharp/AppCheckinLocation/appsettings.json b/samples/app-checkin-location/csharp/AppCheckinLocation/appsettings.json index f491de3503..cfdca4a79b 100644 --- a/samples/app-checkin-location/csharp/AppCheckinLocation/appsettings.json +++ b/samples/app-checkin-location/csharp/AppCheckinLocation/appsettings.json @@ -1,5 +1,19 @@ { - "MicrosoftAppId": "{{Microsoft-App-Id}}", - "MicrosoftAppPassword": "{{ Microsoft-App-Password}}", - "ApplicationBaseUrl": "{{ Application Base Url }}" + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + }, + "Microsoft.Teams": { + "Enable": "*", + "Level": "debug" + } + }, + "AllowedHosts": "*", + "ApplicationBaseUrl": "", + "Teams": { + "ClientId": "", + "ClientSecret": "", + "BotType": "" + } } \ No newline at end of file diff --git a/samples/app-checkin-location/csharp/M365Agent/.gitignore b/samples/app-checkin-location/csharp/M365Agent/.gitignore new file mode 100644 index 0000000000..c5cae9258c --- /dev/null +++ b/samples/app-checkin-location/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/app-checkin-location/csharp/M365Agent/M365Agent.ttkproj b/samples/app-checkin-location/csharp/M365Agent/M365Agent.atkproj similarity index 51% rename from samples/app-checkin-location/csharp/M365Agent/M365Agent.ttkproj rename to samples/app-checkin-location/csharp/M365Agent/M365Agent.atkproj index 03f52b9d0f..124eb75046 100644 --- a/samples/app-checkin-location/csharp/M365Agent/M365Agent.ttkproj +++ b/samples/app-checkin-location/csharp/M365Agent/M365Agent.atkproj @@ -1,12 +1,8 @@ - + - c7fd0e24-2254-4184-bf12-6c75d2752f28 + b069b3bd-f6bc-cc40-82ab-3fcc2ea50fdf - - - - diff --git a/samples/app-checkin-location/csharp/M365Agent/appPackage/color.png b/samples/app-checkin-location/csharp/M365Agent/appPackage/color.png index b8cf81afbe..01aa37e347 100644 Binary files a/samples/app-checkin-location/csharp/M365Agent/appPackage/color.png and b/samples/app-checkin-location/csharp/M365Agent/appPackage/color.png differ diff --git a/samples/app-checkin-location/csharp/M365Agent/appPackage/manifest.json b/samples/app-checkin-location/csharp/M365Agent/appPackage/manifest.json index 7471ea7a25..a96324cc77 100644 --- a/samples/app-checkin-location/csharp/M365Agent/appPackage/manifest.json +++ b/samples/app-checkin-location/csharp/M365Agent/appPackage/manifest.json @@ -1,61 +1,63 @@ -{ - "$schema": "https://developer.microsoft.com/json-schemas/teams/v1.19/MicrosoftTeams.schema.json", - "manifestVersion": "1.19", - "version": "1.0.0", - "id": "${{TEAMS_APP_ID}}", - "developer": { - "name": "Microsoft", - "websiteUrl": "https://www.microsoft.com", - "privacyUrl": "https://www.microsoft.com/privacy", - "termsOfUseUrl": "https://www.microsoft.com/termsofuse" - }, - "name": { - "short": "App check in location", - "full": "Get check in location of user" - }, - "description": { - "short": "Check in with your location and view past check-ins using a Teams bot.", - "full": "This sample showcases a Microsoft Teams bot that allows users to check in their current location and view all previous check-ins seamlessly." - }, - "icons": { - "outline": "outline.png", - "color": "color.png" - }, - "accentColor": "#60A18E", - "bots": [ - { - "botId": "${{AAD_APP_CLIENT_ID}}", - "scopes": [ - "personal" - ], - "commandLists": [ +{ + "$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", + "websiteUrl": "https://www.microsoft.com", + "privacyUrl": "https://www.microsoft.com/privacy", + "termsOfUseUrl": "https://www.microsoft.com/termsofuse" + }, + "icons": { + "color": "color.png", + "outline": "outline.png" + }, + "name": { + "short": "App check in location${{APP_NAME_SUFFIX}}", + "full": "Get check in location of user" + }, + "description": { + "short": "Check in with your location and view past check-ins using a Teams bot.", + "full": "This sample showcases a Microsoft Teams bot that allows users to check in their current location and view all previous check-ins seamlessly." + }, + "accentColor": "#60A18E", + "bots": [ { - "scopes": [ - "personal" - ], - "commands": [ - { - "title": "checkin", - "description": "To check in current location" - }, - { - "title": "viewcheckin", - "description": "To get all last checkin locations" - } - ] + "botId": "${{BOT_ID}}", + "scopes": [ + "personal" + ], + "supportsFiles": false, + "isNotificationOnly": false, + "commandLists": [ + { + "scopes": ["personal"], + "commands": [ + { + "title": "checkin", + "description": "To check in current location" + }, + { + "title": "viewcheckin", + "description": "To get all last checkin locations" + } + ] + } + ] } - ], - "isNotificationOnly": false - } - ], - "permissions": [ - "identity", - "messageTeamMembers" - ], - "devicePermissions": [ - "geolocation" - ], - "validDomains": [ - "${{BOT_DOMAIN}}" - ] + ], + "composeExtensions": [], + "configurableTabs": [], + "staticTabs": [], + "permissions": [ + "identity", + "messageTeamMembers" + ], + "devicePermissions": [ + "geolocation" + ], + "validDomains": [ + "${{BOT_DOMAIN}}" + ] } \ No newline at end of file diff --git a/samples/app-checkin-location/csharp/M365Agent/appPackage/outline.png b/samples/app-checkin-location/csharp/M365Agent/appPackage/outline.png index 2c3bf6fa65..f7a4c86447 100644 Binary files a/samples/app-checkin-location/csharp/M365Agent/appPackage/outline.png and b/samples/app-checkin-location/csharp/M365Agent/appPackage/outline.png differ diff --git a/samples/app-checkin-location/csharp/M365Agent/env/.env.dev b/samples/app-checkin-location/csharp/M365Agent/env/.env.dev new file mode 100644 index 0000000000..df4f9da508 --- /dev/null +++ b/samples/app-checkin-location/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/app-checkin-location/csharp/M365Agent/env/.env.local b/samples/app-checkin-location/csharp/M365Agent/env/.env.local index ae4c5f84cf..1ec78edd67 100644 --- a/samples/app-checkin-location/csharp/M365Agent/env/.env.local +++ b/samples/app-checkin-location/csharp/M365Agent/env/.env.local @@ -7,19 +7,8 @@ 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= - -RESOURCE_SUFFIX= -AZURE_SUBSCRIPTION_ID= -AZURE_RESOURCE_GROUP_NAME= -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= \ No newline at end of file +BOT_ENDPOINT= \ No newline at end of file diff --git a/samples/app-checkin-location/csharp/M365Agent/infra/azure.bicep b/samples/app-checkin-location/csharp/M365Agent/infra/azure.bicep index c3ce051b3d..658e412a21 100644 --- a/samples/app-checkin-location/csharp/M365Agent/infra/azure.bicep +++ b/samples/app-checkin-location/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/app-checkin-location/csharp/M365Agent/infra/azure.parameters.json b/samples/app-checkin-location/csharp/M365Agent/infra/azure.parameters.json index 6df7ae86a0..5c048c648a 100644 --- a/samples/app-checkin-location/csharp/M365Agent/infra/azure.parameters.json +++ b/samples/app-checkin-location/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": "app-checkin-location" - }, - "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": "AppCheckinLocation" + } } - } -} \ No newline at end of file + } \ No newline at end of file diff --git a/samples/app-checkin-location/csharp/M365Agent/infra/botRegistration/azurebot.bicep b/samples/app-checkin-location/csharp/M365Agent/infra/botRegistration/azurebot.bicep new file mode 100644 index 0000000000..a5a27b8fe4 --- /dev/null +++ b/samples/app-checkin-location/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/app-checkin-location/csharp/M365Agent/infra/botRegistration/readme.md b/samples/app-checkin-location/csharp/M365Agent/infra/botRegistration/readme.md new file mode 100644 index 0000000000..d5416243cd --- /dev/null +++ b/samples/app-checkin-location/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/app-checkin-location/csharp/M365Agent/launchSettings.json b/samples/app-checkin-location/csharp/M365Agent/launchSettings.json index d6491ef52c..2af8ce7a8a 100644 --- a/samples/app-checkin-location/csharp/M365Agent/launchSettings.json +++ b/samples/app-checkin-location/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/app-checkin-location/csharp/M365Agent/m365agents.local.yml b/samples/app-checkin-location/csharp/M365Agent/m365agents.local.yml index cbeb9772de..876d7c5b62 100644 --- a/samples/app-checkin-location/csharp/M365Agent/m365agents.local.yml +++ b/samples/app-checkin-location/csharp/M365Agent/m365agents.local.yml @@ -1,74 +1,68 @@ -# yaml-language-server: $schema=https://aka.ms/teams-toolkit/v1.2/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.2 - -additionalMetadata: - sampleTag: Microsoft-Teams-Samples:app-checkin-location-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: app-checkin-location-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: app-checkin-location-${{TEAMSFX_ENV}} + name: AppCheckinLocation${{APP_NAME_SUFFIX}} # Write the information of created resources into environment file for # the specified environment variable(s). - writeToEnvironmentFile: + 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_TYPE=MultiTenant"; - echo "::set-teamsfx-env MICROSOFT_APP_TENANT_ID=${{AAD_APP_TENANT_ID}}"; + # The Microsoft Entra application's display name + name: AppCheckinLocation${{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: ../AppCheckinLocation/appsettings.json + target: ../AppCheckinLocation/appsettings.Development.json content: - MicrosoftAppId: ${{AAD_APP_CLIENT_ID}} - MicrosoftAppPassword: ${{SECRET_AAD_APP_CLIENT_SECRET}} ApplicationBaseUrl: ${{BOT_ENDPOINT}} + Teams: + ClientId: ${{BOT_ID}} + ClientSecret: ${{SECRET_BOT_PASSWORD}} + TenantId: ${{TEAMS_APP_TENANT_ID}} - - uses: arm/deploy # Deploy given ARM templates parallelly. + # Create or update the bot registration on dev.botframework.com + - uses: botFramework/create 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. + botId: ${{BOT_ID}} + name: AppCheckinLocation + 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: # Path to manifest template manifestPath: ./appPackage/manifest.json outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip - outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json + outputFolder: ./appPackage/build # Validate app package using validation rules - uses: teamsApp/validateAppPackage with: @@ -81,4 +75,4 @@ provision: - uses: teamsApp/update with: # Relative path to this file. This is the path for built zip file. - appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip \ No newline at end of file + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip diff --git a/samples/app-checkin-location/csharp/M365Agent/m365agents.yml b/samples/app-checkin-location/csharp/M365Agent/m365agents.yml index 7d3c748b59..7bbfeb6c60 100644 --- a/samples/app-checkin-location/csharp/M365Agent/m365agents.yml +++ b/samples/app-checkin-location/csharp/M365Agent/m365agents.yml @@ -1,9 +1,88 @@ -# yaml-language-server: $schema=https://aka.ms/teams-toolkit/v1.2/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.2 - -additionalMetadata: - sampleTag: Microsoft-Teams-Samples:app-checkin-location-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: AppCheckinLocation${{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 + AppCheckinLocation.csproj + workingDirectory: ../AppCheckinLocation + # 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: ../AppCheckinLocation diff --git a/samples/app-checkin-location/csharp/README.md b/samples/app-checkin-location/csharp/README.md index b92f962071..51e7c4cb4a 100644 --- a/samples/app-checkin-location/csharp/README.md +++ b/samples/app-checkin-location/csharp/README.md @@ -92,10 +92,11 @@ The simplest way to run this sample in Teams is to use Microsoft 365 Agents Tool git clone https://github.com/OfficeDev/Microsoft-Teams-Samples.git ``` -- Modify the `/appsettings.json` and fill in the following details: - - `{{Microsoft-App-Id}}` - Generated from Step 1 while doing Microsoft Entra ID app registration in Azure portal. - - `{{ Microsoft-App-Password}}` - Generated from Step 1, also referred to as Client secret - - `{{ Application Base Url }}` - Your application's base url. E.g. https://12345.ngrok-free.app if you are using ngrok and if you are using dev tunnels, your URL will be https://12345.devtunnels.ms. +- Modify the `/appsettings.Development.json` and add the following configuration: + - `ClientId` - Generated from Step 1 while doing Microsoft Entra ID app registration in Azure portal (Microsoft App Id). + - `ClientSecret` - Generated from Step 1, also referred to as Client secret or Microsoft App Password. + - `TenantId` - Directory (tenant) ID from your Microsoft Entra ID app registration. + - `ApplicationBaseUrl` - Your application's base url. E.g. https://12345.ngrok-free.app if you are using ngrok and if you are using dev tunnels, your URL will be https://12345.devtunnels.ms. - Run the bot from a terminal or from Visual Studio: @@ -112,18 +113,16 @@ The simplest way to run this sample in Teams is to use Microsoft 365 Agents Tool - Select `AppCheckinLocation.csproj` file - Press `F5` to run the project -**Note**: If you are facing any issue in your app, please uncomment [this](https://github.com/OfficeDev/Microsoft-Teams-Samples/blob/main/samples/app-checkin-location/csharp/AppCheckinLocation/AdapterWithErrorHandler.cs#L33) line and put your debugger for local debug. - 4) Setup Manifest for Teams - __*This step is specific to Teams.*__ - - **Edit** the `manifest.json` contained in the ./AppManifest folder to replace your Microsoft App Id (that was created when you registered your app registration earlier) *everywhere* you see the place holder string `{{Microsoft-App-Id}}` (depending on the scenario the Microsoft App Id may occur multiple times in the `manifest.json`) + - **Edit** the `manifest.json` contained in the ./appPackage folder to replace your Microsoft App Id (that was created when you registered your app registration earlier) *everywhere* you see the place holder string `{{BOT_ID}}` (depending on the scenario the Microsoft App Id may occur multiple times in the `manifest.json`) - **Edit** the `manifest.json` for `validDomains` and replace `{{domain-name}}` with base Url of your 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 `AppManifest` 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 `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 the Apps view click "Upload a custom app") - Go to Microsoft Teams. From the lower left corner, select Apps - From the lower left corner, choose Upload a custom App - - Go to your project directory, the ./AppManifest folder, select the zip folder, and choose Open. + - Go to your project directory, the ./appPackage folder, select the zip folder, and choose Open. - Select Add in the pop-up dialog box. Your app is uploaded to Teams. ### Register your app with Azure AD. @@ -175,7 +174,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) +- [Teams SDK Documentation](https://learn.microsoft.com/en-us/microsoftteams/platform/teams-ai-library/welcome) - [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)