练习 - 从Microsoft Entra受保护的 API 返回产品数据
在本练习中,将更新消息扩展以从自定义 API 检索数据。 基于用户查询从自定义 API 获取数据,并将搜索结果中的数据返回给用户。
安装和配置开发代理
在本练习中,你将使用开发代理,这是一种可以模拟 API 的命令行工具。 当你想要测试应用而无需创建实际 API 时,此功能非常有用。
若要完成本练习,需要安装 最新版本的开发代理 并下载此模块的开发代理预设。
预设使用内存中数据存储(受Microsoft Entra保护)模拟 CRUD (创建、读取、更新、删除) API。 这意味着你可以像在调用需要身份验证的真实 API 一样测试应用。
若要下载预设,请在终端中运行以下命令:
devproxy preset get learn-copilot-me-plugin
获取用户查询值
创建一个方法,该方法按参数的名称获取用户查询值。
在 Visual Studio 和 ProductsPlugin 项目中:
在 “帮助程序” 文件夹中,创建名为 MessageExtensionHelpers.cs
在 文件中,添加以下代码:
using Microsoft.Bot.Schema.Teams; internal class MessageExtensionHelpers { internal static string GetQueryParameterValueByName(IList<MessagingExtensionParameter> parameters, string name) => parameters.FirstOrDefault(p => p.Name == name)?.Value as string ?? string.Empty; }
Save your changes
接下来,更新 OnTeamsMessagingExtensionQueryAsync 方法以使用新的帮助程序方法。
在 “搜索 ”文件夹中,打开 SearchApp.cs
在 OnTeamsMessagingExtensionQueryAsync 方法中,替换以下代码:
var text = query?.Parameters?[0]?.Value as string ?? string.Empty;
跟
var text = MessageExtensionHelpers.GetQueryParameterValueByName(query.Parameters, "ProductName");
将光标移动到 文本 变量,使用
Ctrl + R
、Ctrl + R
并将变量重命名 为名称Save your changes
OnTeamsMessagingExtensionQueryAsync 方法现在应如下所示:
protected override async Task<MessagingExtensionResponse> OnTeamsMessagingExtensionQueryAsync(ITurnContext<IInvokeActivity> turnContext, MessagingExtensionQuery query, CancellationToken cancellationToken)
{
var userTokenClient = turnContext.TurnState.Get<UserTokenClient>();
var tokenResponse = await AuthHelpers.GetToken(userTokenClient, query.State, turnContext.Activity.From.Id, turnContext.Activity.ChannelId, connectionName, cancellationToken);
if (!AuthHelpers.HasToken(tokenResponse))
{
return await AuthHelpers.CreateAuthResponse(userTokenClient, connectionName, (Activity)turnContext.Activity, cancellationToken);
}
var name = MessageExtensionHelpers.GetQueryParameterValueByName(query.Parameters, "ProductName");
var card = await File.ReadAllTextAsync(Path.Combine(".", "Resources", "card.json"), cancellationToken);
var template = new AdaptiveCardTemplate(card);
return new MessagingExtensionResponse
{
ComposeExtension = new MessagingExtensionResult
{
Type = "result",
AttachmentLayout = "list",
Attachments = [
new MessagingExtensionAttachment
{
ContentType = AdaptiveCard.ContentType,
Content = JsonConvert.DeserializeObject(template.Expand(new { title = name })),
Preview = new ThumbnailCard { Title = name }.ToAttachment()
}
]
}
};
}
从自定义 API 获取数据
若要从自定义 API 获取数据,需要在请求的 Authorization 标头中发送访问令牌,并将响应反序列化到表示产品数据的模型中。
首先,创建一个表示从自定义 API 返回的产品数据的模型。
在 Visual Studio 和 ProductsPlugin 项目中:
创建名为 Models 的文件夹
在“模型”文件夹中,创建名为 Product.cs 的新文件
在 文件中,添加以下代码:
using System.Text.Json.Serialization; internal class Product { [JsonPropertyName("productId")] public int Id { get; set; } [JsonPropertyName("imageUrl")] public string ImageUrl { get; set; } [JsonPropertyName("name")] public string Name { get; set; } [JsonPropertyName("category")] public string Category { get; set; } [JsonPropertyName("callVolume")] public int CallVolume { get; set; } [JsonPropertyName("releaseDate")] public string ReleaseDate { get; set; } }
Save your changes
接下来,创建一个服务类,用于从自定义 API 检索产品数据。
创建名为“服务”的文件夹
在 “服务” 文件夹中,创建一个名为 ProductService.cs
在 文件中,添加以下代码:
using System.Net.Http.Headers; internal class ProductsService { private readonly HttpClient _httpClient; private readonly string _baseUri = "https://api.contoso.com/v1/"; internal ProductsService(string token) { _httpClient = new HttpClient(); _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); } internal async Task<Product[]> GetProductsByNameAsync(string name) { var response = await _httpClient.GetAsync($"{_baseUri}products?name={name}"); response.EnsureSuccessStatusCode(); var jsonString = await response.Content.ReadAsStringAsync(); return System.Text.Json.JsonSerializer.Deserialize<Product[]>(jsonString); } }
Save your changes
ProductsService 类包含从自定义 API 获取产品数据的方法。 类构造函数采用访问令牌作为参数,并使用 Authorization 标头中的访问令牌设置 HttpClient 实例。
接下来,更新 OnTeamsMessagingExtensionQueryAsync 方法,以使用 ProductsService 类从自定义 API 获取产品数据。
在 “搜索 ”文件夹中,打开 SearchApp.cs
在 OnTeamsMessagingExtensionQueryAsync 方法中,在 name 变量声明后添加以下代码,以从自定义 API 获取产品数据:
var productService = new ProductsService(tokenResponse.Token); var products = await productService.GetProductsByNameAsync(name);
Save your changes
创建搜索结果
现在,你已拥有产品数据,可以将其包含在返回给用户的搜索结果中。
首先,让我们更新现有的自适应卡片模板以显示产品信息。
在 Visual Studio 和 ProductsPlugin 项目中继续操作:
在“资源”文件夹中,将卡.json重命名为Product.json
在 “资源” 文件夹中,创建一个名为 Product.data.json 的新文件。 此文件包含 Visual Studio 用于生成自适应卡片模板预览的示例数据。
在 文件中,添加以下 JSON:
{ "callVolume": 36, "category": "Enterprise", "imageUrl": "https://raw.githubusercontent.com/SharePoint/sp-dev-provisioning-templates/master/tenant/productsupport/source/Product%20Imagery/Contoso4.png", "name": "Contoso Quad", "productId": 1, "releaseDate": "2019-02-09" }
Save your changes
在 “资源” 文件夹中,打开 Product.json
在 文件中,将内容替换为以下 JSON:
{ "type": "AdaptiveCard", "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", "version": "1.5", "body": [ { "type": "TextBlock", "text": "${name}", "wrap": true, "style": "heading" }, { "type": "TextBlock", "text": "${category}", "wrap": true }, { "type": "Container", "items": [ { "type": "Image", "url": "${imageUrl}", "altText": "${name}" } ], "minHeight": "350px", "verticalContentAlignment": "Center", "horizontalAlignment": "Center" }, { "type": "FactSet", "facts": [ { "title": "Call Volume", "value": "${formatNumber(callVolume,0)}" }, { "title": "Release Date", "value": "${formatDateTime(releaseDate,'dd/MM/yyyy')}" } ] } ] }
Save your changes
自适应卡片模板使用绑定表达式来显示产品信息。 ${name}、${category}、${imageUrl}、${callVolume}和 ${releaseDate} 表达式将替换为产品数据中的相应值。 formatNumber 和 formatDateTime 模板函数用于将 callVolume 和 releaseDate 值分别设置为数字和日期。
请花点时间了解 Visual Studio 中的自适应卡片预览。 预览显示将产品数据绑定到模板时自适应卡片模板的外观。 它使用 Product.data.json 文件中的示例数据来生成预览。
接下来,更新应用清单中的 validDomains 属性以包含 raw.githubusercontent.com 域,以便自适应卡片模板中的图像可以在 Microsoft Teams 中显示。
在 TeamsApp 项目中:
在 appPackage 文件夹中,打开 manifest.json
在 文件中,将 GitHub 域添加到 validDomains 属性:
"validDomains": [ "token.botframework.com", "raw.githubusercontent.com", "${{BOT_DOMAIN}}" ],
Save your changes
接下来,更新 OnTeamsMessagingExtensionQueryAsync 方法,以创建包含产品信息的附件列表。
在 ProductsPlugin 项目中:
在 “搜索 ”文件夹中,打开 SearchApp.cs
将卡.json更新为 Product.json,以反映文件名中的更改。 替换以下代码:
var card = await File.ReadAllTextAsync(Path.Combine(".", "Resources", "card.json"), cancellationToken);
跟
var card = await File.ReadAllTextAsync(Path.Combine(".", "Resources", "Product.json"), cancellationToken);
在 模板 变量声明后添加以下代码,以创建附件列表:
var attachments = products.Select(product => { var content = template.Expand(product); return new MessagingExtensionAttachment { ContentType = AdaptiveCard.ContentType, Content = JsonConvert.DeserializeObject(content), Preview = new ThumbnailCard { Title = product.Name, Subtitle = product.Category, Images = [new() { Url = product.ImageUrl }] }.ToAttachment() }; }).ToList();
更新 return 语句以包含 attachments 变量:
return new MessagingExtensionResponse { ComposeExtension = new MessagingExtensionResult { Type = "result", AttachmentLayout = "list", Attachments = attachments } };
保存更改
创建和更新资源
一切准备就绪后,运行 准备 Teams 应用依赖项 过程以创建新资源并更新现有资源。
在 Visual Studio 中继续:
- 在解决方案资源管理器中,右键单击 TeamsApp 项目
- 展开“Teams 工具包”菜单,选择“准备 Teams 应用依赖项”
- 在“Microsoft 365 帐户”对话框中,选择“继续”
- 在“预配”对话框中,选择“预配”
- 在“Teams 工具包警告”对话框中,选择“预配”
- 在 “Teams 工具包信息 ”对话框中,选择交叉图标以关闭对话框
运行和调试
预配资源后,启动调试会话以测试消息扩展。
首先,启动开发代理以模拟自定义 API。
打开终端窗口
运行以下命令以启动开发代理:
devproxy --config-file "~appFolder/presets/learn-copilot-me-plugin/products-api-config.json"
如果出现提示,请接受证书警告
注意
当开发代理运行时,它充当系统范围的代理。
接下来,在 Visual Studio 中启动调试会话:
若要启动新的调试会话,请按 F5 或从工具栏中选择“ 启动 ”
等待浏览器窗口打开,应用安装对话框显示在 Microsoft Teams Web 客户端中。 如果出现提示,请输入Microsoft 365 帐户凭据。
在应用安装对话框中,选择 “添加”
打开新的或现有的Microsoft Teams 聊天
在邮件撰写区域中,选择 + 以打开应用选取器
在应用列表中,选择 “Contoso 产品 ”以打开消息扩展
在文本框中,输入 mark8
等待搜索完成并显示结果
在结果列表中,选择搜索结果以将卡嵌入撰写邮件框中
返回到 Visual Studio 并从工具栏中选择“ 停止 ”,或按 Shift + F5 停止调试会话。 此外,使用 Ctrl + C 关闭开发代理。