From 4e7f863c6ece31c75a3b9754781b4c6e6f0573c4 Mon Sep 17 00:00:00 2001 From: Dong Lei Date: Tue, 7 May 2024 17:18:44 +0800 Subject: [PATCH 1/7] Init commit --- .../02.echo-bot/AdapterWithErrorHandler.cs | 8 + .../Controllers/DirectlineController.cs | 359 ++++++++++++++++++ .../csharp_dotnetcore/02.echo-bot/Startup.cs | 11 + 3 files changed, 378 insertions(+) create mode 100644 samples/csharp_dotnetcore/02.echo-bot/Controllers/DirectlineController.cs diff --git a/samples/csharp_dotnetcore/02.echo-bot/AdapterWithErrorHandler.cs b/samples/csharp_dotnetcore/02.echo-bot/AdapterWithErrorHandler.cs index 322a6d3790..5de938eb4d 100644 --- a/samples/csharp_dotnetcore/02.echo-bot/AdapterWithErrorHandler.cs +++ b/samples/csharp_dotnetcore/02.echo-bot/AdapterWithErrorHandler.cs @@ -1,6 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Microsoft.Bot.Builder; +using System.Threading.Tasks; +using System.Threading; using Microsoft.Bot.Builder.Integration.AspNet.Core; using Microsoft.Bot.Builder.TraceExtensions; using Microsoft.Bot.Connector.Authentication; @@ -29,5 +32,10 @@ public AdapterWithErrorHandler(BotFrameworkAuthentication auth, ILogger CustomProcessActivityAsync(AuthenticateRequestResult authenticateRequestResult, Microsoft.Bot.Schema.Activity activity, BotCallbackHandler callback, CancellationToken cancellationToken) + { + return await ProcessActivityAsync(authenticateRequestResult, activity, callback, cancellationToken); + } } } diff --git a/samples/csharp_dotnetcore/02.echo-bot/Controllers/DirectlineController.cs b/samples/csharp_dotnetcore/02.echo-bot/Controllers/DirectlineController.cs new file mode 100644 index 0000000000..5f8878da66 --- /dev/null +++ b/samples/csharp_dotnetcore/02.echo-bot/Controllers/DirectlineController.cs @@ -0,0 +1,359 @@ +using System; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Security.Claims; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Routing.Template; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Integration.AspNet.Core; +using Microsoft.Bot.Connector; +using Microsoft.Bot.Connector.Authentication; +using Microsoft.Bot.Schema; +using Microsoft.Bot.Streaming; +using Microsoft.Bot.Streaming.Transport.WebSockets; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; + +namespace Microsoft.BotBuilderSamples.Controllers +{ + [Route("v3/directline")] + [ApiController] + public class DirectlineController : ControllerBase + { + private readonly IBotFrameworkHttpAdapter _adapter; + private readonly IBot _bot; + private readonly ILogger _logger; + + public DirectlineController(IBotFrameworkHttpAdapter adapter, IBot bot, ILogger logger) + { + _adapter = adapter; + _bot = bot; + } + + [HttpPost("tokens/generate")] + [Produces("application/json")] + public async Task TokenGenerate() + { + var requestBody = await new StreamReader(Request.Body).ReadToEndAsync().ConfigureAwait(false); + TokenGenerationParameters tokenGenerationParameters = null; + try + { + tokenGenerationParameters = JsonConvert.DeserializeObject(requestBody); + } + catch (JsonException) + { + _logger.LogTrace("The request body is not a valid JSON object"); + return BadRequest("The request body is not a valid JSON object"); + } + + if (tokenGenerationParameters?.user?.Id == null || tokenGenerationParameters?.user?.Name == null) + { + _logger.LogTrace("The user parameter is required"); + return BadRequest("The user parameter is required"); + } + + // We just encode the user id and name in the token for this sample + // Please choose appropriate security mechanisms for production scenarios + // The token generated here should be validated when connecting to this endpoint below + var token = tokenGenerationParameters.user.ToString(); + var tokenBytes = Encoding.UTF8.GetBytes(token); + token = Convert.ToBase64String(tokenBytes); + + return new + { + token + }; + } + + [HttpGet("conversations/connect")] + public async Task ConnectToConversation() + { + if (!HttpContext.WebSockets.IsWebSocketRequest) + { + HttpContext.Response.StatusCode = 400; + await HttpContext.Response.BodyWriter.WriteAsync(Encoding.UTF8.GetBytes("Upgrade to WebSocket is required")).ConfigureAwait(false); + return; + } + + var token = Request.Query["token"]; + if (string.IsNullOrEmpty(token)) + { + HttpContext.Response.StatusCode = 400; + await HttpContext.Response.BodyWriter.WriteAsync(Encoding.UTF8.GetBytes("The query parameter 'token' is required")).ConfigureAwait(false); + return; + } + + // Again, this is just a sample. In production scenarios, you should validate the token + // based on how you generated it in the TokenGenerate method above + var tokenBytes = Convert.FromBase64String(token); + var tokenString = Encoding.UTF8.GetString(tokenBytes); + var tokenParts = tokenString.Split(';'); + if (tokenParts.Length != 2) + { + HttpContext.Response.StatusCode = 400; + await HttpContext.Response.BodyWriter.WriteAsync(Encoding.UTF8.GetBytes("The token is invalid")).ConfigureAwait(false); + return; + } + + var user = new ChannelAccount(tokenParts[0], tokenParts[1]); + + var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync(); + if (webSocket == null) + { + HttpContext.Response.StatusCode = 500; + _logger.LogError("Failed to accept WebSocket connection"); + return; + } + else + { + // This may not necessarily be an AdapterWithErrorHandler, but we need to access the custom ProcessActivityAsync method + if (_adapter is AdapterWithErrorHandler adapterWithErrorHandler) + { + var directlineClientRequestHander = new DirectlineRequestHandler(adapterWithErrorHandler, _bot, user); + var wbServer = new WebSocketServer(webSocket, directlineClientRequestHander); + directlineClientRequestHander.WebSocketServer = wbServer; + await wbServer.StartAsync(); + } + else + { + HttpContext.Response.StatusCode = 500; + return; + } + } + } + + public class TokenGenerationParameters + { + public class User + { + [JsonProperty("id")] + public string Id { get; set; } + [JsonProperty("name")] + public string Name { get; set; } + + public override string ToString() + { + return Id + ";" + Name; + } + } + + [JsonProperty("user")] + public User user { get; set; } + } + } + + public class DirectlineRequestHandler : RequestHandler + { + private readonly AdapterWithErrorHandler _adapter; + private readonly IBot _bot; + private ChannelAccount _user; + private readonly string channelId = "webchat"; + + public WebSocketServer WebSocketServer { get; set; } + + public DirectlineRequestHandler(AdapterWithErrorHandler adapter, IBot bot, ChannelAccount user) + { + _adapter = adapter; + _bot = bot; + _user = user; + } + + public override Task ProcessRequestAsync(ReceiveRequest request, ILogger logger, object context = null, CancellationToken cancellationToken = default) + { + if (request.Verb == "POST") + { + // It's either creating a new conversation or sending an activity + // First check if it's creating a new conversation + if (request.Path == "/v3/directline/conversations") + { + return ProcessCreateConversationRequestAsync(request, logger, context, cancellationToken); + } + + // Then check if it's sending an activity + var postActivityTemplate = TemplateParser.Parse("/v3/directline/conversations/{conversationId}/activities"); + var postActivityMatcher = new TemplateMatcher(postActivityTemplate, new RouteValueDictionary()); + var routeValues = new RouteValueDictionary(); + if (postActivityMatcher.TryMatch(request.Path, routeValues)) + { + var conversationId = routeValues["conversationId"] as string; + return ProcessPostActivityRequestAsync(request, logger, context, conversationId, _user, cancellationToken); + } + } + + throw new NotImplementedException($"Request {request.Verb} {request.Path}"); + } + + private async Task ProcessCreateConversationRequestAsync(ReceiveRequest request, ILogger logger, object context = null, CancellationToken cancellationToken = default) + { + var conversation = new + { + conversationId = Guid.NewGuid().ToString(), + }; + + await SendConversationUpdateToBotAsync(request, conversation.conversationId, logger, cancellationToken); + + var streamResponse = StreamingResponse.OK(new StringContent(JsonConvert.SerializeObject(conversation, SerializationSettings.DefaultSerializationSettings))); + + return streamResponse; + } + + private async Task ProcessPostActivityRequestAsync(ReceiveRequest request, ILogger logger, object context, string conversationId, ChannelAccount user, CancellationToken cancellationToken) + { + var activity = await request.ReadBodyAsJsonAsync().ConfigureAwait(false); + activity.Conversation = new ConversationAccount { Id = conversationId }; + activity.Id = Guid.NewGuid().ToString(); + activity.Timestamp = DateTime.UtcNow; + + // Echo back the activity to client first, so the client knows the activity has been received + await SendActivityToClient(request, activity, logger, cancellationToken); + + var invokeResponse = await SendActivityToBot(request, activity, logger, cancellationToken); + object result = (object)invokeResponse ?? new { activity.Id }; + var streamResponse = StreamingResponse.OK(new StringContent(JsonConvert.SerializeObject(result, SerializationSettings.DefaultSerializationSettings))); + return streamResponse; + } + + private async Task SendConversationUpdateToBotAsync(ReceiveRequest requestContext, string conversationId, ILogger logger, CancellationToken cancellationToken) + { + var update = new Activity + { + Type = ActivityTypes.ConversationUpdate, + MembersAdded = new[] { _user }, + Id = Guid.NewGuid().ToString(), + Timestamp = DateTime.UtcNow, + ChannelId = channelId, + Conversation = new ConversationAccount { Id = conversationId }, + Recipient = new ChannelAccount { Id = "Bot", Name = "Bot" }, + From = _user + }; + return await SendActivityToBot(requestContext, update, logger, cancellationToken); + } + + private async Task SendActivityToBot(ReceiveRequest requestContext, Activity activity, ILogger logger, CancellationToken cancellationToken) + { + var authenticationRequestResult = new AuthenticateRequestResult() + { + // It might be helpful to fill more meaningful data here for logging + Audience = "https://api.botframework.com", + ClaimsIdentity = new ClaimsIdentity(), + }; + authenticationRequestResult.ConnectorFactory = new StreamingConnectionFactory(WebSocketServer, logger); + return await _adapter.CustomProcessActivityAsync(authenticationRequestResult, activity, _bot.OnTurnAsync, cancellationToken); + } + + private async Task SendActivityToClient(ReceiveRequest requestContext, Activity activity, ILogger logger, CancellationToken cancellationToken) + { + var clientRequest = new StreamingRequest + { + Path = requestContext.Path, + Verb = requestContext.Verb + }; + var activitySet = new ActivitySet + { + Activities = new[] { activity }, + Watermark = "watermark" + }; + + // WebChat expects the activities to be wrapped in an object with an activities property + clientRequest.SetBody(activitySet); + return await WebSocketServer.SendAsync(clientRequest, cancellationToken).ConfigureAwait(false); + } + + private class ActivitySet + { + [JsonProperty("activities")] + public Activity[] Activities { get; set; } + + [JsonProperty("watermark")] + public string Watermark { get; set; } + } + + private class StreamingConnectionFactory : ConnectorFactory + { + private readonly WebSocketServer _socketServer; + private readonly ILogger _logger; + + public StreamingConnectionFactory(WebSocketServer socketServer, ILogger logger) + { + _socketServer = socketServer; + _logger = logger; + } + + public override Task CreateAsync(string serviceUrl, string audience, CancellationToken cancellationToken) + { + var httpClient = new WebSocketHttpClient(_socketServer, _logger); + return Task.FromResult(new ConnectorClient(MicrosoftAppCredentials.Empty, httpClient, false)); + } + } + + private class WebSocketHttpClient : HttpClient + { + private readonly WebSocketServer _socketServer; + private readonly ILogger _logger; + + public WebSocketHttpClient(WebSocketServer socketServer, ILogger logger) + { + _socketServer = socketServer; + _logger = logger; + } + + public override async Task SendAsync(HttpRequestMessage httpRequestMessage, CancellationToken cancellationToken) + { + var streamingRequest = await CreateSteamingRequestAsync(httpRequestMessage).ConfigureAwait(false); + var receiveResponse = await SendStreamingRequestAsync(streamingRequest, cancellationToken).ConfigureAwait(false); + var httpResponseMessage = await CreateHttpResponseAsync(receiveResponse).ConfigureAwait(false); + return httpResponseMessage; + } + + private async Task CreateSteamingRequestAsync(HttpRequestMessage httpRequestMessage) + { + var streamingRequest = new StreamingRequest + { + Path = httpRequestMessage.RequestUri.OriginalString.Substring(httpRequestMessage.RequestUri.OriginalString.IndexOf("/v3", StringComparison.Ordinal)), + Verb = httpRequestMessage.Method.ToString(), + }; + + if (httpRequestMessage.Content != null) + { + var contentString = await httpRequestMessage.Content.ReadAsStringAsync(); + var activity = JsonConvert.DeserializeObject(contentString); + activity.Timestamp = DateTime.UtcNow; + activity.Id = Guid.NewGuid().ToString(); + + var activitySet = new + { + activities = new Activity[] { activity }, + watermark = "watermark" + }; + + streamingRequest.SetBody(activitySet); + } + return streamingRequest; + } + + private async Task SendStreamingRequestAsync(StreamingRequest request, CancellationToken cancellationToken) + { + ReceiveResponse receiveResponse = new ReceiveResponse() { StatusCode = (int)HttpStatusCode.OK }; + if (_socketServer.IsConnected) + { + var response = await _socketServer.SendAsync(request, cancellationToken).ConfigureAwait(false); + receiveResponse.StatusCode = response.StatusCode; + } + return receiveResponse; + } + + private async Task CreateHttpResponseAsync(ReceiveResponse receiveResponse) + { + var httpResponseMessage = new HttpResponseMessage((HttpStatusCode)receiveResponse.StatusCode); + httpResponseMessage.Content = new StringContent(await receiveResponse.ReadBodyAsStringAsync().ConfigureAwait(false)); + return httpResponseMessage; + } + } + + } +} diff --git a/samples/csharp_dotnetcore/02.echo-bot/Startup.cs b/samples/csharp_dotnetcore/02.echo-bot/Startup.cs index 663e9cbf04..e23b75000c 100644 --- a/samples/csharp_dotnetcore/02.echo-bot/Startup.cs +++ b/samples/csharp_dotnetcore/02.echo-bot/Startup.cs @@ -39,6 +39,16 @@ public void ConfigureServices(IServiceCollection services) // Create the bot as a transient. In this case the ASP Controller is expecting an IBot. services.AddTransient(); + + services.AddCors(options => + { + options.AddDefaultPolicy(builder => + { + builder.AllowAnyOrigin() + .AllowAnyHeader() + .AllowAnyMethod(); + }); + }); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -53,6 +63,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) .UseStaticFiles() .UseWebSockets() .UseRouting() + .UseCors() .UseAuthorization() .UseEndpoints(endpoints => { From 973685f960ed881f1d34a78745a0f99a9cae3001 Mon Sep 17 00:00:00 2001 From: Dong Lei Date: Tue, 7 May 2024 17:36:12 +0800 Subject: [PATCH 2/7] Add copy right --- .../02.echo-bot/Controllers/DirectlineController.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/samples/csharp_dotnetcore/02.echo-bot/Controllers/DirectlineController.cs b/samples/csharp_dotnetcore/02.echo-bot/Controllers/DirectlineController.cs index 5f8878da66..0c4a08a026 100644 --- a/samples/csharp_dotnetcore/02.echo-bot/Controllers/DirectlineController.cs +++ b/samples/csharp_dotnetcore/02.echo-bot/Controllers/DirectlineController.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; using System.IO; using System.Net; using System.Net.Http; From e2ee89e3b8ef90bc2493a6aa1153285045cc410d Mon Sep 17 00:00:00 2001 From: Dong Lei Date: Sat, 25 May 2024 10:59:09 +0800 Subject: [PATCH 3/7] Fix logger level, make user id and name opitional --- .../02.echo-bot/Controllers/DirectlineController.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/samples/csharp_dotnetcore/02.echo-bot/Controllers/DirectlineController.cs b/samples/csharp_dotnetcore/02.echo-bot/Controllers/DirectlineController.cs index 0c4a08a026..aad5b25150 100644 --- a/samples/csharp_dotnetcore/02.echo-bot/Controllers/DirectlineController.cs +++ b/samples/csharp_dotnetcore/02.echo-bot/Controllers/DirectlineController.cs @@ -36,6 +36,7 @@ public DirectlineController(IBotFrameworkHttpAdapter adapter, IBot bot, ILogger< { _adapter = adapter; _bot = bot; + _logger = logger; } [HttpPost("tokens/generate")] @@ -50,14 +51,19 @@ public async Task TokenGenerate() } catch (JsonException) { - _logger.LogTrace("The request body is not a valid JSON object"); + _logger.LogError("The request body is not a valid JSON object"); return BadRequest("The request body is not a valid JSON object"); } if (tokenGenerationParameters?.user?.Id == null || tokenGenerationParameters?.user?.Name == null) { - _logger.LogTrace("The user parameter is required"); - return BadRequest("The user parameter is required"); + _logger.LogWarning("No user specified, using default user id and name"); + tokenGenerationParameters = new TokenGenerationParameters(); + tokenGenerationParameters.user = new TokenGenerationParameters.User + { + Id = "default-user-id", + Name = "default-user-name" + }; } // We just encode the user id and name in the token for this sample From 77d05c8ca2769fd082aadebd30eb19b751bfc0ad Mon Sep 17 00:00:00 2001 From: Dong Lei Date: Sat, 25 May 2024 11:05:01 +0800 Subject: [PATCH 4/7] Generating conversation id when connecting --- .../Controllers/DirectlineController.cs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/samples/csharp_dotnetcore/02.echo-bot/Controllers/DirectlineController.cs b/samples/csharp_dotnetcore/02.echo-bot/Controllers/DirectlineController.cs index aad5b25150..da596f32ff 100644 --- a/samples/csharp_dotnetcore/02.echo-bot/Controllers/DirectlineController.cs +++ b/samples/csharp_dotnetcore/02.echo-bot/Controllers/DirectlineController.cs @@ -75,6 +75,7 @@ public async Task TokenGenerate() return new { + conversationId = Guid.NewGuid().ToString(), token }; } @@ -97,6 +98,13 @@ public async Task ConnectToConversation() return; } + var conversationId = Request.Query["conversationId"]; + if (string.IsNullOrEmpty(conversationId)) + { + _logger.LogInformation("No conversationId specified when connecting, generating new conversation id"); + conversationId = Guid.NewGuid().ToString(); + } + // Again, this is just a sample. In production scenarios, you should validate the token // based on how you generated it in the TokenGenerate method above var tokenBytes = Convert.FromBase64String(token); @@ -123,7 +131,7 @@ public async Task ConnectToConversation() // This may not necessarily be an AdapterWithErrorHandler, but we need to access the custom ProcessActivityAsync method if (_adapter is AdapterWithErrorHandler adapterWithErrorHandler) { - var directlineClientRequestHander = new DirectlineRequestHandler(adapterWithErrorHandler, _bot, user); + var directlineClientRequestHander = new DirectlineRequestHandler(adapterWithErrorHandler, _bot, user, conversationId); var wbServer = new WebSocketServer(webSocket, directlineClientRequestHander); directlineClientRequestHander.WebSocketServer = wbServer; await wbServer.StartAsync(); @@ -162,14 +170,16 @@ public class DirectlineRequestHandler : RequestHandler private readonly IBot _bot; private ChannelAccount _user; private readonly string channelId = "webchat"; + private readonly string _conversationId; public WebSocketServer WebSocketServer { get; set; } - public DirectlineRequestHandler(AdapterWithErrorHandler adapter, IBot bot, ChannelAccount user) + public DirectlineRequestHandler(AdapterWithErrorHandler adapter, IBot bot, ChannelAccount user, string conversationId) { _adapter = adapter; _bot = bot; _user = user; + _conversationId = conversationId; } public override Task ProcessRequestAsync(ReceiveRequest request, ILogger logger, object context = null, CancellationToken cancellationToken = default) @@ -201,7 +211,7 @@ private async Task ProcessCreateConversationRequestAsync(Rece { var conversation = new { - conversationId = Guid.NewGuid().ToString(), + conversationId = _conversationId, }; await SendConversationUpdateToBotAsync(request, conversation.conversationId, logger, cancellationToken); From 2bf1785755c1c73804dfd4d3508459004743bce7 Mon Sep 17 00:00:00 2001 From: Dong Lei Date: Sat, 25 May 2024 14:57:54 +0800 Subject: [PATCH 5/7] Meet the expecation of path for c# streaming client --- .../Controllers/DirectlineController.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/samples/csharp_dotnetcore/02.echo-bot/Controllers/DirectlineController.cs b/samples/csharp_dotnetcore/02.echo-bot/Controllers/DirectlineController.cs index da596f32ff..d678f741c4 100644 --- a/samples/csharp_dotnetcore/02.echo-bot/Controllers/DirectlineController.cs +++ b/samples/csharp_dotnetcore/02.echo-bot/Controllers/DirectlineController.cs @@ -220,13 +220,14 @@ private async Task ProcessCreateConversationRequestAsync(Rece return streamResponse; } - + private async Task ProcessPostActivityRequestAsync(ReceiveRequest request, ILogger logger, object context, string conversationId, ChannelAccount user, CancellationToken cancellationToken) { var activity = await request.ReadBodyAsJsonAsync().ConfigureAwait(false); activity.Conversation = new ConversationAccount { Id = conversationId }; activity.Id = Guid.NewGuid().ToString(); activity.Timestamp = DateTime.UtcNow; + activity.Recipient = new ChannelAccount { Id = "bot", Name = "bot" }; // Echo back the activity to client first, so the client knows the activity has been received await SendActivityToClient(request, activity, logger, cancellationToken); @@ -247,7 +248,7 @@ private async Task SendConversationUpdateToBotAsync(ReceiveReque Timestamp = DateTime.UtcNow, ChannelId = channelId, Conversation = new ConversationAccount { Id = conversationId }, - Recipient = new ChannelAccount { Id = "Bot", Name = "Bot" }, + Recipient = new ChannelAccount { Id = "bot", Name = "bot" }, From = _user }; return await SendActivityToBot(requestContext, update, logger, cancellationToken); @@ -269,7 +270,8 @@ private async Task SendActivityToClient(ReceiveRequest requestC { var clientRequest = new StreamingRequest { - Path = requestContext.Path, + // Stream client is expecting the path to be relative to /v3/directline + Path = requestContext.Path.Replace("/v3/directline", ""), Verb = requestContext.Verb }; var activitySet = new ActivitySet @@ -333,16 +335,20 @@ private async Task CreateSteamingRequestAsync(HttpRequestMessa { var streamingRequest = new StreamingRequest { - Path = httpRequestMessage.RequestUri.OriginalString.Substring(httpRequestMessage.RequestUri.OriginalString.IndexOf("/v3", StringComparison.Ordinal)), + Path = httpRequestMessage.RequestUri.OriginalString.Substring(httpRequestMessage.RequestUri.OriginalString.IndexOf("/conversation", StringComparison.Ordinal)), Verb = httpRequestMessage.Method.ToString(), }; + // Stream client doesn't expect the path to contains any thing after activities + streamingRequest.Path = streamingRequest.Path.Substring(0, streamingRequest.Path.IndexOf("activities", StringComparison.Ordinal) + "activities".Length); + if (httpRequestMessage.Content != null) { var contentString = await httpRequestMessage.Content.ReadAsStringAsync(); var activity = JsonConvert.DeserializeObject(contentString); activity.Timestamp = DateTime.UtcNow; activity.Id = Guid.NewGuid().ToString(); + activity.From = new ChannelAccount { Id = "bot", Name = "bot" }; var activitySet = new { From c0b82c2c6234583df1011b63842a289237b05b41 Mon Sep 17 00:00:00 2001 From: Dong Lei Date: Sat, 25 May 2024 15:09:21 +0800 Subject: [PATCH 6/7] Add .bot endpoint to minimize required changes for client --- .../02.echo-bot/Controllers/DirectlineController.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/samples/csharp_dotnetcore/02.echo-bot/Controllers/DirectlineController.cs b/samples/csharp_dotnetcore/02.echo-bot/Controllers/DirectlineController.cs index d678f741c4..5a2f336069 100644 --- a/samples/csharp_dotnetcore/02.echo-bot/Controllers/DirectlineController.cs +++ b/samples/csharp_dotnetcore/02.echo-bot/Controllers/DirectlineController.cs @@ -24,7 +24,7 @@ namespace Microsoft.BotBuilderSamples.Controllers { - [Route("v3/directline")] + [Route(".bot")] [ApiController] public class DirectlineController : ControllerBase { @@ -39,7 +39,13 @@ public DirectlineController(IBotFrameworkHttpAdapter adapter, IBot bot, ILogger< _logger = logger; } - [HttpPost("tokens/generate")] + [HttpGet] + public IActionResult Get() + { + return Ok("Welcome to the Local Directline Controller example, which is free and open-source. This example allows clients (webchat or dotnet) to directly connect to bot without any external service and let you take control the network as well as authentication"); + } + + [HttpPost("v3/directline/tokens/generate")] [Produces("application/json")] public async Task TokenGenerate() { @@ -80,7 +86,7 @@ public async Task TokenGenerate() }; } - [HttpGet("conversations/connect")] + [HttpGet("v3/directline/conversations/connect")] public async Task ConnectToConversation() { if (!HttpContext.WebSockets.IsWebSocketRequest) From 3ee914640a89087739daec348beec54a5227fb7e Mon Sep 17 00:00:00 2001 From: Alin Date: Fri, 14 Jun 2024 09:45:46 +0300 Subject: [PATCH 7/7] Fix AuthenticateRequestResult, appId must be added inside claimsIdentity --- .../Controllers/DirectlineController.cs | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/samples/csharp_dotnetcore/02.echo-bot/Controllers/DirectlineController.cs b/samples/csharp_dotnetcore/02.echo-bot/Controllers/DirectlineController.cs index 5a2f336069..5cf5313e96 100644 --- a/samples/csharp_dotnetcore/02.echo-bot/Controllers/DirectlineController.cs +++ b/samples/csharp_dotnetcore/02.echo-bot/Controllers/DirectlineController.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Collections.Generic; using System.IO; using System.Net; using System.Net.Http; @@ -19,6 +20,7 @@ using Microsoft.Bot.Schema; using Microsoft.Bot.Streaming; using Microsoft.Bot.Streaming.Transport.WebSockets; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Newtonsoft.Json; @@ -31,12 +33,14 @@ public class DirectlineController : ControllerBase private readonly IBotFrameworkHttpAdapter _adapter; private readonly IBot _bot; private readonly ILogger _logger; + private readonly IConfiguration _configuration; - public DirectlineController(IBotFrameworkHttpAdapter adapter, IBot bot, ILogger logger) + public DirectlineController(IBotFrameworkHttpAdapter adapter, IBot bot, ILogger logger, IConfiguration configuration) { _adapter = adapter; _bot = bot; _logger = logger; + _configuration = configuration; } [HttpGet] @@ -137,7 +141,7 @@ public async Task ConnectToConversation() // This may not necessarily be an AdapterWithErrorHandler, but we need to access the custom ProcessActivityAsync method if (_adapter is AdapterWithErrorHandler adapterWithErrorHandler) { - var directlineClientRequestHander = new DirectlineRequestHandler(adapterWithErrorHandler, _bot, user, conversationId); + var directlineClientRequestHander = new DirectlineRequestHandler(adapterWithErrorHandler, _bot, user, conversationId, _configuration); var wbServer = new WebSocketServer(webSocket, directlineClientRequestHander); directlineClientRequestHander.WebSocketServer = wbServer; await wbServer.StartAsync(); @@ -177,15 +181,17 @@ public class DirectlineRequestHandler : RequestHandler private ChannelAccount _user; private readonly string channelId = "webchat"; private readonly string _conversationId; + private readonly string _appId; public WebSocketServer WebSocketServer { get; set; } - public DirectlineRequestHandler(AdapterWithErrorHandler adapter, IBot bot, ChannelAccount user, string conversationId) + public DirectlineRequestHandler(AdapterWithErrorHandler adapter, IBot bot, ChannelAccount user, string conversationId, IConfiguration configuration) { _adapter = adapter; _bot = bot; _user = user; _conversationId = conversationId; + _appId = configuration["MicrosoftAppId"] ?? string.Empty; } public override Task ProcessRequestAsync(ReceiveRequest request, ILogger logger, object context = null, CancellationToken cancellationToken = default) @@ -226,7 +232,7 @@ private async Task ProcessCreateConversationRequestAsync(Rece return streamResponse; } - + private async Task ProcessPostActivityRequestAsync(ReceiveRequest request, ILogger logger, object context, string conversationId, ChannelAccount user, CancellationToken cancellationToken) { var activity = await request.ReadBodyAsJsonAsync().ConfigureAwait(false); @@ -262,11 +268,17 @@ private async Task SendConversationUpdateToBotAsync(ReceiveReque private async Task SendActivityToBot(ReceiveRequest requestContext, Activity activity, ILogger logger, CancellationToken cancellationToken) { + var claimsIdentity = new ClaimsIdentity(new List + { + new Claim("aud", _appId), + new Claim("appid", _appId) + }); + var authenticationRequestResult = new AuthenticateRequestResult() { // It might be helpful to fill more meaningful data here for logging Audience = "https://api.botframework.com", - ClaimsIdentity = new ClaimsIdentity(), + ClaimsIdentity = claimsIdentity }; authenticationRequestResult.ConnectorFactory = new StreamingConnectionFactory(WebSocketServer, logger); return await _adapter.CustomProcessActivityAsync(authenticationRequestResult, activity, _bot.OnTurnAsync, cancellationToken);