microsoft/teams.net

Public

mirrored from https://github.com/microsoft/teams.netAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
pr-134

Branches

Tags

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

Clone

HTTPS

Download ZIP

Samples/Samples.Cards/Program.cs

514lines · modeblame

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