使用 bot 发送和接收文件Send and receive files with a bot

重要

本节中的代码示例基于 Bot 框架 SDK 的4.6 和更高版本。The code samples in this section are based on 4.6 and later versions of the Bot Framework SDK. 如果您正在查找早期版本的文档,请参阅文档的资源文件夹中的 " bot-V3 SDK " 一节。If you're looking for documentation for earlier versions, see the Bots - v3 SDK section in the Resources folder of the documentation.

本文介绍如何与你的 bot 在一对一聊天中与用户交换文件。This article describe how to exchange files with a user in a one-to-one chat with your bot. 您不能使用此功能在团队或组聊天中交换文件。You cannot use this functionality to exchange files in a team or group chat.

有两种方法可供选择:There are two approaches to choose from:

  1. 支持所有三个作用域的Microsoft Graph api personalchannel、和groupchatMicrosoft Graph APIs, which supports all three scopes: personal, channel, and groupchat
  2. 仅支持personal作用域的团队 bot apiTeams bot APIs, which only support the personal scope.

备注

不支持在移动设备上向 bot 发送和接收文件。Sending and receiving files to bots on mobile devices is not supported.

使用 Microsoft Graph ApiUsing the Microsoft Graph APIs

您可以使用适用于OneDrive 和 SharePoint的 Microsoft Graph api,使用包含卡片附件的邮件发布引用现有 SharePoint 文件的邮件。You can post messages with card attachments referencing existing SharePoint files using the Microsoft Graph APIs for OneDrive and SharePoint. 使用图形 Api 需要通过标准 OAuth 2.0 流获取经过身份验证的访问权限,以:Using the Graph APIs requires obtaining authenticated access, through the standard OAuth 2.0 flow, to:

  • 用户的 OneDrive 文件夹(用于personalgroupchat文件)。A user's OneDrive folder (for personal and groupchat files).
  • 或团队频道中的文件(对于channel文件)。Or to the files in a team's channels (for channel files). 此方法适用于所有团队作用域。This method works in all Teams scopes.

使用团队 Bot ApiUsing the Teams Bot APIs

你的 bot 可以使用团队 Api 直接在personal上下文中的用户发送和接收文件(也称为个人聊天)。Your bot can directly send and receive files with users in the personal context, also known as personal chats, using Teams APIs. 这使您可以实现费用报告、图像识别、文件存档、电子签名以及涉及直接操作文件内容的其他方案等应用场景。This lets you implement scenarios such expense reporting, image recognition, file archival, e-signatures, and other scenarios involving direct manipulation of file content. 在团队中共享的文件通常显示为卡片,并允许进行丰富的应用程序内查看。Files shared in Teams typically appear as cards, and allow rich in-app viewing. API 作为Microsoft 团队 Bot 平台的一部分提供。The API is provided as part of the Microsoft Teams Bot Platform.

备注

此方法仅在personal上下文中有效。This method works only in the personal context. 它在channelgroupchat上下文中不起作用。It does not work in the channel or groupchat context.

将你的 bot 配置为支持文件Configure your bot to support files

若要在你的 bot 中发送和接收文件,您supportsFiles必须将清单中的true属性设置为。To send and receive files in your bot, you must set the supportsFiles property in the manifest to true. 此属性在清单参考的 " bot " 部分中进行描述。This property is described in the bots section of the Manifest reference.

设置如下所示: "supportsFiles": trueThe setting looks like this: "supportsFiles": true.

用户接受文件上传时调用活动Invoke activity when the user accepts the file upload

