microsoft/teams.net

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
91427f12753b43af544fcd5f77244b200b039c87

Branches

Tags

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

Clone

HTTPS

Download ZIP

Samples/Samples.Cards/Program.cs

526lines · modecode

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