microsoft/teams.net

Public

mirrored fromhttps://github.com/microsoft/teams.netAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
next/core

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

core/src/Microsoft.Teams.Bot.Compat/CompatBotAdapter.cs

176lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4using System.Text.Json;
5using Microsoft.AspNetCore.Http;
6using Microsoft.Bot.Builder;
7using Microsoft.Bot.Schema;
8using Microsoft.Extensions.Logging;
9using Microsoft.Teams.Bot.Core;
10using Microsoft.Teams.Bot.Core.Schema;
11using Newtonsoft.Json;
12
13
14namespace Microsoft.Teams.Bot.Compat;
15
16/// <summary>
17/// Provides a Bot Framework adapter that enables compatibility between the Bot Framework SDK and a custom bot
18/// application implementation.
19/// </summary>
20/// <remarks>Use this adapter to bridge Bot Framework turn contexts and activities with a custom bot application.
21/// This class is intended for scenarios where integration with non-standard bot runtimes or legacy systems is
22/// required.</remarks>
23/// <param name="botApplication">The Teams bot application instance.</param>
24/// <param name="httpContextAccessor">The HTTP context accessor.</param>
25/// <param name="logger">The logger instance.</param>
26public class CompatBotAdapter(
27 BotApplication botApplication,
28 IHttpContextAccessor? httpContextAccessor = null,
29 ILogger? logger = null) : BotAdapter
30{
31 private readonly JsonSerializerOptions _writeIndentedJsonOptions = new() { WriteIndented = true };
32 private readonly BotApplication botApplication = botApplication;
33 private readonly IHttpContextAccessor? httpContextAccessor = httpContextAccessor;
34 private readonly ILogger? logger = logger;
35
36 /// <summary>
37 /// Deletes an activity from the conversation.
38 /// </summary>
39 /// <param name="turnContext">The turn context containing the activity information. Cannot be null.</param>
40 /// <param name="reference">The conversation reference identifying the activity to delete.</param>
41 /// <param name="cancellationToken">A cancellation token that can be used to cancel the asynchronous operation.</param>
42 /// <returns>A task that represents the asynchronous delete operation.</returns>
43 public override async Task DeleteActivityAsync(ITurnContext turnContext, ConversationReference reference, CancellationToken cancellationToken)
44 {
45 ArgumentNullException.ThrowIfNull(turnContext);
46 ArgumentNullException.ThrowIfNull(reference);
47
48 // Extract values from conversation reference
49 string conversationId = reference.Conversation?.Id
50 ?? throw new ArgumentException("ConversationReference must contain a valid Conversation.Id", nameof(reference));
51
52 string activityId = reference.ActivityId
53 ?? throw new ArgumentException("ConversationReference must contain a valid ActivityId", nameof(reference));
54
55 string serviceUrlString = reference.ServiceUrl
56 ?? turnContext.Activity.ServiceUrl
57 ?? throw new ArgumentException("ServiceUrl must be provided", nameof(reference));
58
59 Uri serviceUrl = new(serviceUrlString);
60
61 // Extract agentic identity from turn context if available
62 AgenticIdentity? agenticIdentity = AgenticIdentity.FromAccount(turnContext.Activity?.FromCompatActivity().From);
63
64 await botApplication.ConversationClient.DeleteActivityAsync(
65 conversationId,
66 activityId,
67 serviceUrl,
68 agenticIdentity,
69 customHeaders: null,
70 cancellationToken).ConfigureAwait(false);
71 }
72
73 /// <summary>
74 /// Sends a set of activities to the conversation.
75 /// </summary>
76 /// <param name="turnContext">The turn context for the conversation. Cannot be null.</param>
77 /// <param name="activities">An array of activities to send. Cannot be null.</param>
78 /// <param name="cancellationToken">A cancellation token that can be used to cancel the asynchronous operation.</param>
79 /// <returns>
80 /// A task that represents the asynchronous operation. The task result contains an array of <see cref="ResourceResponse"/>
81 /// objects with the IDs of the sent activities.
82 /// </returns>
83 public override async Task<ResourceResponse[]> SendActivitiesAsync(ITurnContext turnContext, Activity[] activities, CancellationToken cancellationToken)
84 {
85 ArgumentNullException.ThrowIfNull(activities);
86 ArgumentNullException.ThrowIfNull(turnContext);
87
88 ResourceResponse[] responses = new Microsoft.Bot.Schema.ResourceResponse[activities.Length];
89
90 for (int i = 0; i < activities.Length; i++)
91 {
92 Activity activity = activities[i];
93
94 if (activity.Type == ActivityTypes.Trace)
95 {
96 return [new ResourceResponse() { Id = null }];
97 }
98
99 if (activity.Type == "invokeResponse")
100 {
101 WriteInvokeResponseToHttpResponse(activity.Value as InvokeResponse);
102 return [new ResourceResponse() { Id = null }];
103 }
104
105 CoreActivity coreActivity = activity.FromCompatActivity();
106
107 // Ensure ServiceUrl is set from turn context if not already present
108 if (coreActivity.ServiceUrl == null && !string.IsNullOrWhiteSpace(turnContext.Activity.ServiceUrl))
109 {
110 coreActivity.ServiceUrl = new Uri(turnContext.Activity.ServiceUrl);
111 }
112
113 coreActivity.Conversation ??= new Microsoft.Teams.Bot.Core.Schema.Conversation(
114 turnContext.Activity.Conversation?.Id
115 ?? throw new InvalidOperationException("Conversation ID is required to send activities."));
116 SendActivityResponse? resp = await botApplication.SendActivityAsync(coreActivity, cancellationToken: cancellationToken).ConfigureAwait(false);
117
118 logger?.LogDebug("Resp from SendActivitiesAsync: {RespId}", resp?.Id);
119
120 responses[i] = new Microsoft.Bot.Schema.ResourceResponse() { Id = resp?.Id };
121 }
122 return responses;
123 }
124
125 /// <summary>
126 /// Updates an existing activity in the conversation.
127 /// </summary>
128 /// <param name="turnContext">The turn context for the conversation.</param>
129 /// <param name="activity">The activity with updated content. Cannot be null.</param>
130 /// <param name="cancellationToken">A cancellation token that can be used to cancel the asynchronous operation.</param>
131 /// <returns>
132 /// A task that represents the asynchronous operation. The task result contains a <see cref="ResourceResponse"/>
133 /// with the ID of the updated activity.
134 /// </returns>
135 public override async Task<ResourceResponse> UpdateActivityAsync(ITurnContext turnContext, Activity activity, CancellationToken cancellationToken)
136 {
137 ArgumentNullException.ThrowIfNull(activity);
138 ArgumentNullException.ThrowIfNull(turnContext);
139
140 CoreActivity coreActivity = activity.FromCompatActivity();
141
142 // Ensure ServiceUrl is set from turn context if not already present
143 if (coreActivity.ServiceUrl == null && !string.IsNullOrWhiteSpace(turnContext.Activity.ServiceUrl))
144 {
145 coreActivity.ServiceUrl = new Uri(turnContext.Activity.ServiceUrl);
146 }
147
148 UpdateActivityResponse res = await botApplication.ConversationClient.UpdateActivityAsync(
149 activity.Conversation.Id,
150 activity.Id,
151 coreActivity,
152 cancellationToken: cancellationToken).ConfigureAwait(false);
153 return new ResourceResponse() { Id = res.Id };
154 }
155
156 private void WriteInvokeResponseToHttpResponse(InvokeResponse? invokeResponse)
157 {
158 ArgumentNullException.ThrowIfNull(invokeResponse);
159 HttpResponse? response = httpContextAccessor?.HttpContext?.Response;
160 if (response is not null && !response.HasStarted)
161 {
162 response.StatusCode = invokeResponse.Status;
163 using StreamWriter httpResponseStreamWriter = new(response.BodyWriter.AsStream());
164 using JsonTextWriter httpResponseJsonWriter = new(httpResponseStreamWriter);
165 logger?.LogTrace("Sending Invoke Response: \n {InvokeResponse} with status: {Status} \n", System.Text.Json.JsonSerializer.Serialize(invokeResponse.Body, _writeIndentedJsonOptions), invokeResponse.Status);
166 if (invokeResponse.Body is not null)
167 {
168 Microsoft.Bot.Builder.Integration.AspNet.Core.HttpHelper.BotMessageSerializer.Serialize(httpResponseJsonWriter, invokeResponse.Body);
169 }
170 }
171 else
172 {
173 logger?.LogWarning("HTTP response is null or has started. Cannot write invoke response. ResponseStarted: {ResponseStarted}", response?.HasStarted);
174 }
175 }
176}
177