diff --git a/CSharp/core-SendAttachment/README.md b/CSharp/core-SendAttachment/README.md index 99fe758f00..dfdcc50bde 100644 --- a/CSharp/core-SendAttachment/README.md +++ b/CSharp/core-SendAttachment/README.md @@ -15,41 +15,148 @@ The minimum prerequisites to run this sample are: ### Code Highlights Many messaging channels provide the ability to attach richer objects. To pass a simple media attachment (image/audio/video/file) to an activity you add a simple attachment data structure with a link to the content, setting the ContentType, ContentUrl and Name properties. -The Attachments property is an array of Attachment objects which allow you to send and receive images and other content. Check out the key code located in the [SendAttachmentDialog](SendAttachmentDialog.cs#L22-L30) class where the `replyMessage.Attachments` property of the message activity is populated with an image attachment. +The Attachments property is an array of Attachment objects which allow you to send and receive images and other content. Check out the key code located in the [SendAttachmentDialog](SendAttachmentDialog.cs#L55-L82) class where the `replyMessage.Attachments` property of the message activity is populated with an image attachment. ````C# -public virtual async Task MessageReceivedAsync(IDialogContext context, IAwaitable argument) +public async Task ProcessSelectedOptionAsync(IDialogContext context, IAwaitable argument) { + var message = await argument; + var replyMessage = context.MakeMessage(); + Attachment attachment = null; + + switch (message) + { + case "1": + attachment = GetInlineAttachment(); + break; + case "2": + attachment = await GetUploadedAttachmentAsync(replyMessage.ServiceUrl, replyMessage.Conversation.Id); + break; + case "3": + attachment = GetInternetAttachment(); + break; + } + // The Attachments property allows you to send and receive images and other content - replyMessage.Attachments = new List() + replyMessage.Attachments = new List { attachment }; + + await context.PostAsync(replyMessage); + + await this.DisplayOptionsAsync(context); +} +```` + +As a developer, you have three ways to send the attachment. The attachment can be: + - An inline file, by encoding the file as base64 and use it in the contentUrl + - A file uploaded to the channel's store via the Connection API, then using the attachmentId to create the contentUrl + - An externally hosted file, by just specifying the Url of the file (it should be publicly accessible) + +#### Attaching the image inline + +It consists on sending the file contents, encoded in base64, along with the message payload. This option works for small files, like icon size images. +You'll need to encode file's content, then set the attachment's `contentUrl` as follows: + +```` +data:image/png;base64,iVBORw0KGgo… +```` + +Checkout [GetInlineAttachment](SendAttachmentDialog.cs#L84-L96) to see how to convert a file read from filesystem and return the attachment instance to add to the attachments collection in the key method shown above (`ProcessSelectedOptionAsync`). + +````C# +private static Attachment GetInlineAttachment() +{ + var imagePath = HttpContext.Current.Server.MapPath("~/images/small-image.png"); + + var imageData = Convert.ToBase64String(File.ReadAllBytes(imagePath)); + + return new Attachment + { + Name = "small-image.png", + ContentType = "image/png", + ContentUrl = $"data:image/png;base64,{imageData}" + }; +} +```` + +#### Uploading the file via the Connector API + +This option should be used when the file to send is less than 256Kb in size when encoded to base64. A good scenario are images generated based on user input. + +Checkout [GetUploadedAttachmentAsync](SendAttachmentDialog.cs#L98-L127) to see how to get the required information to create the attachment instance to add to the attachments collection in the key method shown above (`ProcessSelectedOptionAsync`). + +It does require a few more steps than the other methods, but leverages the channels store to store the file: + +1. Get an instance to the Connector API which will handle communication with channel storage ([relevant code](SendAttachmentDialog.cs#L102)) +2. Create a new attachment set providing the Connector API instance as argument ([relevant code](SendAttachmentDialog.cs#L104)) +3. Read (or generate) the content file and store it in a Byte array to use it as argument within the AttachmentData members ([relevant code](SendAttachmentDialog.cs#L110)) +4. Perform the attachment upload to the channel ([relevant code](SendAttachmentDialog.cs#L105)) +5. Get the attachment Uri (using the uploaded Id) where the channel stored the uploaded image ([relevant code](SendAttachmentDialog.cs#L114)) + +````C# +private static async Task GetUploadedAttachmentAsync(string serviceUrl, string conversationId) +{ + var imagePath = HttpContext.Current.Server.MapPath("~/images/big-image.png"); + + using (var connector = new ConnectorClient(new Uri(serviceUrl))) { - new Attachment() + var attachments = new Attachments(connector); + var response = await attachments.Client.Conversations.UploadAttachmentAsync( + conversationId, + new AttachmentData + { + Name = "big-image.png", + OriginalBase64 = File.ReadAllBytes(imagePath), + Type = "image/png" + }); + + var attachmentUri = attachments.GetAttachmentUri(response.Id); + + // Typo bug in current assembly version '.Replace("{vieWId}", Uri.EscapeDataString(viewId))'. + // TODO: remove this line when replacement Bug is fixed on future releases. PR: https://github.com/Microsoft/BotBuilder/pull/2079 + attachmentUri = attachmentUri.Replace("{viewId}", "original"); + + return new Attachment { - ContentUrl = "https://docs.botframework.com/en-us/images/faq-overview/botframework_overview_july.png", + Name = "big-image.png", ContentType = "image/png", - Name = "BotFrameworkOverview.png" - } - }; + ContentUrl = attachmentUri + }; + } +} +```` - await context.PostAsync(replyMessage); +#### Using an externally hosted file + +This option is the simplest but requires the image to be already on the Internet and be publicly accesible. +You could also provide an Url pointing to your own site. - context.Wait(this.MessageReceivedAsync); +Checkout [GetInternetAttachment](SendAttachmentDialog.cs#L129-L137) to see how to get the required information to create the attachment instance to add to the attachments collection in the key method shown above (`ProcessSelectedOptionAsync`). + +````C# +private static Attachment GetInternetAttachment() +{ + return new Attachment + { + Name = "BotFrameworkOverview.png", + ContentType = "image/png", + ContentUrl = "https://docs.botframework.com/en-us/images/faq-overview/botframework_overview_july.png" + }; } ```` ### Outcome -You will see the following in the Bot Framework Emulator when opening and running the sample solution. +You will see the following in the Bot Framework Emulator when selecting the inline attachment. See how the image is encoded in the `contentUrl` of the attachment. ![Sample Outcome](images/outcome-emulator.png) -You will see the following in your Facebook Messenger. +You will see the following in your Facebook Messenger when selecting to upload the attachment. ![Sample Outcome](images/outcome-facebook.png) -On the other hand, you will see the following in Skype. +On the other hand, you will see the following in Skype when selecting an Internet attachment. ![Sample Outcome](images/outcome-skype.png) diff --git a/CSharp/core-SendAttachment/SendAttachmentBot.csproj b/CSharp/core-SendAttachment/SendAttachmentBot.csproj index 08e9055477..016d750080 100644 --- a/CSharp/core-SendAttachment/SendAttachmentBot.csproj +++ b/CSharp/core-SendAttachment/SendAttachmentBot.csproj @@ -114,6 +114,8 @@ + + Designer diff --git a/CSharp/core-SendAttachment/SendAttachmentDialog.cs b/CSharp/core-SendAttachment/SendAttachmentDialog.cs index b748fbb2a9..de87de19fb 100644 --- a/CSharp/core-SendAttachment/SendAttachmentDialog.cs +++ b/CSharp/core-SendAttachment/SendAttachmentDialog.cs @@ -2,36 +2,138 @@ { using System; using System.Collections.Generic; + using System.IO; using System.Threading.Tasks; + using System.Web; using Microsoft.Bot.Builder.Dialogs; using Microsoft.Bot.Connector; [Serializable] internal class SendAttachmentDialog : IDialog { + private const string ShowInlineAttachment = "(1) Show inline attachment"; + private const string ShowUploadedAttachment = "(2) Show uploaded attachment"; + private const string ShowInternetAttachment = "(3) Show Internet attachment"; + + private readonly IDictionary options = new Dictionary + { + { "1", ShowInlineAttachment }, + { "2", ShowUploadedAttachment }, + { "3", ShowInternetAttachment } + }; + public async Task StartAsync(IDialogContext context) { context.Wait(this.MessageReceivedAsync); } - public virtual async Task MessageReceivedAsync(IDialogContext context, IAwaitable argument) + public async virtual Task MessageReceivedAsync(IDialogContext context, IAwaitable result) { + var message = await result; + + var welcomeMessage = context.MakeMessage(); + welcomeMessage.Text = "Welcome, here you can see attachment alternatives:"; + + await context.PostAsync(welcomeMessage); + + await this.DisplayOptionsAsync(context); + } + + public async Task DisplayOptionsAsync(IDialogContext context) + { + PromptDialog.Choice( + context, + this.ProcessSelectedOptionAsync, + this.options.Keys, + "What sample option would you like to see?", + "Ooops, what you wrote is not a valid option, please try again", + 3, + PromptStyle.PerLine, + this.options.Values); + } + + public async Task ProcessSelectedOptionAsync(IDialogContext context, IAwaitable argument) + { + var message = await argument; + var replyMessage = context.MakeMessage(); + Attachment attachment = null; + + switch (message) + { + case "1": + attachment = GetInlineAttachment(); + break; + case "2": + attachment = await GetUploadedAttachmentAsync(replyMessage.ServiceUrl, replyMessage.Conversation.Id); + break; + case "3": + attachment = GetInternetAttachment(); + break; + } + // The Attachments property allows you to send and receive images and other content - replyMessage.Attachments = new List() + replyMessage.Attachments = new List { attachment }; + + await context.PostAsync(replyMessage); + + await this.DisplayOptionsAsync(context); + } + + private static Attachment GetInlineAttachment() + { + var imagePath = HttpContext.Current.Server.MapPath("~/images/small-image.png"); + + var imageData = Convert.ToBase64String(File.ReadAllBytes(imagePath)); + + return new Attachment { - new Attachment() - { - ContentUrl = "https://docs.botframework.com/en-us/images/faq-overview/botframework_overview_july.png", - ContentType = "image/png", - Name = "BotFrameworkOverview.png" - } + Name = "small-image.png", + ContentType = "image/png", + ContentUrl = $"data:image/png;base64,{imageData}" }; + } - await context.PostAsync(replyMessage); + private static async Task GetUploadedAttachmentAsync(string serviceUrl, string conversationId) + { + var imagePath = HttpContext.Current.Server.MapPath("~/images/big-image.png"); - context.Wait(this.MessageReceivedAsync); + using (var connector = new ConnectorClient(new Uri(serviceUrl))) + { + var attachments = new Attachments(connector); + var response = await attachments.Client.Conversations.UploadAttachmentAsync( + conversationId, + new AttachmentData + { + Name = "big-image.png", + OriginalBase64 = File.ReadAllBytes(imagePath), + Type = "image/png" + }); + + var attachmentUri = attachments.GetAttachmentUri(response.Id); + + // Typo bug in current assembly version '.Replace("{vieWId}", Uri.EscapeDataString(viewId))'. + // TODO: remove this line when replacement Bug is fixed on future releases. PR: https://github.com/Microsoft/BotBuilder/pull/2079 + attachmentUri = attachmentUri.Replace("{viewId}", "original"); + + return new Attachment + { + Name = "big-image.png", + ContentType = "image/png", + ContentUrl = attachmentUri + }; + } + } + + private static Attachment GetInternetAttachment() + { + return new Attachment + { + Name = "BotFrameworkOverview.png", + ContentType = "image/png", + ContentUrl = "https://docs.botframework.com/en-us/images/faq-overview/botframework_overview_july.png" + }; } } } \ No newline at end of file diff --git a/CSharp/core-SendAttachment/images/big-image.png b/CSharp/core-SendAttachment/images/big-image.png new file mode 100644 index 0000000000..4b52a6a17a Binary files /dev/null and b/CSharp/core-SendAttachment/images/big-image.png differ diff --git a/CSharp/core-SendAttachment/images/outcome-emulator.png b/CSharp/core-SendAttachment/images/outcome-emulator.png index caef27eb31..5ab8879f12 100644 Binary files a/CSharp/core-SendAttachment/images/outcome-emulator.png and b/CSharp/core-SendAttachment/images/outcome-emulator.png differ diff --git a/CSharp/core-SendAttachment/images/outcome-facebook.png b/CSharp/core-SendAttachment/images/outcome-facebook.png index 1f5afcdfa0..cb4a3ac2d9 100644 Binary files a/CSharp/core-SendAttachment/images/outcome-facebook.png and b/CSharp/core-SendAttachment/images/outcome-facebook.png differ diff --git a/CSharp/core-SendAttachment/images/outcome-skype.png b/CSharp/core-SendAttachment/images/outcome-skype.png index 54c04358dc..b0ead5d247 100644 Binary files a/CSharp/core-SendAttachment/images/outcome-skype.png and b/CSharp/core-SendAttachment/images/outcome-skype.png differ diff --git a/CSharp/core-SendAttachment/images/small-image.png b/CSharp/core-SendAttachment/images/small-image.png new file mode 100644 index 0000000000..703bbbcfa9 Binary files /dev/null and b/CSharp/core-SendAttachment/images/small-image.png differ