在同意上载后,文件将上传到用户的OneDrive存储。The file is uploaded to the user's OneDrive storage after the consent to upload is issued. Bot 将收到包含文件元数据的邮件活动,如名称和内容 URL。The bot will receive a message activity which contains file metadata, such as its name and the content URL. 请按以下步骤操作:Follow these steps:

  1. 向用户发送一封邮件,请求写入该文件的权限。Send a message to the user requesting permission to write the file. 此邮件必须包含要FileConsentCard上载的文件的名称附件。This message must contain a FileConsentCard attachment with the name of the file to be uploaded.

    bot 文件上传权限卡片

  2. 如果用户接受文件上传,你的 bot 将收到带有位置 URL 的调用活动。If the user accepts the file upload, your bot will receive an Invoke activity with a location URL.

  3. 若要转移文件,你的 bot 将HTTP POST直接执行提供的位置 URL 中的。To transfer the file, your bot performs an HTTP POST directly into the provided location URL.

  4. (可选)如果您不希望允许用户接受对同一文件的更多上载,则可以删除原始同意卡。Optionally, you can remove the original consent card if you do not want to allow the user to accept further uploads of the same file.

下面的示例展示了你的 bot 将接收的 invoke 活动的 abridged 版本:The following example shows an abridged version of the invoke activity that your bot will receive:

{
    "name": "fileConsent/invoke",
    "type": "invoke",
    "timestamp": "2019-10-24T20:22:37.875Z",
    "localTimestamp": "2019-10-24T13:22:37.875-07:00",
    "id": "f:8805947989118514037",

    ...

    "value": {
        "type": "fileUpload",
        "action": "accept",
        "context": {
            "filename": "teams-logo.png"
        },
        "uploadInfo": {
            "contentUrl": "https://contoso.sharepoint.com//personal/<user alias>/Documents/Applications/TeamsFilesBot/teams-logo.png",
            "name": "teams-logo.png",
            "uploadUrl": "https://contoso.sharepoint.com//personal/<user alias>/_api/v2.0/drive/items/01FED6KHQXVVCUCI6XVJCZZMU2WMUSA6JS/uploadSession?guid=<GUID>",
            "uniqueId": "<Unique ID>",
            "fileType": "png"
        }
    },

    "locale": "en-US"
}

下表描述了附件的内容属性:The following table describes the content properties of the attachment:

属性Property 用途Purpose
uploadUrl 用于上传文件内容的 OneDrive URL。OneDrive URL for uploading the content of the file.
uniqueId 唯一的文件 ID。Unique file ID. 这将是 OneDrive 驱动器项目 ID。This will be the OneDrive drive item ID.
fileType 文件扩展名类型,如 pdf 或 * png * *。File extension type, such as pdf or *png**.

作为一种最佳做法,您应通过向用户发送回邮件来确认文件上载。As a best practice, you should acknowledge the file upload by sending back a message to the user.

如果用户拒绝该文件,则你的 bot 将收到以下事件,其中包含相同的总体活动名称:If the user declines the file, your bot will receive the following event, with the same overall activity name:

{
  "name": "fileConsent/invoke",
  "value": {
    "type": "fileUpload",
    "action": "decline",
    "context": {
      ...
    }
  }
}

通知用户有关上载的文件Notifying the user about an uploaded file

将文件上传到用户的 OneDrive 后,应向用户发送一封确认消息。After uploading a file to the user's OneDrive, you should send a confirmation message to the user. 此邮件应包含用户FileCard可单击的附件,可在 OneDrive 中进行预览、在 OneDrive 中打开或在本地下载。This message should contain a FileCard attachment that the user can click on, either to preview it, open it in OneDrive, or download locally. 示例如下。The following is an example.

{
  "attachments": [{
    "contentType": "application/vnd.microsoft.teams.card.file.info",
    "contentUrl": "https://contoso.sharepoint.com/personal/johnadams_contoso_com/Documents/Applications/file_example.txt",
    "name": "file_example.txt",
    "content": {
      "uniqueId": "<unique ID>",
      "fileType": "png",
    }
  }]
}

下表描述了附件的内容属性:The following table describes the content properties of the attachment:

属性Property 用途Purpose
uniqueId OneDrive/SharePoint 驱动器项目 ID。OneDrive/SharePoint drive item ID.
fileType 文件类型,如 pdf 或 .docx。File type, such as pdf or docx.

