您现在访问的是微软AZURE全球版技术文档网站,若需要访问由世纪互联运营的MICROSOFT AZURE中国区技术文档网站,请访问 https://docs.azure.cn.

向消息添加媒体Add media to messages

适用范围:yesSDK v4 noSDK v3 APPLIES TO: yesSDK v4 no SDK v3

用户与机器人之间的消息交换可以包含媒体附件,例如图像、视频、音频和文件。Messages exchanged between user and bot can contain media attachments, such as images, video, audio, and files. Bot Framework SDK 支持向用户发送富消息的任务。The Bot Framework SDK supports the task of sending rich messages to the user. 若要确定某个通道(Facebook、Slack 等)支持的富消息的类型,请查看该通道的文档,了解存在哪些限制。To determine the type of rich messages a channel (Facebook, Slack, etc.) supports, consult the channel's documentation for information about limitations.

先决条件Prerequisites

发送附件Send attachments

若要向用户发送内容(如图像或视频),可以向消息添加附件或附件列表。To send the user content like an image or a video, you can add an attachment or list of attachments to a message.

有关可用卡的示例,请参阅设计用户体验See design user experience for examples of available cards.

Activity 对象的 Attachments 属性包含一组 Attachment 对象,表示媒体附件和附加到消息的富卡。The Attachments property of the Activity object contains an array of Attachment objects that represent the media attachments and rich cards attached to the message. 若要向消息添加媒体附件,请为 reply 活动创建 Attachment 对象(以前是在活动外使用 CreateReply() 创建的),并设置 ContentTypeContentUrlName 属性。To add a media attachment to a message, create an Attachment object for the reply activity (that was created off the activity with CreateReply()) and set the ContentType, ContentUrl, and Name properties.

此处显示的源代码基于处理附件示例。The source code shown here is based on the Handling Attachments sample.

若要创建回复消息,请定义文本,然后设置附件。To create the reply message, define the text and then set up the attachments. 将附件分配到回复的操作对于每个附加类型来说是相同的,但不同附件的设置和定义方法是不同的,如以下代码片段所示。Assigning the attachments to the reply is the same for each attachment type, however the various attachments are set up and defined differently, as seen in the following snippets. 以下代码为内联附件设置回复:The code below is setting up the reply for an inline attachment:

Bots/AttachmentsBot.csBots/AttachmentsBot.cs

reply = MessageFactory.Text("This is an inline attachment.");
reply.Attachments = new List<Attachment>() { GetInlineAttachment() };

接下来,我们查看附件的类型。Next, we look at the types of attachments. 首先是内联附件:First is an inline attachment:

Bots/AttachmentsBot.csBots/AttachmentsBot.cs

private static Attachment GetInlineAttachment()
{
    var imagePath = Path.Combine(Environment.CurrentDirectory, @"Resources\architecture-resize.png");
    var imageData = Convert.ToBase64String(File.ReadAllBytes(imagePath));

    return new Attachment
    {
        Name = @"Resources\architecture-resize.png",
        ContentType = "image/png",
        ContentUrl = $"data:image/png;base64,{imageData}",
    };
}

然后是上传的附件:Then, an uploaded attachment:

Bots/AttachmentsBot.csBots/AttachmentsBot.cs

