microsoft/teams.net

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
feat/msal

Branches

Tags

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

Clone

HTTPS

Download ZIP

Samples/Samples.Cards/Program.cs

515lines · modecode

1using System.Text.Json;
2
3using Microsoft.Teams.Api.Activities.Invokes;
4using Microsoft.Teams.Api.AdaptiveCards;
5using Microsoft.Teams.Apps;
6using Microsoft.Teams.Apps.Activities;
7using Microsoft.Teams.Apps.Annotations;
8using Microsoft.Teams.Apps.Extensions;
9using Microsoft.Teams.Apps.Plugins;
10using Microsoft.Teams.Cards;
11using Microsoft.Teams.Common;
12using Microsoft.Teams.Plugins.AspNetCore.DevTools.Extensions;
13using Microsoft.Teams.Plugins.AspNetCore.Extensions;
14
15namespace Samples.Cards;
16
17public static partial class Program
18{
19 public static void Main(string[] args)
20 {
21 var builder = WebApplication.CreateBuilder(args);
22 // Bind the application to localhost:3978 when run with `dotnet run`
23 builder.WebHost.UseUrls("http://localhost:3978");
24 builder.Services.AddOpenApi();
25 builder.Services.AddTransient<Controller>();
26 builder.AddTeams().AddTeamsDevTools();
27
28 var app = builder.Build();
29
30 if (app.Environment.IsDevelopment())
31 {
32 app.MapOpenApi();
33 }
34
35 app.UseHttpsRedirection();
36 app.UseTeams();
37 app.Run();
38 }
39
40 [TeamsController]
41 public class Controller
42 {
43 [Message]
44 public async Task OnMessage([Context] Microsoft.Teams.Api.Activities.MessageActivity activity, [Context] IContext.Client client, [Context] Microsoft.Teams.Common.Logging.ILogger log)
45 {
46 log.Info($"[MESSAGE] Received: {SanitizeForLog(activity.Text)}");
47 log.Info($"[MESSAGE] From: {SanitizeForLog(activity.From?.Name ?? "unknown")}");
48
49 var text = activity.Text?.ToLowerInvariant() ?? "";
50
51 if (text.Contains("card"))
52 {
53 log.Info("[CARD] Basic card requested");
54 var card = CreateBasicAdaptiveCard();
55 await client.Send(card);
56 }
57 else if (text.Contains("profile"))
58 {
59 log.Info("[PROFILE] Profile card requested");
60 var card = CreateProfileCard();
61 await client.Send(card);
62 }
63 else if (text.Contains("validation"))
64 {
65 log.Info("[VALIDATION] Validation card requested");
66 var card = CreateProfileCardWithValidation();
67 await client.Send(card);
68 }
69 else if (text.Contains("feedback"))
70 {
71 log.Info("[FEEDBACK] Feedback card requested");
72 var card = CreateFeedbackCard();
73 await client.Send(card);
74 }
75 else if (text.Contains("form"))
76 {
77 log.Info("[FORM] Task form card requested");
78 var card = CreateTaskFormCard();
79 await client.Send(card);
80 }
81 else if (text.Contains("json"))
82 {
83 log.Info("[JSON] JSON deserialization card requested");
84 var card = CreateCardFromJson();
85 await client.Send(card);
86 }
87 else if (text.Contains("reply"))
88 {
89 await client.Send("Hello! How can I assist you today?");
90 }
91 else
92 {
93 await client.Typing();
94 await client.Send($"You said '{activity.Text}'. Try typing: card, profile, validation, feedback, form, json, or reply");
95 }
96 }
97
98 [Microsoft.Teams.Apps.Activities.Invokes.AdaptiveCard.Action]
99 public async Task<ActionResponse> OnCardAction([Context] AdaptiveCards.ActionActivity activity, [Context] IContext.Client client, [Context] Microsoft.Teams.Common.Logging.ILogger log)
100 {
101 log.Info("[CARD_ACTION] Card action received");
102
103 var data = activity.Value?.Action?.Data;
104
105 // Let's log the actual data structure to understand what we're working with
106 log.Info($"[CARD_ACTION] Raw data: {System.Text.Json.JsonSerializer.Serialize(data)}");
107
108 if (data == null)
109 {
110 log.Error("[CARD_ACTION] No data in card action");
111 return new ActionResponse.Message("No data specified") { StatusCode = 400 };
112 }
113
114 // Extract action from the Value property
115 string? action = data.TryGetValue("action", out var actionObj) ? actionObj?.ToString() : null;
116
117 if (string.IsNullOrEmpty(action))
118 {
119 log.Error("[CARD_ACTION] No action specified in card data");
120 return new ActionResponse.Message("No action specified") { StatusCode = 400 };
121 }
122 log.Info($"[CARD_ACTION] Processing action: {action}");
123
124 // Helper method to extract form field values (they're at root level, not in Value)
125 string? GetFormValue(string key)
126 {
127 if (data.TryGetValue(key, out var val))
128 {
129 if (val is System.Text.Json.JsonElement element)
130 return element.GetString();
131 return val?.ToString();
132 }
133 return null;
134 }
135
136 switch (action)
137 {
138 case "submit_basic":
139 var notifyValue = GetFormValue("notify") ?? "false";
140 await client.Send($"Basic card submitted! Notify setting: {notifyValue}");
141 break;
142
143 case "submit_feedback":
144 var feedbackText = GetFormValue("feedback") ?? "No feedback provided";
145 await client.Send($"Feedback received: {feedbackText}");
146 break;
147
148 case "create_task":
149 var title = GetFormValue("title") ?? "Untitled";
150 var priority = GetFormValue("priority") ?? "medium";
151 var dueDate = GetFormValue("due_date") ?? "No date";
152 await client.Send($"Task created!\nTitle: {title}\nPriority: {priority}\nDue: {dueDate}");
153 break;
154
155 case "save_profile":
156 var name = GetFormValue("name") ?? "Unknown";
157 var email = GetFormValue("email") ?? "No email";
158 var subscribe = GetFormValue("subscribe") ?? "false";
159 var age = GetFormValue("age");
160 var location = GetFormValue("location") ?? "Not specified";
161
162 var response = $"Profile saved!\nName: {name}\nEmail: {email}\nSubscribed: {subscribe}";
163 if (!string.IsNullOrEmpty(age))
164 response += $"\nAge: {age}";
165 if (location != "Not specified")
166 response += $"\nLocation: {location}";
167
168 await client.Send(response);
169 break;
170
171 case "test_json":
172 await client.Send("✅ JSON deserialization test successful! The card was properly created from JSON and the action was processed correctly.");
173 break;
174
175 default:
176 log.Error($"[CARD_ACTION] Unknown action: {action}");
177 return new ActionResponse.Message("Unknown action") { StatusCode = 400 };
178 }
179
180 return new ActionResponse.Message("Action processed successfully") { StatusCode = 200 };
181 }
182
183 private static AdaptiveCard CreateBasicAdaptiveCard()
184 {
185 return new AdaptiveCard
186 {
187 Schema = "http://adaptivecards.io/schemas/adaptive-card.json",
188 Body = new List<CardElement>
189 {
190 new TextBlock("Hello world")
191 {
192 Wrap = true,
193 Weight = TextWeight.Bolder
194 },
195 new ToggleInput("Notify me")
196 {
197 Id = "notify"
198 }
199 },
200 Actions = new List<Microsoft.Teams.Cards.Action>
201 {
202 new ExecuteAction
203 {
204 Title = "Submit",
205 Data = new Union<string, SubmitActionData>(new SubmitActionData { NonSchemaProperties = new Dictionary<string, object?> { { "action", "submit_basic" } } }),
206 AssociatedInputs = AssociatedInputs.Auto
207 }
208 }
209 };
210 }
211
212 private static AdaptiveCard CreateProfileCard()
213 {
214 return new AdaptiveCard
215 {
216 Schema = "http://adaptivecards.io/schemas/adaptive-card.json",
217 Body = new List<CardElement>
218 {
219 new TextBlock("User Profile")
220 {
221 Weight = TextWeight.Bolder,
222 Size = TextSize.Large
223 },
224 new TextInput
225 {
226 Id = "name",
227 Label = "Name",
228 Value = "John Doe"
229 },
230 new TextInput
231 {
232 Id = "email",
233 Label = "Email",
234 Value = "john@contoso.com"
235 },
236 new ToggleInput("Subscribe to newsletter")
237 {
238 Id = "subscribe",
239 Value = "false"
240 }
241 },
242 Actions = new List<Microsoft.Teams.Cards.Action>
243 {
244 new ExecuteAction
245 {
246 Title = "Save",
247 Data = new Union<string, SubmitActionData>(new SubmitActionData { NonSchemaProperties = new Dictionary<string, object?> { { "action", "save_profile" }, { "entity_id", "12345" } } }),
248 AssociatedInputs = AssociatedInputs.Auto
249 },
250 new OpenUrlAction("https://adaptivecards.microsoft.com")
251 {
252 Title = "Learn More"
253 }
254 }
255 };
256 }
257
258 private static AdaptiveCard CreateProfileCardWithValidation()
259 {
260 return new AdaptiveCard
261 {
262 Schema = "http://adaptivecards.io/schemas/adaptive-card.json",
263 Body = new List<CardElement>
264 {
265 new TextBlock("Profile with Validation")
266 {
267 Weight = TextWeight.Bolder,
268 Size = TextSize.Large
269 },
270 new NumberInput
271 {
272 Id = "age",
273 Label = "Age",
274 IsRequired = true,
275 Min = 0,
276 Max = 120
277 },
278 new TextInput
279 {
280 Id = "name",
281 Label = "Name",
282 IsRequired = true,
283 ErrorMessage = "Name is required"
284 },
285 new TextInput
286 {
287 Id = "location",
288 Label = "Location"
289 }
290 },
291 Actions = new List<Microsoft.Teams.Cards.Action>
292 {
293 new ExecuteAction
294 {
295 Title = "Save",
296 Data = new Union<string, SubmitActionData>(new SubmitActionData { NonSchemaProperties = new Dictionary<string, object?> { { "action", "save_profile" } } }),
297 AssociatedInputs = AssociatedInputs.Auto
298 }
299 }
300 };
301 }
302
303 private static AdaptiveCard CreateFeedbackCard()
304 {
305 return new AdaptiveCard
306 {
307 Schema = "http://adaptivecards.io/schemas/adaptive-card.json",
308 Body = new List<CardElement>
309 {
310 new TextBlock("Feedback Form")
311 {
312 Weight = TextWeight.Bolder,
313 Size = TextSize.Large
314 },
315 new TextInput
316 {
317 Id = "feedback",
318 Label = "Your Feedback",
319 Placeholder = "Please share your thoughts...",
320 IsMultiline = true,
321 IsRequired = true
322 }
323 },
324 Actions = new List<Microsoft.Teams.Cards.Action>
325 {
326 new ExecuteAction
327 {
328 Title = "Submit Feedback",
329 Data = new Union<string, SubmitActionData>(new SubmitActionData { NonSchemaProperties = new Dictionary<string, object?> { { "action", "submit_feedback" } } }),
330 AssociatedInputs = AssociatedInputs.Auto
331 }
332 }
333 };
334 }
335
336 private static AdaptiveCard CreateTaskFormCard()
337 {
338 return new AdaptiveCard
339 {
340 Schema = "http://adaptivecards.io/schemas/adaptive-card.json",
341 Body = new List<CardElement>
342 {
343 new TextBlock("Create New Task")
344 {
345 Weight = TextWeight.Bolder,
346 Size = TextSize.Large
347 },
348 new TextInput
349 {
350 Id = "title",
351 Label = "Task Title",
352 Placeholder = "Enter task title"
353 },
354 new TextInput
355 {
356 Id = "description",
357 Label = "Description",
358 Placeholder = "Enter task details",
359 IsMultiline = true
360 },
361 new ChoiceSetInput
362 {
363 Id = "priority",
364 Label = "Priority",
365 Value = "medium",
366 Choices = new List<Choice>
367 {
368 new() { Title = "High", Value = "high" },
369 new() { Title = "Medium", Value = "medium" },
370 new() { Title = "Low", Value = "low" }
371 }
372 },
373 new DateInput
374 {
375 Id = "due_date",
376 Label = "Due Date",
377 Value = DateTime.Now.ToString("yyyy-MM-dd")
378 }
379 },
380 Actions = new List<Microsoft.Teams.Cards.Action>
381 {
382 new ExecuteAction
383 {
384 Title = "Create Task",
385 Data = new Union<string, SubmitActionData>(new SubmitActionData { NonSchemaProperties = new Dictionary<string, object?> { { "action", "create_task" } } }),
386 AssociatedInputs = AssociatedInputs.Auto,
387 Style = ActionStyle.Positive
388 }
389 }
390 };
391 }
392
393 private static AdaptiveCard CreateCardFromJson()
394 {
395 // JSON similar to the Python create_model_validate_card example
396 var cardJson = @"{
397 ""type"": ""AdaptiveCard"",
398 ""body"": [
399 {
400 ""type"": ""ColumnSet"",
401 ""columns"": [
402 {
403 ""type"": ""Column"",
404 ""verticalContentAlignment"": ""center"",
405 ""items"": [
406 {
407 ""type"": ""Image"",
408 ""style"": ""Person"",
409 ""url"": ""https://aka.ms/AAp9xo4"",
410 ""size"": ""Small"",
411 ""altText"": ""Portrait of David Claux""
412 }
413 ],
414 ""width"": ""auto""
415 },
416 {
417 ""type"": ""Column"",
418 ""spacing"": ""medium"",
419 ""verticalContentAlignment"": ""center"",
420 ""items"": [
421 {
422 ""type"": ""TextBlock"",
423 ""weight"": ""Bolder"",
424 ""text"": ""David Claux"",
425 ""wrap"": true
426 }
427 ],
428 ""width"": ""auto""
429 },
430 {
431 ""type"": ""Column"",
432 ""spacing"": ""medium"",
433 ""verticalContentAlignment"": ""center"",
434 ""items"": [
435 {
436 ""type"": ""TextBlock"",
437 ""text"": ""Principal Platform Architect at Microsoft"",
438 ""isSubtle"": true,
439 ""wrap"": true
440 }
441 ],
442 ""width"": ""stretch""
443 }
444 ]
445 },
446 {
447 ""type"": ""TextBlock"",
448 ""text"": ""This card was created from JSON deserialization!"",
449 ""wrap"": true,
450 ""color"": ""good"",
451 ""spacing"": ""medium""
452 }
453 ],
454 ""actions"": [
455 {
456 ""type"": ""Action.Execute"",
457 ""title"": ""Test JSON Action"",
458 ""data"": {
459 ""Value"": {
460 ""action"": ""test_json""
461 }
462 },
463 ""associatedInputs"": ""auto""
464 }
465 ],
466 ""version"": ""1.5"",
467 ""schema"": ""http://adaptivecards.io/schemas/adaptive-card.json""
468 }";
469
470 try
471 {
472 var card = JsonSerializer.Deserialize<AdaptiveCard>(cardJson);
473
474 return card ?? throw new InvalidOperationException("Failed to deserialize card");
475 }
476 catch (Exception ex)
477 {
478 Console.WriteLine($"Error deserializing card JSON: {ex.Message}");
479 // If deserialization fails, return a fallback card with error info
480 return new AdaptiveCard
481 {
482 Schema = "http://adaptivecards.io/schemas/adaptive-card.json",
483 Body = new List<CardElement>
484 {
485 new TextBlock("JSON Deserialization Test")
486 {
487 Weight = TextWeight.Bolder,
488 Size = TextSize.Large,
489 Color = TextColor.Attention
490 },
491 new TextBlock($"Deserialization failed: {ex.Message}")
492 {
493 Wrap = true,
494 Color = TextColor.Attention
495 }
496 }
497 };
498 }
499 }
500
501 [Microsoft.Teams.Apps.Events.Event("activity")]
502 public void OnEvent(IPlugin plugin, Microsoft.Teams.Apps.Events.Event @event)
503 {
504 Console.WriteLine("!!HIT!!");
505 }
506
507 // Helper method to sanitize user input for logging
508 private static string SanitizeForLog(string input)
509 {
510 if (input == null) return "";
511 // Remove carriage returns and line feeds to prevent log forging
512 return input.Replace("\r", "").Replace("\n", "");
513 }
514 }
515}