使用 Bot 框架 SDK 的示例Example using the Bot Framework SDK

下面的示例展示了如何在 bot 的对话框中处理文件上载和向用户发送文件同意请求。The following example shows how you can handle file uploads and send file consent requests to the user in the bot's dialog. 下一步显示的代码段属于可运行的完整示例,可在此位置下载:文件The code snippets shown next, belong to a complete runnable example you can download at this location: FileUpload.

protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
    bool messageWithFileDownloadInfo = turnContext.Activity.Attachments?[0].ContentType == FileDownloadInfo.ContentType;
    if (messageWithFileDownloadInfo)
    {
        var file = turnContext.Activity.Attachments[0];
        var fileDownload = JObject.FromObject(file.Content).ToObject<FileDownloadInfo>();

        string filePath = Path.Combine("Files", file.Name);

        var client = _clientFactory.CreateClient();
        var response = await client.GetAsync(fileDownload.DownloadUrl);
        using (var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
        {
            await response.Content.CopyToAsync(fileStream);
        }

        var reply = ((Activity)turnContext.Activity).CreateReply();
        reply.TextFormat = "xml";
        reply.Text = $"Complete downloading <b>{file.Name}</b>";
        await turnContext.SendActivityAsync(reply, cancellationToken);
    }
    else
    {
        string filename = "teams-logo.png";
        string filePath = Path.Combine("Files", filename);
        long fileSize = new FileInfo(filePath).Length;
        await SendFileCardAsync(turnContext, filename, fileSize, cancellationToken);
    }
}

private async Task SendFileCardAsync(ITurnContext turnContext, string filename, long filesize, CancellationToken cancellationToken)
{
    var consentContext = new Dictionary<string, string>
    {
        { "filename", filename },
    };

    var fileCard = new FileConsentCard
    {
        Description = "This is the file I want to send you",
        SizeInBytes = filesize,
        AcceptContext = consentContext,
        DeclineContext = consentContext,
    };

    var asAttachment = new Attachment
    {
        Content = fileCard,
        ContentType = FileConsentCard.ContentType,
        Name = filename,
    };

    var replyActivity = turnContext.Activity.CreateReply();
    replyActivity.Attachments = new List<Attachment>() { asAttachment };

    await turnContext.SendActivityAsync(replyActivity, cancellationToken);
}

protected override async Task OnTeamsFileConsentAcceptAsync(ITurnContext<IInvokeActivity> turnContext, FileConsentCardResponse fileConsentCardResponse, CancellationToken cancellationToken)
{
    try
    {
        JToken context = JObject.FromObject(fileConsentCardResponse.Context);

        string filePath = Path.Combine("Files", context["filename"].ToString());
        long fileSize = new FileInfo(filePath).Length;
        var client = _clientFactory.CreateClient();
        using (var fileStream = File.OpenRead(filePath))
        {
            var fileContent = new StreamContent(fileStream);
            fileContent.Headers.ContentLength = fileSize;
            fileContent.Headers.ContentRange = new ContentRangeHeaderValue(0, fileSize - 1, fileSize);
            await client.PutAsync(fileConsentCardResponse.UploadInfo.UploadUrl, fileContent, cancellationToken);
        }

        await FileUploadCompletedAsync(turnContext, fileConsentCardResponse, cancellationToken);
    }
    catch (Exception e)
    {
        await FileUploadFailedAsync(turnContext, e.ToString(), cancellationToken);
    }
}

protected override async Task OnTeamsFileConsentDeclineAsync(ITurnContext<IInvokeActivity> turnContext, FileConsentCardResponse fileConsentCardResponse, CancellationToken cancellationToken)
{
    JToken context = JObject.FromObject(fileConsentCardResponse.Context);

    var reply = ((Activity)turnContext.Activity).CreateReply();
    reply.TextFormat = "xml";
    reply.Text = $"Declined. We won't upload file <b>{context["filename"]}</b>.";
    await turnContext.SendActivityAsync(reply, cancellationToken);
}