microsoft/teams.net

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
devtools-port-no-auth

Branches

Tags

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

Clone

HTTPS

Download ZIP

devtools-plan/02-core-architecture.md

299lines · modecode

1# Core Branch Architecture (`upstream/next/core`)
2
3How the target `upstream/next/core` branch works — the architecture DevTools must integrate with.
4
5## Overview
6
7The core branch is a complete rewrite of the .NET SDK. Key differences from `main`:
8
9- **No plugin system** — `IPlugin`, `IAspNetCorePlugin`, `ISenderPlugin`, `[Plugin]`, `[Dependency]` are all removed
10- **3 layers**: `Microsoft.Teams.Bot.Core` → `Microsoft.Teams.Bot.Apps` → `Microsoft.Teams.Bot.Compat`
11- **Middleware-based pipeline** instead of plugin event callbacks
12- **Virtual methods on `ConversationClient`** for extensibility
13- **Minimal API hosting** (`MapPost`) instead of MVC controllers
14
15---
16
17## Layer 1: `Microsoft.Teams.Bot.Core`
18
19The protocol-level foundation. No Teams-specific concepts.
20
21### `BotApplication.cs`
22
23The central class that processes incoming activities.
24
25```csharp
26public class BotApplication
27{
28 private readonly ConversationClient? _conversationClient;
29 private readonly UserTokenClient? _userTokenClient;
30 internal TurnMiddleware MiddleWare { get; }
31
32 public BotApplication(ConversationClient conversationClient, UserTokenClient userTokenClient,
33 ILogger<BotApplication> logger, BotApplicationOptions? options = null)
34 {
35 MiddleWare = new TurnMiddleware();
36 _conversationClient = conversationClient;
37 _userTokenClient = userTokenClient;
38 // ...
39 }
40
41 public ConversationClient ConversationClient => _conversationClient ?? throw ...;
42 public UserTokenClient UserTokenClient => _userTokenClient ?? throw ...;
43
44 // Terminal handler — invoked after all middleware runs
45 public virtual Func<CoreActivity, CancellationToken, Task>? OnActivity { get; set; }
46
47 // Entry point for incoming HTTP requests
48 public virtual async Task ProcessAsync(HttpContext httpContext, CancellationToken cancellationToken = default)
49 {
50 CoreActivity activity = await CoreActivity.FromJsonStreamAsync(httpContext.Request.Body, cancellationToken)
51 ?? throw new InvalidOperationException("Invalid Activity");
52
53 try
54 {
55 CancellationToken token = Debugger.IsAttached ? CancellationToken.None : cancellationToken;
56 await MiddleWare.RunPipelineAsync(this, activity, this.OnActivity, 0, token);
57 }
58 catch (Exception ex)
59 {
60 throw new BotHandlerException("Error processing activity", ex, activity);
61 }
62 }
63
64 // Sends activity via ConversationClient
65 public async Task<SendActivityResponse?> SendActivityAsync(CoreActivity activity, CancellationToken cancellationToken = default)
66 {
67 return await _conversationClient.SendActivityAsync(activity, cancellationToken: cancellationToken);
68 }
69
70 // Register middleware
71 public ITurnMiddleware UseMiddleware(ITurnMiddleware middleware)
72 {
73 MiddleWare.Use(middleware);
74 return MiddleWare;
75 }
76}
77```
78
79**Key integration points for DevTools:**
80- `ProcessAsync` — where incoming activities enter the system
81- `SendActivityAsync` — delegates to `ConversationClient` (interceptable via virtual override)
82- `UseMiddleware` — how to register middleware that sees every incoming activity
83- `OnActivity` — terminal callback, runs after all middleware
84
85### `ITurnMiddleware.cs`
86
87```csharp
88public delegate Task NextTurn(CancellationToken cancellationToken);
89
90public interface ITurnMiddleware
91{
92 Task OnTurnAsync(BotApplication botApplication, CoreActivity activity,
93 NextTurn nextTurn, CancellationToken cancellationToken = default);
94}
95```
96
97Middleware can:
98- Run code **before** `nextTurn()` (pre-processing)
99- Run code **after** `nextTurn()` (post-processing)
100- Wrap `nextTurn()` in try/catch (error handling)
101- Short-circuit by not calling `nextTurn()` at all
102
103### `TurnMiddleware.cs` (internal)
104
105Chain-of-responsibility pipeline executor:
106
107```csharp
108internal sealed class TurnMiddleware : ITurnMiddleware, IEnumerable<ITurnMiddleware>
109{
110 private readonly IList<ITurnMiddleware> _middlewares = [];
111
112 internal TurnMiddleware Use(ITurnMiddleware middleware) { _middlewares.Add(middleware); return this; }
113
114 public Task RunPipelineAsync(BotApplication botApplication, CoreActivity activity,
115 Func<CoreActivity, CancellationToken, Task>? callback, int nextMiddlewareIndex, CancellationToken ct)
116 {
117 if (nextMiddlewareIndex == _middlewares.Count)
118 return callback?.Invoke(activity, ct) ?? Task.CompletedTask;
119
120 ITurnMiddleware nextMiddleware = _middlewares[nextMiddlewareIndex];
121 return nextMiddleware.OnTurnAsync(
122 botApplication, activity,
123 (ct) => RunPipelineAsync(botApplication, activity, callback, nextMiddlewareIndex + 1, ct),
124 ct);
125 }
126}
127```
128
129### `ConversationClient.cs`
130
131All methods are `virtual` — this is how DevTools can intercept outgoing activities:
132
133```csharp
134public class ConversationClient(HttpClient httpClient, ILogger<ConversationClient> logger = default!)
135{
136 internal const string ConversationHttpClientName = "BotConversationClient";
137
138 public CustomHeaders DefaultCustomHeaders { get; } = [];
139
140 public virtual async Task<SendActivityResponse> SendActivityAsync(CoreActivity activity,
141 CustomHeaders? customHeaders = null, CancellationToken cancellationToken = default)
142 {
143 // Builds URL from activity.ServiceUrl + conversation ID
144 // Serializes activity to JSON, sends via HTTP POST
145 // Returns SendActivityResponse with activity ID
146 }
147
148 public virtual async Task<SendActivityResponse> UpdateActivityAsync(...) { ... }
149 public virtual async Task DeleteActivityAsync(...) { ... }
150 public virtual async Task<IList<ConversationAccount>> GetConversationMembersAsync(...) { ... }
151 public virtual async Task<T> GetConversationMemberAsync<T>(...) { ... }
152 // ... all methods are virtual
153}
154```
155
156### `CoreActivity.cs` (Schema)
157
158The activity DTO — replaces `Activity`/`IActivity` from main:
159
160```csharp
161public class CoreActivity
162{
163 [JsonPropertyName("type")] public string Type { get; set; }
164 [JsonPropertyName("channelId")] public string? ChannelId { get; set; }
165 [JsonPropertyName("id")] public string? Id { get; set; }
166 [JsonPropertyName("serviceUrl")] public Uri? ServiceUrl { get; set; }
167 [JsonPropertyName("channelData")] public ChannelData? ChannelData { get; set; }
168 [JsonPropertyName("from")] public ConversationAccount? From { get; set; }
169 [JsonPropertyName("recipient")] public ConversationAccount? Recipient { get; set; }
170 [JsonPropertyName("conversation")] public Conversation? Conversation { get; set; }
171 [JsonPropertyName("entities")] public JsonArray? Entities { get; set; }
172 [JsonPropertyName("attachments")] public JsonArray? Attachments { get; set; }
173 [JsonPropertyName("value")] public JsonNode? Value { get; set; }
174 [JsonPropertyName("replyToId")] public string? ReplyToId { get; set; }
175 [JsonExtensionData] public ExtendedPropertiesDictionary Properties { get; set; } = [];
176
177 // AOT-compatible serialization
178 public virtual string ToJson() => JsonSerializer.Serialize(this, CoreActivityJsonContext.Default.CoreActivity);
179 public static CoreActivity FromJsonString(string json) => ...;
180 public static ValueTask<CoreActivity?> FromJsonStreamAsync(Stream stream, CancellationToken ct) => ...;
181}
182```
183
184### `ConversationAccount.cs` (Schema)
185
186```csharp
187public class ConversationAccount
188{
189 [JsonPropertyName("id")] public string? Id { get; set; }
190 [JsonPropertyName("name")] public string? Name { get; set; }
191 [JsonExtensionData] public ExtendedPropertiesDictionary Properties { get; set; } = [];
192}
193```
194
195### `Conversation.cs` (Schema)
196
197```csharp
198public class Conversation
199{
200 [JsonPropertyName("id")] public string Id { get; set; } = string.Empty;
201 [JsonExtensionData] public ExtendedPropertiesDictionary Properties { get; set; } = [];
202}
203```
204
205> **Note:** Core's `Conversation` has only `Id` + extension data. Main's `Conversation` has `Id`, `Type`, `Name`. This affects the `chat` wire format — see porting design doc.
206
207---
208
209## Layer 2: `Microsoft.Teams.Bot.Apps`
210
211Teams-specific application layer built on Core.
212
213### `TeamsBotApplication.cs`
214
215Extends `BotApplication` with Teams routing:
216
217```csharp
218public class TeamsBotApplication : BotApplication
219{
220 private readonly Router _router = new();
221
222 // Handler registration
223 public TeamsBotApplication OnMessage(Func<Context, CancellationToken, Task> handler) { ... }
224 public TeamsBotApplication OnInvoke(string name, Func<Context, CancellationToken, Task> handler) { ... }
225 // ... other handler types
226}
227```
228
229### Hosting Extensions
230
231**Service registration:**
232```csharp
233// AddTeamsBotApplication registers:
234// - TeamsApiClient (with auth handler)
235// - Then calls AddBotApplication<TeamsBotApplication>() which registers:
236// - BotApplicationOptions
237// - HttpContextAccessor
238// - JWT auth + authorization
239// - ConversationClient (with named HttpClient "BotConversationClient" + auth handler)
240// - UserTokenClient
241// - TeamsBotApplication as singleton
242```
243
244**Endpoint mapping:**
245```csharp
246public static TApp UseBotApplication<TApp>(this IEndpointRouteBuilder endpoints, string routePath = "api/messages")
247 where TApp : BotApplication
248{
249 // Adds auth/authz middleware
250 if (endpoints is IApplicationBuilder app)
251 {
252 app.UseAuthentication();
253 app.UseAuthorization();
254 }
255
256 TApp botApp = endpoints.ServiceProvider.GetService<TApp>()
257 ?? throw new InvalidOperationException("Application not registered");
258
259 // Maps POST endpoint that calls ProcessAsync
260 endpoints.MapPost(routePath, (HttpContext httpContext, CancellationToken cancellationToken)
261 => botApp.ProcessAsync(httpContext, cancellationToken)
262 ).RequireAuthorization();
263
264 return botApp;
265}
266```
267
268---
269
270## Typical Usage
271
272```csharp
273var builder = WebApplication.CreateSlimBuilder(args);
274builder.Services.AddTeamsBotApplication();
275
276var app = builder.Build();
277var teamsApp = app.UseTeamsBotApplication();
278
279teamsApp.OnMessage(async (context, ct) =>
280{
281 await context.SendActivityAsync(new CoreActivity("message") { ... }, ct);
282});
283
284app.Run();
285```
286
287---
288
289## Summary: What DevTools Needs to Hook Into
290
291| Concern | Core mechanism |
292|---------|---------------|
293| Intercept incoming activities | `ITurnMiddleware` — registered via `botApp.UseMiddleware()` |
294| Intercept outgoing activities | Subclass `ConversationClient` — override `virtual SendActivityAsync()` |
295| Intercept errors | Middleware wraps `nextTurn()` in try/catch |
296| Serve static files | `IApplicationBuilder` middleware / `IEndpointRouteBuilder` endpoints |
297| WebSocket connections | `IApplicationBuilder.UseWebSockets()` + endpoint mapping |
298| DI registration | `IServiceCollection` extensions |
299| Configuration | `IConfiguration` binding |
300