private static async Task<Attachment> GetUploadedAttachmentAsync(ITurnContext turnContext, string serviceUrl, string conversationId, CancellationToken cancellationToken)
{
    if (string.IsNullOrWhiteSpace(serviceUrl))
    {
        throw new ArgumentNullException(nameof(serviceUrl));
    }

    if (string.IsNullOrWhiteSpace(conversationId))
    {
        throw new ArgumentNullException(nameof(conversationId));
    }

    var imagePath = Path.Combine(Environment.CurrentDirectory, @"Resources\architecture-resize.png");

    var connector = turnContext.TurnState.Get<IConnectorClient>() as ConnectorClient;
    var attachments = new Attachments(connector);
    var response = await attachments.Client.Conversations.UploadAttachmentAsync(
        conversationId,
        new AttachmentData
        {
            Name = @"Resources\architecture-resize.png",
            OriginalBase64 = File.ReadAllBytes(imagePath),
            Type = "image/png",
        },
        cancellationToken);

    var attachmentUri = attachments.GetAttachmentUri(response.Id);

    return new Attachment
    {
        Name = @"Resources\architecture-resize.png",
        ContentType = "image/png",
        ContentUrl = attachmentUri,
    };

最后是 Internet 附件:Lastly, an internet attachment:

Bots/AttachmentsBot.csBots/AttachmentsBot.cs

// Creates an <see cref="Attachment"/> to be sent from the bot to the user from a HTTP URL.
private static Attachment GetInternetAttachment()
{
    // ContentUrl must be HTTPS.
    return new Attachment
    {
        Name = @"Resources\architecture-resize.png",
        ContentType = "image/png",
        ContentUrl = "https://docs.microsoft.com/en-us/bot-framework/media/how-it-works/architecture-resize.png",
    };

如果附件是图像、音频或视频,连接器服务将以一种让通道可以在会话中呈现附件的方式将此附件数据传递给通道。If an attachment is an image, audio, or video, the Connector service will communicate attachment data to the channel in a way that enables the channel to render that attachment within the conversation. 如果附件是文件,该文件 URL 将呈现为该会话中的超链接。If the attachment is a file, the file URL will be rendered as a hyperlink within the conversation.

发送英雄卡Send a hero card

除了简单的图像或视频附件,还可以附加英雄卡 ,这样可以将图像和按钮组合在一个对象中,并将其发送给用户。Besides simple image or video attachments, you can attach a hero card, which allows you to combine images and buttons in one object, and send them to the user. 大多数文本字段支持 Markdown,但支持可能因通道而异。Markdown is supported for most text fields, but support may vary by channel.

若要撰写含英雄卡和按钮的消息,可以将 HeroCard 附加到消息中。To compose a message with a hero card and button, you can attach a HeroCard to a message.

此处显示的源代码基于处理附件示例。The source code shown here is based on the Handling Attachments sample.

Bots/AttachmentsBot.csBots/AttachmentsBot.cs

private static async Task DisplayOptionsAsync(ITurnContext turnContext, CancellationToken cancellationToken)
{
    // Create a HeroCard with options for the user to interact with the bot.
    var card = new HeroCard
    {
        Text = "You can upload an image or select one of the following choices",
        Buttons = new List<CardAction>
        {
            // Note that some channels require different values to be used in order to get buttons to display text.
            // In this code the emulator is accounted for with the 'title' parameter, but in other channels you may
            // need to provide a value for other parameters like 'text' or 'displayText'.
            new CardAction(ActionTypes.ImBack, title: "1. Inline Attachment", value: "1"),
            new CardAction(ActionTypes.ImBack, title: "2. Internet Attachment", value: "2"),
            new CardAction(ActionTypes.ImBack, title: "3. Uploaded Attachment", value: "3"),
        },
    };

    var reply = MessageFactory.Attachment(card.ToAttachment());
    await turnContext.SendActivityAsync(reply, cancellationToken);
}

处理资讯卡中的事件Process events within rich cards

若要处理富卡中的事件,请使用 card action 对象指定当用户单击按钮或点击卡的某个部分时应发生的情况。To process events within rich cards, use card action objects to specify what should happen when the user clicks a button or taps a section of the card. 每个卡片操作都有类型和值。 Each card action has a type and value.

若要正常运行,请为卡上的每个可点击项目指定一种操作类型。To function correctly, assign an action type to each clickable item on the card. 下表列出并描述了可用的操作类型以及应该存在于相关联的值属性中的内容。This table lists and describes the available action types and what should be in the associated value property.

类型Type 说明Description Value
openUrlopenUrl 在内置浏览器中打开一个 URL。Opens a URL in the built-in browser. 要打开的 URL。The URL to open.
imBackimBack 向机器人发送一条消息,并在聊天中发布一个可见的响应。Sends a message to the bot, and posts a visible response in the chat. 要发送的消息文本。Text of the message to send.
postBackpostBack 向机器人发送一条消息,可能不在聊天中发布一个可见的响应。Sends a message to the bot, and may not post a visible response in the chat. 要发送的消息文本。Text of the message to send.
callcall 拨打电话。Initiates a phone call. 电话呼叫的目标采用 tel:123123123123 格式。Destination for the phone call in this format: tel:123123123123.
playAudioplayAudio 播放音频。Plays audio. 要播放的音频的 URL。The URL of the audio to play.
playVideoplayVideo 播放视频。Plays a video. 要播放的视频的 URL。The URL of video to play.
showImageshowImage 显示图像。Displays an image. 要显示的图像的 URL。The URL of the image to display.
downloadFiledownloadFile 下载一个文件。Downloads a file. 要下载的文件的 URL。The URL of the file to download.
signinsignin 启动 OAuth 登录过程。Initiates an OAuth signin process. 要启动的 OAuth 流的 URL。The URL of the OAuth flow to initiate.

使用各种事件类型的英雄卡Hero card using various event types

以下代码显示了使用各种富卡事件的示例。The following code shows examples using various rich card events.

如需所有可用卡片的示例,请参阅 C# 卡片示例For examples of all the available cards, see the C# cards sample.

Cards.csCards.cs


public static HeroCard GetHeroCard()
{
    var heroCard = new HeroCard
    {
        Title = "BotFramework Hero Card",
        Subtitle = "Microsoft Bot Framework",
        Text = "Build and connect intelligent bots to interact with your users naturally wherever they are," +
               " from text/sms to Skype, Slack, Office 365 mail and other popular services.",
        Images = new List<CardImage> { new CardImage("https://sec.ch9.ms/ch9/7ff5/e07cfef0-aa3b-40bb-9baa-7c9ef8ff7ff5/buildreactionbotframework_960.jpg") },
        Buttons = new List<CardAction> { new CardAction(ActionTypes.OpenUrl, "Get Started", value: "https://docs.microsoft.com/bot-framework") },
    };

    return heroCard;

Cards.csCards.cs


public static SigninCard GetSigninCard()
{
    var signinCard = new SigninCard
    {
        Text = "BotFramework Sign-in Card",
        Buttons = new List<CardAction> { new CardAction(ActionTypes.Signin, "Sign-in", value: "https://login.microsoftonline.com/") },
    };

    return signinCard;

发送自适应卡Send an Adaptive Card

自适应卡片和 MessageFactory 用于发送丰富消息(包括文本、图像、视频、音频和文件)来与用户通信。Adaptive Card and MessageFactory are used to send rich messages including texts, images, video, audio and files to communicate with users. 但是,两者存在一些差异。However, there are some differences between them.

首先,只有某些通道支持自适应卡片,有些通道可能只是部分支持自适应卡片。First, only some channels support Adaptive Cards, and channels that do support it might partially support Adaptive Cards. 例如,如果在 Facebook 中发送自适应卡片,则按钮可能不起作用,而文本和图像可正常使用。For example, if you send an Adaptive Card in Facebook, the buttons won't work while texts and images work well. MessageFactory 只是 Bot Framework SDK 中的一个帮助器类,可以自动完成创建步骤,并受大多数通道的支持。MessageFactory is just a helper class within the Bot Framework SDK to automate creation steps for you, and supported by most channels.

其次,自适应卡片以卡片格式传送消息,通道确定卡片的布局。Second, Adaptive Card delivers messages in the card format, and the channel determines the layout of the card. MessageFactory 传送消息的格式取决于通道,除非在附件中包含自适应卡片,否则不一定采用卡片格式。The format of messages MessageFactory delivers depends on the channel, and is not necessarily in the card format unless Adaptive Card is part of the attachment.

若要查找有关自适应卡片通道支持的最新信息,请参阅自适应卡片设计器To find the latest information on Adaptive Card channel support, see the Adaptive Cards Designer.

若要使用自适应卡,请务必添加 AdaptiveCards NuGet 包。To use adaptive cards, be sure to add the AdaptiveCards NuGet package.

备注

应该使用机器人将使用的通道测试此功能,以确定这些通道是否支持自适应卡。You should test this feature with the channels your bot will use to determine whether those channels support adaptive cards.

若要使用自适应卡片,请务必添加 AdaptiveCards NuGet 包。To use Adaptive Cards, be sure to add the AdaptiveCards NuGet package.

此处显示的源代码基于使用卡片示例。The source code shown here is based on the Using cards sample.

Cards.csCards.cs

public static Attachment CreateAdaptiveCardAttachment()
{
    // combine path for cross platform support
    var paths = new[] { ".", "Resources", "adaptiveCard.json" };
    var adaptiveCardJson = File.ReadAllText(Path.Combine(paths));

    var adaptiveCardAttachment = new Attachment()
    {
        ContentType = "application/vnd.microsoft.card.adaptive",
        Content = JsonConvert.DeserializeObject(adaptiveCardJson),
    };

    return adaptiveCardAttachment;

消息还可以包括采用轮播布局的多个附件,此布局并排放置附件并允许用户滚动。Messages can also include multiple attachments in a carousel layout, which places the attachments side by side and allows the user to scroll across.

此处显示的源代码基于卡片示例The source code shown here is based on the Cards sample.

首先,创建回复并将附件定义为列表。First, create the reply and define the attachments as a list.

Dialogs/MainDialog.csDialogs/MainDialog.cs

// Cards are sent as Attachments in the Bot Framework.
// So we need to create a list of attachments for the reply activity.
var attachments = new List<Attachment>();

// Reply to the activity we received with an activity.
var reply = MessageFactory.Attachment(attachments);

然后,添加附件。Then add the attachments. 在这里,我们一次添加一个附件,但你可以根据自己的偏好来添加卡片,对列表随意进行操作。Here we're adding them one at a time, but feel free to manipulate the list to add the cards however you prefer.

Dialogs/MainDialog.csDialogs/MainDialog.cs

// Display a carousel of all the rich card types.
reply.AttachmentLayout = AttachmentLayoutTypes.Carousel;
reply.Attachments.Add(Cards.CreateAdaptiveCardAttachment());
reply.Attachments.Add(Cards.GetAnimationCard().ToAttachment());
reply.Attachments.Add(Cards.GetAudioCard().ToAttachment());
reply.Attachments.Add(Cards.GetHeroCard().ToAttachment());
reply.Attachments.Add(Cards.GetReceiptCard().ToAttachment());
reply.Attachments.Add(Cards.GetSigninCard().ToAttachment());
reply.Attachments.Add(Cards.GetThumbnailCard().ToAttachment());
reply.Attachments.Add(Cards.GetVideoCard().ToAttachment());

添加附件以后,即可发送回复,就像发送任何其他内容一样。Once the attachments are added, you can send the reply just like any other.

Dialogs/MainDialog.csDialogs/MainDialog.cs

// Send the card(s) to the user as an attachment to the activity
await stepContext.Context.SendActivityAsync(reply, cancellationToken);

其他资源Additional resources

有关可用卡的示例,请参阅设计用户体验See design user experience for examples of available cards.

有关架构的详细信息,请参阅 Bot Framework 卡片架构以及“Bot Framework 活动架构”的“消息活动”部分For detailed information on the schema, see the Bot Framework card schema and the message activity section of the Bot Framework Activity schema.

用于处理 Adaptive Card 输入的代码示例Code sample for processing Adaptive Card input

此代码示例显示了在机器人对话类中使用 Adaptive Card 输入的一种方法。This sample code shows one way to use Adaptive Card inputs within a bot dialog class. 它通过验证来自响应客户端的文本字段中收到的输入来扩展当前的 06.using-cards 示例。It extends the current sample 06.using-cards by validating the input received in the text field from the responding client. 首先,在 resources 文件夹中的 adaptive card.json 的最后一个括号之前添加以下代码,将文本输入和按钮功能添加到现有的自适应卡:We first added text input and button functionality to the existing adaptive card by adding the following code just before the final bracket of adaptiveCard.json, found in the resources folder:

...
  "actions": [
    {
      "type": "Action.ShowCard",
      "title": "Text",
      "card": {
      "type": "AdaptiveCard",
      "body": [
        {
          "type": "Input.Text",
          "id": "text",
          "isMultiline": true,
          "placeholder": "Enter your comment"
        }
      ],
      "actions": [
        {
          "type": "Action.Submit",
          "title": "OK"
        }
      ]
    }
  }
]

请注意,输入字段标记为“text”,因此自适应卡会将注释文本数据附加为 Value.[text.]Note that the input field is labeled "text" so our adaptive card will attach comment text data as Value.[text.]

验证器使用 Newtonsoft.json 首先将其转换为 JObject,然后创建一个经过剪裁的文本字符串进行比较。Our validator uses Newtonsoft.json to first convert this to a JObject, and then create a trimmed text string for comparison. 因此,请添加:So add:

using System;
using System.Linq;
using Newtonsoft.Json.Linq;

到 MainDialog.cs,并安装 Newtonsoft.Json 的最新稳定 NuGet 包。to MainDialog.cs and install the latest stable nuget package of Newtonsoft.Json. 在验证程序代码中,我们将逻辑流添加到代码注释中。In the validator code we added the logic flow into the code comments. 此 choicevalidator() 代码放置于右大括号后的 06.using-cards 示例中,用于公开声明 MainDialog:This ChoiceValidator() code is placed into the 06.using-cards sample just after the closed brace public for declaration of MainDialog:

private async Task ChoiceValidator(
    PromptValidatorContext promptContext,
    CancellationToken cancellationToken)
{
    // Retrieves Adaptive Card comment text as JObject.
    // looks for JObject field "text" and converts that input into a trimmed text string.
    var jobject = promptContext.Context.Activity.Value as JObject;
    var jtoken = jobject?["text"];
    var text = jtoken?.Value().Trim();

    // Logic: 1. if succeeded = true, just return promptContext
    //        2. if false, see if JObject contained Adaptive Card input.
    //               No = (bad input) return promptContext
    //               Yes = update Value field with JObject text string, return "true".
    if (!promptContext.Recognized.Succeeded && text != null)
    {
        var choice = promptContext.Options.Choices.FirstOrDefault(
        c => c.Value.Equals(text, StringComparison.InvariantCultureIgnoreCase));
        if (choice != null)
        {
            promptContext.Recognized.Value = new FoundChoice
            {
                Value = choice.Value,
            };
            return true;
        }
    }
    return promptContext.Recognized.Succeeded;
}

在上面的 MainDialog 声明更改中:Now above in the MainDialog declaration change:

// Define the main dialog and its related components.
AddDialog(new ChoicePrompt(nameof(ChoicePrompt)));

修改为:to:

// Define the main dialog and its related components.
AddDialog(new ChoicePrompt(nameof(ChoicePrompt), ChoiceValidator));

每当创建新的 ChoicePrompt 时,这会调用验证器来查找自适应卡输入。This will invoke your validator to look for Adaptive Card input each time a new ChoicePrompt is created.

若要测试代码,显示自适应卡后,单击“文本”按钮,输入有效的选择内容(如“英雄卡”),然后单击“确定”按钮。To test your code, once an Adaptive Card has been displayed, Click the "Text" button, Enter a valid selection such as "Hero Card" and click the "OK" button.

测试自适应卡

  1. 第一个输入将用于启动新的对话。The first input will be used to start a new dialog.
  2. 再次单击“确定”按钮,此输入将用于选择新的卡。Click the "OK" button again and this input will be used to select a new card.

后续步骤Next steps