Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create New Sample Msgext Thirdparty Storage Csharp #1523

Merged
merged 7 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/build-complete-samples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,10 @@ jobs:
- project_path: 'samples/bot-streaming/csharp/StreamingBot.csproj'
name: 'bot-streaming'
version: '8.0.x'

- project_path: 'samples/msgext-thirdparty-storage/csharp/TeamsMsgextThirdpartyStorage.csproj'
name: 'msgext-thirdparty-storage'
version: '6.0.x'

fail-fast: false
name: Build All "${{ matrix.name }}" csharp
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ The [Teams Toolkit](https://marketplace.visualstudio.com/items?itemName=TeamsDev
| 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) | | | |
| 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) |
| 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) | | | |
| 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) | | | |
| 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) | | | |

#### Additional samples

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

[tab-sso#ts]:samples/tab-sso/nodejs
Expand Down
25 changes: 25 additions & 0 deletions samples/msgext-thirdparty-storage/csharp/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# TeamsFx files
build
appPackage/build
env/.env.*.user
env/.env.local
appsettings.Development.json
.deployment

# User-specific files
*.user

# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/

# Notification local store
.notification.localstore.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Microsoft.Bot.Builder.Integration.AspNet.Core;
using Microsoft.Bot.Builder.TraceExtensions;
using Microsoft.Bot.Connector.Authentication;
using Microsoft.Extensions.Logging;

namespace Microsoft.BotBuilderSamples
{
public class AdapterWithErrorHandler : CloudAdapter
{
public AdapterWithErrorHandler(BotFrameworkAuthentication auth, ILogger<IBotFrameworkHttpAdapter> logger)
: base(auth, logger)
{
OnTurnError = async (turnContext, exception) =>
{
// Log any leaked exception from the application.
// NOTE: In production environment, you should consider logging this to
// Azure Application Insights. Visit https://aka.ms/bottelemetry to see how
// to add telemetry capture to your bot.
logger.LogError(exception, $"[OnTurnError] unhandled error : {exception.Message}");

// Uncomment below commented line for local debugging.
// await turnContext.SendActivityAsync($"Sorry, it looks like something went wrong. Exception Caught: {exception.Message}");

// Send a trace activity, which will be displayed in the Bot Framework Emulator
await turnContext.TraceActivityAsync("OnTurnError Trace", exception.Message, "https://www.botframework.com/schemas/error", "TurnError");
};
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AdaptiveCards;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Teams;
using Microsoft.Bot.Schema;
using Microsoft.Bot.Schema.Teams;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json.Linq;

namespace Microsoft.BotBuilderSamples.Bots
{
/// <summary>
/// A bot that handles Teams messaging extensions for third-party storage integration.
/// </summary>
public class TeamsMsgextThirdpartyStorageBot : TeamsActivityHandler
{
private readonly string baseUrl;

/// <summary>
/// Initializes a new instance of the <see cref="TeamsMsgextThirdpartyStorageBot"/> class.
/// </summary>
/// <param name="configuration">Configuration object to access app settings.</param>
public TeamsMsgextThirdpartyStorageBot(IConfiguration configuration)
{
this.baseUrl = configuration["BaseUrl"] ?? throw new ArgumentNullException(nameof(configuration), "BaseUrl is not configured.");
}

/// <summary>
/// Handles the submit action for messaging extensions.
/// </summary>
/// <param name="turnContext">The turn context.</param>
/// <param name="action">The messaging extension action.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task that represents the messaging extension response.</returns>
protected override async Task<MessagingExtensionActionResponse> OnTeamsMessagingExtensionSubmitActionAsync(
ITurnContext<IInvokeActivity> turnContext,
MessagingExtensionAction action,
CancellationToken cancellationToken)
{
try
{
switch (action.CommandId)
{
case "createWithPreview":
return CreateWebViewResponse(turnContext, action);
default:
throw new NotSupportedException($"Command '{action.CommandId}' is not supported.");
}
}
catch (Exception ex)
{
// Log the exception
Console.WriteLine($"Error in OnTeamsMessagingExtensionSubmitActionAsync: {ex.Message}");
return new MessagingExtensionActionResponse
{
ComposeExtension = new MessagingExtensionResult
{
Type = "message",
Text = "An error occurred while processing your request. Please try again later."
}
};
}
}

/// <summary>
/// Generates a messaging extension action response containing an Adaptive Card with file details.
/// </summary>
/// <param name="turnContext">The turn context.</param>
/// <param name="action">The messaging extension action.</param>
/// <returns>A MessagingExtensionActionResponse with an Adaptive Card.</returns>
private MessagingExtensionActionResponse CreateWebViewResponse(ITurnContext<IInvokeActivity> turnContext, MessagingExtensionAction action)
{
try
{
// Parse the uploaded data from action.Data
var uploadedFiles = JArray.Parse(action.Data.ToString());

// Dictionary for file type icons
var fileTypeIcons = new Dictionary<string, string>
{
{ "spreadsheet", $"{this.baseUrl}/icons/ExcelIcon.png" },
{ "pdf", $"{this.baseUrl}/icons/PdfIcon.png" },
{ "wordprocessing", $"{this.baseUrl}/icons/WordIcons.png" },
{ "image", $"{this.baseUrl}/icons/ImageIcon.png" },
};

var cardBody = new List<AdaptiveElement>();

foreach (var file in uploadedFiles)
{
string name = file["name"]?.ToString() ?? "Unknown";
string type = file["type"]?.ToString() ?? string.Empty;
string fileTypeIconUrl = fileTypeIcons.FirstOrDefault(kvp => type.Contains(kvp.Key)).Value
?? $"{this.baseUrl}/icons/DefaultIcon.png";

cardBody.Add(new AdaptiveColumnSet
{
Columns = new List<AdaptiveColumn>
{
new AdaptiveColumn
{
Width = "auto",
Items = new List<AdaptiveElement>
{
new AdaptiveImage
{
Url = new Uri(fileTypeIconUrl),
Size = AdaptiveImageSize.Small
}
}
},
new AdaptiveColumn
{
Width = "stretch",
Items = new List<AdaptiveElement>
{
new AdaptiveTextBlock
{
Text = name,
Wrap = true
}
}
}
}
});
}

// Create Adaptive Card
var adaptiveCard = new AdaptiveCard(new AdaptiveSchemaVersion(1, 4)) { Body = cardBody };

return new MessagingExtensionActionResponse
{
ComposeExtension = new MessagingExtensionResult
{
Type = "result",
AttachmentLayout = "list",
Attachments = new List<MessagingExtensionAttachment>
{
new MessagingExtensionAttachment
{
ContentType = AdaptiveCard.ContentType,
Content = adaptiveCard
}
}
}
};
}
catch (Exception ex)
{
// Log the exception
Console.WriteLine($"Error in CreateWebViewResponse: {ex.Message}");
throw;
}
}

/// <summary>
/// Handles the fetch task for messaging extensions.
/// </summary>
/// <param name="turnContext">The turn context.</param>
/// <param name="action">The messaging extension action.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task that represents the messaging extension response.</returns>
protected override async Task<MessagingExtensionActionResponse> OnTeamsMessagingExtensionFetchTaskAsync(
ITurnContext<IInvokeActivity> turnContext,
MessagingExtensionAction action,
CancellationToken cancellationToken)
{
try
{
// Check if the replyToId is empty and commandContext is "thirdParty"
var activityValue = turnContext.Activity.Value as JObject;
var commandContext = activityValue?["commandContext"]?.ToString();
var replyToId = activityValue?["messagePayload"]?["replyToId"]?.ToString();

// Process if conditions are met
if (replyToId == "" && commandContext == "thirdParty")
{
// Call the method to generate the response based on context
return CreateMediaNameDetailsTaskResponse(turnContext, action);
}

// Default response for other conditions
return new MessagingExtensionActionResponse
{
Task = new TaskModuleContinueResponse
{
Value = new TaskModuleTaskInfo
{
Title = "Default Task",
Height = 200,
Width = 400,
Url = null
}
}
};
}
catch (Exception ex)
{
// Log the exception for debugging
Console.WriteLine($"Error in OnTeamsMessagingExtensionFetchTaskAsync: {ex.Message}");

// Return an error response
return new MessagingExtensionActionResponse
{
Task = new TaskModuleContinueResponse
{
Value = new TaskModuleTaskInfo
{
Title = "Error",
Height = 200,
Width = 400,
Url = null
}
}
};
}
}

/// <summary>
/// Generates a task module response for the employee details form.
/// </summary>
/// <param name="turnContext">The turn context.</param>
/// <param name="action">The messaging extension action.</param>
/// <returns>A MessagingExtensionActionResponse with a task module.</returns>
private MessagingExtensionActionResponse CreateMediaNameDetailsTaskResponse(ITurnContext<IInvokeActivity> turnContext, MessagingExtensionAction action)
{
try
{
return new MessagingExtensionActionResponse
{
Task = new TaskModuleContinueResponse
{
Value = new TaskModuleTaskInfo
{
Height = 530,
Width = 700,
Title = "Task Module WebView",
Url = $"{this.baseUrl}/CustomForm",
},
},
};
}
catch (Exception ex)
{
// Log the exception
Console.WriteLine($"Error in CreateMediaNameDetailsTaskResponse: {ex.Message}");
throw;
}
}
}
}
Loading