Skip to content

Commit

Permalink
Merge pull request microsoft#57 from southworkscom/csharp-sendattachm…
Browse files Browse the repository at this point in the history
…ent-update

[CSharp] Send Attachment updates
  • Loading branch information
willportnoy authored Jan 23, 2017
2 parents a6e26b6 + 3205509 commit 0d0ad42
Show file tree
Hide file tree
Showing 8 changed files with 234 additions and 23 deletions.
133 changes: 120 additions & 13 deletions CSharp/core-SendAttachment/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<IMessageActivity> argument)
public async Task ProcessSelectedOptionAsync(IDialogContext context, IAwaitable<string> 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<Attachment>()
replyMessage.Attachments = new List<Attachment> { 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<Attachment> 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)

Expand Down
2 changes: 2 additions & 0 deletions CSharp/core-SendAttachment/SendAttachmentBot.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@
<ItemGroup>
<Content Include="default.htm" />
<Content Include="Global.asax" />
<Content Include="images\big-image.png" />
<Content Include="images\small-image.png" />
<Content Include="Web.config">
<SubType>Designer</SubType>
</Content>
Expand Down
122 changes: 112 additions & 10 deletions CSharp/core-SendAttachment/SendAttachmentDialog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<object>
{
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<string, string> options = new Dictionary<string, string>
{
{ "1", ShowInlineAttachment },
{ "2", ShowUploadedAttachment },
{ "3", ShowInternetAttachment }
};

public async Task StartAsync(IDialogContext context)
{
context.Wait(this.MessageReceivedAsync);
}

public virtual async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> argument)
public async virtual Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> 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<string>(
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<string> 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<Attachment>()
replyMessage.Attachments = new List<Attachment> { 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<Attachment> 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"
};
}
}
}
Binary file added CSharp/core-SendAttachment/images/big-image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified CSharp/core-SendAttachment/images/outcome-emulator.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified CSharp/core-SendAttachment/images/outcome-facebook.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified CSharp/core-SendAttachment/images/outcome-skype.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added CSharp/core-SendAttachment/images/small-image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 0d0ad42

Please sign in to comment.