microsoft/teams.net

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
cc0afc372921f44ba6892f968e58ed3b0ad0ed29

Branches

Tags

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

Clone

HTTPS

Download ZIP

Samples/Samples.Cards/Program.cs

514lines · modecode

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