diff --git a/samples/.gitignore b/samples/.gitignore new file mode 100644 index 0000000..e28aefd --- /dev/null +++ b/samples/.gitignore @@ -0,0 +1,228 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates +target/ + +# Cake +/.cake +/version.txt +/PSRunCmds*.ps1 + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +/bin/ +/binSigned/ +/obj/ +Drop/ +target/ +Symbols/ +objd/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ +**/Properties/launchSettings.json + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +#nodeJS stuff +/node_modules/ + +#local development +appsettings.local.json +appsettings.Development.json +appsettings.Production.json +**/[Aa]ppManifest/*.zip +.deployment + +# JetBrains Rider +*.sln.iml +.idea + +# Mac files +.DS_Store + +# VS Code files +.vscode \ No newline at end of file diff --git a/samples/README.md b/samples/README.md index 96de2f3..4127b7b 100644 --- a/samples/README.md +++ b/samples/README.md @@ -11,6 +11,6 @@ To enable debugging, open this folder using Visual Studio Code (VSCode). Follow |Category | Name | Description | node | dotnet | python | |---------|-------------|-------------|--------|--------|--------| -| Basic | Echo Bot | Simplest bot | [basic/echo-bot/nodejs](./basic/echo-bot/nodejs) | TBD | TBD | +| Basic | Echo Bot | Simplest bot | [basic/echo-bot/nodejs](./basic/echo-bot/nodejs) | [basic/echo-bot/dotnet](./basic/echo-bot/dotnet) | TBD | | Basic | Copilot Studio Client | Consume CopilotStudio Agent | [basic/copilotstudio-client/nodejs](./basic/copilotstudio-client/nodejs) | TBD | TBD | | Complex | Copilot Studio Skill | Call the echo bot from a Copilot Studio skill | [complex/copilotstudio-skill/nodejs](./complex/copilotstudio-skill/nodejs) | TBD | TBD | diff --git a/samples/Samples.sln b/samples/Samples.sln new file mode 100644 index 0000000..b946818 --- /dev/null +++ b/samples/Samples.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.13.35818.85 d17.13 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EchoBot", "basic\echo-bot\dotnet\EchoBot.csproj", "{B1FC20C1-9900-FAC4-B8E6-223888633020}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B1FC20C1-9900-FAC4-B8E6-223888633020}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B1FC20C1-9900-FAC4-B8E6-223888633020}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B1FC20C1-9900-FAC4-B8E6-223888633020}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B1FC20C1-9900-FAC4-B8E6-223888633020}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {46F8E711-A447-4AB5-8357-03A5B5D21B49} + EndGlobalSection +EndGlobal diff --git a/samples/basic/echo-bot/dotnet/AspNetExtensions.cs b/samples/basic/echo-bot/dotnet/AspNetExtensions.cs new file mode 100644 index 0000000..6d8371a --- /dev/null +++ b/samples/basic/echo-bot/dotnet/AspNetExtensions.cs @@ -0,0 +1,216 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.Agents.Authentication; +using Microsoft.IdentityModel.Tokens; +using System.Globalization; +using Microsoft.IdentityModel.Protocols.OpenIdConnect; +using Microsoft.IdentityModel.Protocols; +using System.Collections.Concurrent; +using Microsoft.IdentityModel.Validators; +using System.IdentityModel.Tokens.Jwt; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using System.Collections.Generic; +using System; +using System.Threading.Tasks; +using System.Linq; +using System.Net.Http; + +#nullable disable + +namespace Microsoft.Agents.Samples +{ + public static class AspNetExtensions + { + private static readonly ConcurrentDictionary> _openIdMetadataCache = new(); + + /// + /// Adds token validation typical for ABS/SMBA and Bot-to-bot. + /// default to Azure Public Cloud. + /// + /// + /// + /// Name of the config section to read. + /// Optional logger to use for authentication event logging. + /// + /// Configuration: + /// + /// "TokenValidation": { + /// "Audiences": [ + /// "{required:bot-appid}" + /// ], + /// "TenantId": "{recommended:tenant-id}", + /// "ValidIssuers": [ + /// "{default:Public-AzureBotService}" + /// ], + /// "IsGov": {optional:false}, + /// "AzureBotServiceOpenIdMetadataUrl": optional, + /// "OpenIdMetadataUrl": optional, + /// "AzureBotServiceTokenHandling": "{optional:true}" + /// "OpenIdMetadataRefresh": "optional-12:00:00" + /// } + /// + /// + /// `IsGov` can be omitted, in which case public Azure Bot Service and Azure Cloud metadata urls are used. + /// `ValidIssuers` can be omitted, in which case the Public Azure Bot Service issuers are used. + /// `TenantId` can be omitted if the Agent is not being called by another Agent. Otherwise it is used to add other known issuers. Only when `ValidIssuers` is omitted. + /// `AzureBotServiceOpenIdMetadataUrl` can be omitted. In which case default values in combination with `IsGov` is used. + /// `OpenIdMetadataUrl` can be omitted. In which case default values in combination with `IsGov` is used. + /// `AzureBotServiceTokenHandling` defaults to true and should always be true until Azure Bot Service sends Entra ID token. + /// + public static void AddBotAspNetAuthentication(this IServiceCollection services, IConfiguration configuration, string tokenValidationSectionName = "TokenValidation", ILogger logger = null) + { + IConfigurationSection tokenValidationSection = configuration.GetSection(tokenValidationSectionName); + List validTokenIssuers = tokenValidationSection.GetSection("ValidIssuers").Get>(); + List audiences = tokenValidationSection.GetSection("Audiences").Get>(); + + if (!tokenValidationSection.Exists()) + { + logger?.LogError($"Missing configuration section '{tokenValidationSectionName}'. This section is required to be present in appsettings.json"); + throw new InvalidOperationException($"Missing configuration section '{tokenValidationSectionName}'. This section is required to be present in appsettings.json"); + } + + // If ValidIssuers is empty, default for ABS Public Cloud + if (validTokenIssuers == null || validTokenIssuers.Count == 0) + { + validTokenIssuers = + [ + "https://api.botframework.com", + "https://sts.windows.net/d6d49420-f39b-4df7-a1dc-d59a935871db/", + "https://login.microsoftonline.com/d6d49420-f39b-4df7-a1dc-d59a935871db/v2.0", + "https://sts.windows.net/f8cdef31-a31e-4b4a-93e4-5f571e91255a/", + "https://login.microsoftonline.com/f8cdef31-a31e-4b4a-93e4-5f571e91255a/v2.0", + "https://sts.windows.net/69e9b82d-4842-4902-8d1e-abc5b98a55e8/", + "https://login.microsoftonline.com/69e9b82d-4842-4902-8d1e-abc5b98a55e8/v2.0", + ]; + + string tenantId = tokenValidationSection["TenantId"]; + if (!string.IsNullOrEmpty(tenantId)) + { + validTokenIssuers.Add(string.Format(CultureInfo.InvariantCulture, AuthenticationConstants.ValidTokenIssuerUrlTemplateV1, tenantId)); + validTokenIssuers.Add(string.Format(CultureInfo.InvariantCulture, AuthenticationConstants.ValidTokenIssuerUrlTemplateV2, tenantId)); + } + } + + if (audiences == null || audiences.Count == 0) + { + throw new ArgumentException($"{tokenValidationSectionName}:Audiences requires at least one value"); + } + + bool isGov = tokenValidationSection.GetValue("IsGov", false); + var azureBotServiceTokenHandling = tokenValidationSection.GetValue("AzureBotServiceTokenHandling", true); + + // If the `AzureBotServiceOpenIdMetadataUrl` setting is not specified, use the default based on `IsGov`. This is what is used to authenticate ABS tokens. + var azureBotServiceOpenIdMetadataUrl = tokenValidationSection["AzureBotServiceOpenIdMetadataUrl"]; + if (string.IsNullOrEmpty(azureBotServiceOpenIdMetadataUrl)) + { + azureBotServiceOpenIdMetadataUrl = isGov ? AuthenticationConstants.GovAzureBotServiceOpenIdMetadataUrl : AuthenticationConstants.PublicAzureBotServiceOpenIdMetadataUrl; + } + + // If the `OpenIdMetadataUrl` setting is not specified, use the default based on `IsGov`. This is what is used to authenticate Entra ID tokens. + var openIdMetadataUrl = tokenValidationSection["OpenIdMetadataUrl"]; + if (string.IsNullOrEmpty(openIdMetadataUrl)) + { + openIdMetadataUrl = isGov ? AuthenticationConstants.GovOpenIdMetadataUrl : AuthenticationConstants.PublicOpenIdMetadataUrl; + } + + var openIdRefreshInterval = tokenValidationSection.GetValue("OpenIdMetadataRefresh", BaseConfigurationManager.DefaultAutomaticRefreshInterval); + + services.AddAuthentication(options => + { + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + }) + .AddJwtBearer(options => + { + options.SaveToken = true; + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidateAudience = true, + ValidateLifetime = true, + ClockSkew = TimeSpan.FromMinutes(5), + ValidIssuers = validTokenIssuers, + ValidAudiences = audiences, + ValidateIssuerSigningKey = true, + RequireSignedTokens = true, + }; + + // Using Microsoft.IdentityModel.Validators + options.TokenValidationParameters.EnableAadSigningKeyIssuerValidation(); + + options.Events = new JwtBearerEvents + { + // Create a ConfigurationManager based on the requestor. This is to handle ABS non-Entra tokens. + OnMessageReceived = async context => + { + var authorizationHeader = context.Request.Headers.Authorization.ToString(); + + if (string.IsNullOrEmpty(authorizationHeader)) + { + // Default to AadTokenValidation handling + context.Options.TokenValidationParameters.ConfigurationManager ??= options.ConfigurationManager as BaseConfigurationManager; + await Task.CompletedTask.ConfigureAwait(false); + return; + } + + string[] parts = authorizationHeader?.Split(' '); + if (parts.Length != 2 || parts[0] != "Bearer") + { + // Default to AadTokenValidation handling + context.Options.TokenValidationParameters.ConfigurationManager ??= options.ConfigurationManager as BaseConfigurationManager; + await Task.CompletedTask.ConfigureAwait(false); + return; + } + + JwtSecurityToken token = new(parts[1]); + var issuer = token.Claims.FirstOrDefault(claim => claim.Type == AuthenticationConstants.IssuerClaim)?.Value; + + if (azureBotServiceTokenHandling && AuthenticationConstants.BotFrameworkTokenIssuer.Equals(issuer)) + { + // Use the Bot Framework authority for this configuration manager + context.Options.TokenValidationParameters.ConfigurationManager = _openIdMetadataCache.GetOrAdd(azureBotServiceOpenIdMetadataUrl, key => + { + return new ConfigurationManager(azureBotServiceOpenIdMetadataUrl, new OpenIdConnectConfigurationRetriever(), new HttpClient()) + { + AutomaticRefreshInterval = openIdRefreshInterval + }; + }); + } + else + { + context.Options.TokenValidationParameters.ConfigurationManager = _openIdMetadataCache.GetOrAdd(openIdMetadataUrl, key => + { + return new ConfigurationManager(openIdMetadataUrl, new OpenIdConnectConfigurationRetriever(), new HttpClient()) + { + AutomaticRefreshInterval = openIdRefreshInterval + }; + }); + } + + await Task.CompletedTask.ConfigureAwait(false); + }, + + OnTokenValidated = context => + { + logger?.LogDebug("TOKEN Validated"); + return Task.CompletedTask; + }, + OnForbidden = context => + { + logger?.LogWarning(context.Result.ToString()); + return Task.CompletedTask; + }, + OnAuthenticationFailed = context => + { + logger?.LogWarning(context.Exception.ToString()); + return Task.CompletedTask; + } + }; + }); + } + } +} diff --git a/samples/basic/echo-bot/dotnet/BotController.cs b/samples/basic/echo-bot/dotnet/BotController.cs new file mode 100644 index 0000000..70a1ebb --- /dev/null +++ b/samples/basic/echo-bot/dotnet/BotController.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Agents.Hosting.AspNetCore; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Agents.BotBuilder; + +namespace EchoBot +{ + // ASP.Net Controller that receives incoming HTTP requests from the Azure Bot Service or other configured event activity protocol sources. + // When called, the request has already been authorized and credentials and tokens validated. + [Authorize] + [ApiController] + [Route("api/messages")] + public class BotController(IBotHttpAdapter adapter, IBot bot) : ControllerBase + { + [HttpPost] + public Task PostAsync(CancellationToken cancellationToken) + => adapter.ProcessAsync(Request, Response, bot, cancellationToken); + + } +} diff --git a/samples/basic/echo-bot/dotnet/EchoBot.csproj b/samples/basic/echo-bot/dotnet/EchoBot.csproj new file mode 100644 index 0000000..1b6be96 --- /dev/null +++ b/samples/basic/echo-bot/dotnet/EchoBot.csproj @@ -0,0 +1,30 @@ + + + + net8.0 + latest + disable + + + + + + + + + + + + + + + + + + + + + Always + + + diff --git a/samples/basic/echo-bot/dotnet/MyBot.cs b/samples/basic/echo-bot/dotnet/MyBot.cs new file mode 100644 index 0000000..5aaab20 --- /dev/null +++ b/samples/basic/echo-bot/dotnet/MyBot.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.BotBuilder; +using Microsoft.Agents.BotBuilder.App; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.Core.Models; +using System.Threading.Tasks; +using System.Threading; + +namespace EchoBot +{ + public class MyBot : AgentApplication + { + public MyBot(AgentApplicationOptions options) : base(options) + { + OnConversationUpdate(ConversationUpdateEvents.MembersAdded, WelcomeMessageAsync); + + // Listen for ANY message to be received. MUST BE AFTER ANY OTHER MESSAGE HANDLERS + OnActivity(ActivityTypes.Message, OnMessageAsync); + } + + protected async Task WelcomeMessageAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) + { + foreach (ChannelAccount member in turnContext.Activity.MembersAdded) + { + if (member.Id != turnContext.Activity.Recipient.Id) + { + await turnContext.SendActivityAsync(MessageFactory.Text("Hello and Welcome!"), cancellationToken); + } + } + } + + protected async Task OnMessageAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) + { + // Increment count state. + int count = turnState.Conversation.IncrementMessageCount(); + + await turnContext.SendActivityAsync($"[{count}] you said: {turnContext.Activity.Text}", cancellationToken: cancellationToken); + } + } +} diff --git a/samples/basic/echo-bot/dotnet/Program.cs b/samples/basic/echo-bot/dotnet/Program.cs new file mode 100644 index 0000000..a7daa31 --- /dev/null +++ b/samples/basic/echo-bot/dotnet/Program.cs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using EchoBot; +using Microsoft.Agents.BotBuilder.App; +using Microsoft.Agents.BotBuilder.State; +using Microsoft.Agents.Hosting.AspNetCore; +using Microsoft.Agents.Samples; +using Microsoft.Agents.Storage; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddControllers(); +builder.Services.AddHttpClient(); +builder.Logging.AddConsole(); + +// Add AspNet token validation +builder.Services.AddBotAspNetAuthentication(builder.Configuration); + +// Add ApplicationOptions +builder.Services.AddTransient(sp => +{ + return new AgentApplicationOptions() + { + StartTypingTimer = false, + TurnStateFactory = () => new TurnState(sp.GetService()) + }; +}); + +// Add the bot (which is transient) +builder.AddBot(); + + +var app = builder.Build(); + +if (app.Environment.IsDevelopment()) +{ + app.MapGet("/", () => "Microsoft Agents SDK Sample"); + app.UseDeveloperExceptionPage(); + app.MapControllers().AllowAnonymous(); +} +else +{ + app.MapControllers(); +} +app.Run(); + diff --git a/samples/basic/echo-bot/dotnet/README.md b/samples/basic/echo-bot/dotnet/README.md new file mode 100644 index 0000000..922c8ae --- /dev/null +++ b/samples/basic/echo-bot/dotnet/README.md @@ -0,0 +1,96 @@ +# EchoBot Sample + +This is a sample of a simple Agent that is hosted on an Asp.net core web service. This Agent is configured to accept a request and echo the text of the request back to the caller. + +This Agent Sample is intended to introduce you the basic operation of the Microsoft 365 Agents SDK messaging loop. It can also be used as a the base for a custom Agent that you choose to develop. + +## Prerequisites + +- [.Net](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) version 8.0 +- [dev tunnel](https://learn.microsoft.com/en-us/azure/developer/dev-tunnels/get-started?tabs=windows) +- [Bot Framework Emulator](https://github.com/Microsoft/BotFramework-Emulator/releases) for Testing Web Chat. + +## Running this sample + +**To run the sample connected to Azure Bot Service, the following additional tools are required:** + +- Access to an Azure Subscription with access to preform the following tasks: + - Create and configure Entra ID Application Identities + - Create and configure an [Azure Bot Service](https://aka.ms/AgentsSDK-CreateBot) for your bot + - Create and configure an [Azure App Service](https://learn.microsoft.com/azure/app-service/) to deploy your bot on to. + - A tunneling tool to allow for local development and debugging should you wish to do local development whilst connected to a external client such as Microsoft Teams. + +## Getting Started with EchoBot Sample + +Read more about [Running an Agent](../../../docs/HowTo/running-an-agent.md) + +### QuickStart using Bot Framework Emulator + +1. Open the Echobot Sample in Visual Studio 2022 +1. Run it in Debug Mode (F5) +1. A blank web page will open, note down the URL which should be similar too `https://localhost:65349/` +1. Open the [BotFramework Emulator](https://github.com/Microsoft/BotFramework-Emulator/releases) + 1. Click **Open Bot** + 1. In the bot URL field input the URL you noted down from the web page and add /api/messages to it. It should appear similar to `https://localhost:65349/api/messages` + 1. Click **Connect** + +If all is working correctly, the Bot Emulator should show you a Web Chat experience with the words **"Hello and Welcome!"** + +If you type a message and hit enter, or the send arrow, your messages should be returned to you with **Echo:your message** + +### QuickStart using WebChat + +1. [Create an Azure Bot](https://aka.ms/AgentsSDK-CreateBot) + - Record the Application ID, the Tenant ID, and the Client Secret for use below + +1. Configuring the token connection in the Agent settings + > The instructions for this sample are for a SingleTenant Azure Bot using ClientSecrets. The token connection configuration will vary if a different type of Azure Bot was configured. For more information see [DotNet MSAL Authentication provider](https://aka.ms/AgentsSDK-DotNetMSALAuth) + + 1. Open the `appsettings.json` file in the root of the sample project. + + 1. Find the section labeled `Connections`, it should appear similar to this: + + ```json + "TokenValidation": { + "Audiences": [ + "{{ClientId}}" // this is the Client ID used for the Azure Bot + ], + "TenantId": "{{TenantId}}" + }, + + "Connections": { + "BotServiceConnection": { + "Settings": { + "AuthType": "ClientSecret", // this is the AuthType for the connection, valid values can be found in Microsoft.Agents.Authentication.Msal.Model.AuthTypes. The default is ClientSecret. + "AuthorityEndpoint": "https://login.microsoftonline.com/{{TenantId}}", + "ClientId": "{{ClientId}}", // this is the Client ID used for the connection. + "ClientSecret": "00000000-0000-0000-0000-000000000000", // this is the Client Secret used for the connection. + "Scopes": [ + "https://api.botframework.com/.default" + ] + } + } + }, + ``` + + 1. Replace all **{{ClientId}}** with the AppId of the bot. + 1. Replace all **{{TenantId}}** with the Tenant Id where your application is registered. + 1. Set the **ClientSecret** to the Secret that was created for your identity. + + > Storing sensitive values in appsettings is not recommend. Follow [AspNet Configuration](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-9.0) for best practices. + +1. Run `dev tunnels`. Please follow [Create and host a dev tunnel](https://learn.microsoft.com/en-us/azure/developer/dev-tunnels/get-started?tabs=windows) and host the tunnel with anonymous user access command as shown below: + > NOTE: Go to your project directory and open the `./Properties/launchSettings.json` file. Check the port number and use that port number in the devtunnel command (instead of 3978). + + ```bash + devtunnel host -p 3978 --allow-anonymous + ``` + +1. On the Azure Bot, select **Settings**, then **Configuration**, and update the **Messaging endpoint** to `{tunnel-url}/api/messages` + +1. Start the Agent in Visual Studio + +1. Select **Test in WebChat** on the Azure Bot + +## Further reading +To learn more about building Bots and Agents, see our [Microsoft 365 Agents SDK](https://github.com/microsoft/agents) repo. \ No newline at end of file diff --git a/samples/basic/echo-bot/dotnet/StateExtensions.cs b/samples/basic/echo-bot/dotnet/StateExtensions.cs new file mode 100644 index 0000000..04762db --- /dev/null +++ b/samples/basic/echo-bot/dotnet/StateExtensions.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Agents.BotBuilder.State; + +namespace EchoBot +{ + public static class StateExtensions + { + public static int MessageCount(this ConversationState state) + { + return state.GetValue("countKey"); + } + + public static void MessageCount(this ConversationState state, int value) + { + state.SetValue("countKey", value); + } + + public static int IncrementMessageCount(this ConversationState state) + { + var count = state.GetValue("countKey"); + state.SetValue("countKey", ++count); + return count; + } + } +} diff --git a/samples/basic/echo-bot/dotnet/appsettings.json b/samples/basic/echo-bot/dotnet/appsettings.json new file mode 100644 index 0000000..8a57c87 --- /dev/null +++ b/samples/basic/echo-bot/dotnet/appsettings.json @@ -0,0 +1,37 @@ +{ + "TokenValidation": { + "Audiences": [ + "{{ClientId}}" // this is the Client ID used for the Azure Bot + ], + "TenantId": "{{TenantId}}" + }, + + "Connections": { + "BotServiceConnection": { + "Settings": { + "AuthType": "ClientSecret", // this is the AuthType for the connection, valid values can be found in Microsoft.Agents.Authentication.Msal.Model.AuthTypes. The default is ClientSecret. + "AuthorityEndpoint": "https://login.microsoftonline.com/{{TenantId}}", + "ClientId": "{{ClientId}}", // this is the Client ID used for the connection. + "ClientSecret": "00000000-0000-0000-0000-000000000000", // this is the Client Secret used for the connection. + "Scopes": [ + "https://api.botframework.com/.default" + ] + } + } + }, + "ConnectionsMap": [ + { + "ServiceUrl": "*", + "Connection": "BotServiceConnection" + } + ], + + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Microsoft.Copilot": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} \ No newline at end of file