Skip to content

Commit bfec218

Browse files
Merge pull request #1523 from OfficeDev/v-hrajandira/Thirdparty-storage-csharp
Create New Sample Msgext Thirdparty Storage Csharp
2 parents 1307140 + fa1cd08 commit bfec218

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+1676
-4
lines changed

.github/workflows/build-complete-samples.yml

+4
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,10 @@ jobs:
487487
- project_path: 'samples/bot-streaming/csharp/StreamingBot.csproj'
488488
name: 'bot-streaming'
489489
version: '8.0.x'
490+
491+
- project_path: 'samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage.csproj'
492+
name: 'msgext-thirdparty-storage'
493+
version: '6.0.x'
490494

491495
fail-fast: false
492496
name: Build All "${{ matrix.name }}" csharp

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ The [Teams Toolkit](https://marketplace.visualstudio.com/items?itemName=TeamsDev
148148
| 9 | Messaging Extensions AI - Sentiment Analysis | Messaging Extension with AI capability that performs sentiment analysis (positive/negative/neutral) for messages posted in Microsoft Teams chat | Advanced | [View][msgext-ai-sentiment-analysis#cs] | [View][msgext-ai-sentiment-analysis#js] ![toolkit-icon](assets/toolkit-icon.png) | | | |
149149
| 10 | Messaging extension - Adaptive Card based loop components | Demonstrates a Messaging Extension for Microsoft Teams that utilizes adaptive card-based loop components, enabling link unfurling and dynamic interactions within the compose area. | Advanced | [View][msgext-unfurling-ac-loop-components#cs] | [View][msgext-unfurling-ac-loop-components#nodejs] ![toolkit-icon](assets/toolkit-icon.png) | | | [View](/samples/msgext-unfurling-ac-loop-components/csharp/demo-manifest/msgext-unfurling-ac-loop-components.zip) |
150150
| 11 | Copilot Handoff | This sample implements a Teams message extension that can be used as a plugin for Microsoft Copilot for Microsoft 365. The message extension showcases copilot handoff along with allowing users to query the Northwind Database | Advanced | | [View][msgext-copilot-handoff#js] ![toolkit-icon](assets/toolkit-icon.png) | | | |
151-
| 12 | Third-Party Cloud Storage | This app enables seamless integration with third-party cloud storage providers for files dragged and dropped in Teams chats or channels. It uses the Microsoft Teams JavaScript SDK's thirdPartyCloudStorage module to fetch and upload files efficiently. | Advanced | | [View][msgext-thirdparty-storage#nodejs] ![toolkit-icon](assets/toolkit-icon.png) | | | |
151+
| 12 | Third-Party Cloud Storage | This app enables seamless integration with third-party cloud storage providers for files dragged and dropped in Teams chats or channels. It uses the Microsoft Teams JavaScript SDK's thirdPartyCloudStorage module to fetch and upload files efficiently. | Advanced | [View][msgext-thirdparty-storage#cs] | [View][msgext-thirdparty-storage#nodejs] ![toolkit-icon](assets/toolkit-icon.png) | | | |
152152

153153
#### Additional samples
154154

@@ -344,6 +344,7 @@ The [Teams Toolkit](https://marketplace.visualstudio.com/items?itemName=TeamsDev
344344
[msgext-search-sso-config#cs]:samples/msgext-search-sso-config/csharp
345345
[msgext-copilot-handoff#js]:samples/msgext-copilot-handoff/ts
346346
[msgext-thirdparty-storage#nodejs]:samples/msgext-thirdparty-storage/nodejs
347+
[msgext-thirdparty-storage#cs]:samples/msgext-thirdparty-storage/csharp
347348
[msgext-expert-finder#nodejs]:samples/msgext-expert-finder-js
348349

349350
[tab-sso#ts]:samples/tab-sso/nodejs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# TeamsFx files
2+
build
3+
appPackage/build
4+
env/.env.*.user
5+
env/.env.local
6+
appsettings.Development.json
7+
.deployment
8+
9+
# User-specific files
10+
*.user
11+
12+
# Build results
13+
[Dd]ebug/
14+
[Dd]ebugPublic/
15+
[Rr]elease/
16+
[Rr]eleases/
17+
x64/
18+
x86/
19+
bld/
20+
[Bb]in/
21+
[Oo]bj/
22+
[Ll]og/
23+
24+
# Notification local store
25+
.notification.localstore.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using Microsoft.Bot.Builder.Integration.AspNet.Core;
5+
using Microsoft.Bot.Builder.TraceExtensions;
6+
using Microsoft.Bot.Connector.Authentication;
7+
using Microsoft.Extensions.Logging;
8+
9+
namespace Microsoft.BotBuilderSamples
10+
{
11+
public class AdapterWithErrorHandler : CloudAdapter
12+
{
13+
public AdapterWithErrorHandler(BotFrameworkAuthentication auth, ILogger<IBotFrameworkHttpAdapter> logger)
14+
: base(auth, logger)
15+
{
16+
OnTurnError = async (turnContext, exception) =>
17+
{
18+
// Log any leaked exception from the application.
19+
// NOTE: In production environment, you should consider logging this to
20+
// Azure Application Insights. Visit https://aka.ms/bottelemetry to see how
21+
// to add telemetry capture to your bot.
22+
logger.LogError(exception, $"[OnTurnError] unhandled error : {exception.Message}");
23+
24+
// Uncomment below commented line for local debugging.
25+
// await turnContext.SendActivityAsync($"Sorry, it looks like something went wrong. Exception Caught: {exception.Message}");
26+
27+
// Send a trace activity, which will be displayed in the Bot Framework Emulator
28+
await turnContext.TraceActivityAsync("OnTurnError Trace", exception.Message, "https://www.botframework.com/schemas/error", "TurnError");
29+
};
30+
}
31+
}
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
using AdaptiveCards;
10+
using Microsoft.Bot.Builder;
11+
using Microsoft.Bot.Builder.Teams;
12+
using Microsoft.Bot.Schema;
13+
using Microsoft.Bot.Schema.Teams;
14+
using Microsoft.Extensions.Configuration;
15+
using Newtonsoft.Json.Linq;
16+
17+
namespace Microsoft.BotBuilderSamples.Bots
18+
{
19+
/// <summary>
20+
/// A bot that handles Teams messaging extensions for third-party storage integration.
21+
/// </summary>
22+
public class TeamsMsgextThirdpartyStorageBot : TeamsActivityHandler
23+
{
24+
private readonly string baseUrl;
25+
26+
/// <summary>
27+
/// Initializes a new instance of the <see cref="TeamsMsgextThirdpartyStorageBot"/> class.
28+
/// </summary>
29+
/// <param name="configuration">Configuration object to access app settings.</param>
30+
public TeamsMsgextThirdpartyStorageBot(IConfiguration configuration)
31+
{
32+
this.baseUrl = configuration["BaseUrl"] ?? throw new ArgumentNullException(nameof(configuration), "BaseUrl is not configured.");
33+
}
34+
35+
/// <summary>
36+
/// Handles the submit action for messaging extensions.
37+
/// </summary>
38+
/// <param name="turnContext">The turn context.</param>
39+
/// <param name="action">The messaging extension action.</param>
40+
/// <param name="cancellationToken">The cancellation token.</param>
41+
/// <returns>A task that represents the messaging extension response.</returns>
42+
protected override async Task<MessagingExtensionActionResponse> OnTeamsMessagingExtensionSubmitActionAsync(
43+
ITurnContext<IInvokeActivity> turnContext,
44+
MessagingExtensionAction action,
45+
CancellationToken cancellationToken)
46+
{
47+
try
48+
{
49+
switch (action.CommandId)
50+
{
51+
case "createWithPreview":
52+
return CreateWebViewResponse(turnContext, action);
53+
default:
54+
throw new NotSupportedException($"Command '{action.CommandId}' is not supported.");
55+
}
56+
}
57+
catch (Exception ex)
58+
{
59+
// Log the exception
60+
Console.WriteLine($"Error in OnTeamsMessagingExtensionSubmitActionAsync: {ex.Message}");
61+
return new MessagingExtensionActionResponse
62+
{
63+
ComposeExtension = new MessagingExtensionResult
64+
{
65+
Type = "message",
66+
Text = "An error occurred while processing your request. Please try again later."
67+
}
68+
};
69+
}
70+
}
71+
72+
/// <summary>
73+
/// Generates a messaging extension action response containing an Adaptive Card with file details.
74+
/// </summary>
75+
/// <param name="turnContext">The turn context.</param>
76+
/// <param name="action">The messaging extension action.</param>
77+
/// <returns>A MessagingExtensionActionResponse with an Adaptive Card.</returns>
78+
private MessagingExtensionActionResponse CreateWebViewResponse(ITurnContext<IInvokeActivity> turnContext, MessagingExtensionAction action)
79+
{
80+
try
81+
{
82+
// Parse the uploaded data from action.Data
83+
var uploadedFiles = JArray.Parse(action.Data.ToString());
84+
85+
// Dictionary for file type icons
86+
var fileTypeIcons = new Dictionary<string, string>
87+
{
88+
{ "spreadsheet", $"{this.baseUrl}/icons/ExcelIcon.png" },
89+
{ "pdf", $"{this.baseUrl}/icons/PdfIcon.png" },
90+
{ "wordprocessing", $"{this.baseUrl}/icons/WordIcons.png" },
91+
{ "image", $"{this.baseUrl}/icons/ImageIcon.png" },
92+
};
93+
94+
var cardBody = new List<AdaptiveElement>();
95+
96+
foreach (var file in uploadedFiles)
97+
{
98+
string name = file["name"]?.ToString() ?? "Unknown";
99+
string type = file["type"]?.ToString() ?? string.Empty;
100+
string fileTypeIconUrl = fileTypeIcons.FirstOrDefault(kvp => type.Contains(kvp.Key)).Value
101+
?? $"{this.baseUrl}/icons/DefaultIcon.png";
102+
103+
cardBody.Add(new AdaptiveColumnSet
104+
{
105+
Columns = new List<AdaptiveColumn>
106+
{
107+
new AdaptiveColumn
108+
{
109+
Width = "auto",
110+
Items = new List<AdaptiveElement>
111+
{
112+
new AdaptiveImage
113+
{
114+
Url = new Uri(fileTypeIconUrl),
115+
Size = AdaptiveImageSize.Small
116+
}
117+
}
118+
},
119+
new AdaptiveColumn
120+
{
121+
Width = "stretch",
122+
Items = new List<AdaptiveElement>
123+
{
124+
new AdaptiveTextBlock
125+
{
126+
Text = name,
127+
Wrap = true
128+
}
129+
}
130+
}
131+
}
132+
});
133+
}
134+
135+
// Create Adaptive Card
136+
var adaptiveCard = new AdaptiveCard(new AdaptiveSchemaVersion(1, 4)) { Body = cardBody };
137+
138+
return new MessagingExtensionActionResponse
139+
{
140+
ComposeExtension = new MessagingExtensionResult
141+
{
142+
Type = "result",
143+
AttachmentLayout = "list",
144+
Attachments = new List<MessagingExtensionAttachment>
145+
{
146+
new MessagingExtensionAttachment
147+
{
148+
ContentType = AdaptiveCard.ContentType,
149+
Content = adaptiveCard
150+
}
151+
}
152+
}
153+
};
154+
}
155+
catch (Exception ex)
156+
{
157+
// Log the exception
158+
Console.WriteLine($"Error in CreateWebViewResponse: {ex.Message}");
159+
throw;
160+
}
161+
}
162+
163+
/// <summary>
164+
/// Handles the fetch task for messaging extensions.
165+
/// </summary>
166+
/// <param name="turnContext">The turn context.</param>
167+
/// <param name="action">The messaging extension action.</param>
168+
/// <param name="cancellationToken">The cancellation token.</param>
169+
/// <returns>A task that represents the messaging extension response.</returns>
170+
protected override async Task<MessagingExtensionActionResponse> OnTeamsMessagingExtensionFetchTaskAsync(
171+
ITurnContext<IInvokeActivity> turnContext,
172+
MessagingExtensionAction action,
173+
CancellationToken cancellationToken)
174+
{
175+
try
176+
{
177+
// Check if the replyToId is empty and commandContext is "thirdParty"
178+
var activityValue = turnContext.Activity.Value as JObject;
179+
var commandContext = activityValue?["commandContext"]?.ToString();
180+
var replyToId = activityValue?["messagePayload"]?["replyToId"]?.ToString();
181+
182+
// Process if conditions are met
183+
if (replyToId == "" && commandContext == "thirdParty")
184+
{
185+
// Call the method to generate the response based on context
186+
return CreateMediaNameDetailsTaskResponse(turnContext, action);
187+
}
188+
189+
// Default response for other conditions
190+
return new MessagingExtensionActionResponse
191+
{
192+
Task = new TaskModuleContinueResponse
193+
{
194+
Value = new TaskModuleTaskInfo
195+
{
196+
Title = "Default Task",
197+
Height = 200,
198+
Width = 400,
199+
Url = null
200+
}
201+
}
202+
};
203+
}
204+
catch (Exception ex)
205+
{
206+
// Log the exception for debugging
207+
Console.WriteLine($"Error in OnTeamsMessagingExtensionFetchTaskAsync: {ex.Message}");
208+
209+
// Return an error response
210+
return new MessagingExtensionActionResponse
211+
{
212+
Task = new TaskModuleContinueResponse
213+
{
214+
Value = new TaskModuleTaskInfo
215+
{
216+
Title = "Error",
217+
Height = 200,
218+
Width = 400,
219+
Url = null
220+
}
221+
}
222+
};
223+
}
224+
}
225+
226+
/// <summary>
227+
/// Generates a task module response for the employee details form.
228+
/// </summary>
229+
/// <param name="turnContext">The turn context.</param>
230+
/// <param name="action">The messaging extension action.</param>
231+
/// <returns>A MessagingExtensionActionResponse with a task module.</returns>
232+
private MessagingExtensionActionResponse CreateMediaNameDetailsTaskResponse(ITurnContext<IInvokeActivity> turnContext, MessagingExtensionAction action)
233+
{
234+
try
235+
{
236+
return new MessagingExtensionActionResponse
237+
{
238+
Task = new TaskModuleContinueResponse
239+
{
240+
Value = new TaskModuleTaskInfo
241+
{
242+
Height = 530,
243+
Width = 700,
244+
Title = "Task Module WebView",
245+
Url = $"{this.baseUrl}/CustomForm",
246+
},
247+
},
248+
};
249+
}
250+
catch (Exception ex)
251+
{
252+
// Log the exception
253+
Console.WriteLine($"Error in CreateMediaNameDetailsTaskResponse: {ex.Message}");
254+
throw;
255+
}
256+
}
257+
}
258+
}

0 commit comments

Comments
 (0)