你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

教程:使用变体功能标志运行试验(预览版)

在应用程序上运行试验有助于做出明智的决策,以提高应用的性能和用户体验。 本指南介绍如何在 Azure 应用程序配置存储中设置和执行试验。 了解如何使用 Azure 应用程序配置、Application Insights(预览版)和 拆分试验工作区(预览版)来收集和测量数据。

通过这样做,可以做出数据驱动的决策来改进应用程序。

注意

开始试验之旅的快速方法是运行 Quote of the Day AZD 示例。此存储库提供了一个全面的示例,其中包含 Azure 资源预配和第一个试验,介绍如何将 Azure 应用程序配置与 .NET 应用程序集成以运行试验。

在本教程中,你将了解:

  • 创建变体功能标志
  • 将 Application Insights 资源添加到存储
  • 将拆分试验工作区添加到存储
  • 设置应用以运行试验
  • 启用遥测并在变体功能标志中创建试验
  • 为试验创建指标
  • 获取试验结果

先决条件

创建变体功能标志(预览)

功能标志快速入门中所述,使用两个变体(“关闭”和“打开”)创建名为“问候语”的变体功能标志。

将 Application Insights(预览版)资源连接到配置存储

若要运行试验,首先需要将基于工作区的 Application Insights 资源连接到 Azure 应用程序配置存储。 将此资源连接到 Azure 应用程序配置存储将为试验设置具有遥测源的配置存储。

  1. 在 Azure 应用程序配置存储中,选择“遥测”>“Application Insights (预览版)”。

    Microsoft Azure 门户的屏幕截图,将 Application Insights 添加到存储。

  2. 选择要用作变体功能标志和应用程序的遥测提供程序的 Application Insights 资源,然后选择“保存”。 如果没有 Application Insights 资源,请选择“新建”来创建一个。 有关如何继续的详细信息,请转到创建基于工作区的资源。 然后,返回到“Application Insights (预览版)”,重新加载可用的 Application Insights 资源列表并选择新的 Application Insights 资源。

  3. 通知指示已成功为 Azure 应用程序配置存储更新 Application Insights 资源。

将拆分试验工作区(预览版)连接到存储

若要在 Azure 应用程序配置中运行试验,将使用拆分试验工作区。 按照以下步骤将拆分试验工作区连接到存储。

  1. 在 Azure 应用程序配置存储中,从左侧菜单中选择“试验”>“拆分试验工作区(预览版)”。

    Microsoft Azure 门户的屏幕截图,将拆分试验工作区添加到应用程序配置存储。

  2. 选择“拆分试验工作区”,然后选择“保存”。 如果没有拆分试验工作区,请按照拆分试验工作区快速入门创建一个。

    注意

    拆分试验工作区中选择的数据源必须与上一步中选择的 Application Insights 资源相同。

  3. 通知指示操作成功。

设置应用以运行试验

将 Application Insights(预览版)资源连接到 Azure 应用程序配置存储后,请设置一个应用来运行试验(预览版)。

在此示例中,你将创建一个名为“今日名言”的 ASP.NET Web 应用。 加载该应用后,它会显示一条名言。 用户可以点击心形按钮给它点赞。 为了改进用户参与度,你希望了解个性化问候语消息是否会增加喜欢名言的用户数。 在 Azure 应用程序配置中创建具有两个变体“关闭”和“打开”的“问候语”功能标志。 接收“关闭”变体的用户将看到标准标题。 接收“打开”变体的用户将收到问候消息。 在 Application Insights 中收集和保存用户交互的遥测数据。 使用拆分试验工作区,可以分析试验的有效性。

创建应用并添加用户机密

  1. 打开命令提示符并运行以下代码。 这将使用个人帐户身份验证在 ASP.NET Core 中创建新的 Razor Pages 应用程序,并将其置于名为 QuoteOfTheDay 的输出文件夹中。

    dotnet new razor --auth Individual -o QuoteOfTheDay
    
  2. 在命令提示符中,导航到 QuoteOfTheDay 文件夹,并运行以下命令为应用程序创建用户机密。 此机密保存 Azure 应用程序配置的连接字符串。

    dotnet user-secrets set ConnectionStrings:AppConfiguration "<App Configuration Connection string>"
    
  3. 创建另一个保存 Application Insights 连接字符串的用户机密。

    dotnet user-secrets set ConnectionStrings:AppInsights "<Application Insights Connection string>"
    

更新应用程序代码

  1. QuoteOfTheDay.csproj 中,根据需要添加功能管理和 Azure 应用程序配置 SDK 的最新预览版。

    <PackageReference Include="Microsoft.Azure.AppConfiguration.AspNetCore" Version="8.0.0-preview.2" />
    <PackageReference Include="Microsoft.FeatureManagement.Telemetry.ApplicationInsights" Version="4.0.0-preview3" />
    <PackageReference Include="Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore" Version="4.0.0-preview3" />
    <PackageReference Include="Microsoft.FeatureManagement.AspNetCore" Version="4.0.0-preview3" />
    
  2. Program.cs,在行 var builder = WebApplication.CreateBuilder(args); 下,添加 Azure 应用程序配置提供程序,该提供程序在应用程序启动时从 Azure 拉取配置。 默认情况下,UseFeatureFlags 方法包括所有没有标签的功能标志,并设置缓存过期时间 30 秒。

    builder.Configuration
        .AddAzureAppConfiguration(o =>
        {
            o.Connect(builder.Configuration.GetConnectionString("AppConfiguration"));
    
            o.UseFeatureFlags();
        });
    
  3. Program.cs 中,添加以下 using 语句:

    using Microsoft.ApplicationInsights.AspNetCore.Extensions;
    using Microsoft.ApplicationInsights.Extensibility;
    using Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore;
    
  4. 在调用 builder.Configuration.AddAzureAppConfiguration 的位置下,添加:

    // Add Application Insights telemetry.
    builder.Services.AddApplicationInsightsTelemetry(
        new ApplicationInsightsServiceOptions
        {
            ConnectionString = builder.Configuration.GetConnectionString("AppInsights"),
            EnableAdaptiveSampling = false
        })
        .AddSingleton<ITelemetryInitializer, TargetingTelemetryInitializer>();
    

    此代码片段执行以下操作。

    • 将 Application Insights 遥测客户端添加到应用程序。
    • 添加一个遥测初始化表达式,用于将目标信息追加到传出遥测。
    • 禁用自适应采样。 有关禁用自适应采样的详细信息,请转到故障排除
  5. 在根文件夹中 QuoteOfTheDay 中,创建名为 ExampleTargetingContextAccessor.cs 的新文件。 这将创建一个名为 ExampleTargetingContextAccessor 的新类。 将以下内容粘贴到文件中。

    using Microsoft.FeatureManagement.FeatureFilters;
    
    namespace QuoteOfTheDay
    {
        public class ExampleTargetingContextAccessor : ITargetingContextAccessor
        {
            private const string TargetingContextLookup = "ExampleTargetingContextAccessor.TargetingContext";
            private readonly IHttpContextAccessor _httpContextAccessor;
    
            public ExampleTargetingContextAccessor(IHttpContextAccessor httpContextAccessor)
            {
                _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
            }
    
            public ValueTask<TargetingContext> GetContextAsync()
            {
                HttpContext httpContext = _httpContextAccessor.HttpContext;
                if (httpContext.Items.TryGetValue(TargetingContextLookup, out object value))
                {
                    return new ValueTask<TargetingContext>((TargetingContext)value);
                }
                List<string> groups = new List<string>();
                if (httpContext.User.Identity.Name != null)
                {
                    groups.Add(httpContext.User.Identity.Name.Split("@", StringSplitOptions.None)[1]);
                }
                TargetingContext targetingContext = new TargetingContext
                {
                    UserId = httpContext.User.Identity.Name ?? "guest",
                    Groups = groups
                };
                httpContext.Items[TargetingContextLookup] = targetingContext;
                return new ValueTask<TargetingContext>(targetingContext);
            }
        }
    }
    

    此类声明 FeatureManagement 的目标如何获取用户的上下文。 在这种情况下,它会读取 UserIdhttpContext.User.Identity.Name,并将电子邮件地址的域视为 Group

  6. 导航回 Program.cs 并添加以下 using 语句。

    using Microsoft.FeatureManagement.Telemetry;
    using Microsoft.FeatureManagement;
    using QuoteOfTheDay;
    
  7. 在调用 AddApplicationInsightsTelemetry 的位置下,添加用于处理 Azure 应用程序配置刷新的服务、设置功能管理、配置功能管理目标以及启用功能管理以发布遥测事件。

    builder.Services.AddHttpContextAccessor();
    
    // Add Azure App Configuration and feature management services to the container.
    builder.Services.AddAzureAppConfiguration()
        .AddFeatureManagement()
        .WithTargeting<ExampleTargetingContextAccessor>()
        .AddTelemetryPublisher<ApplicationInsightsTelemetryPublisher>();
    
  8. 在行 var app = builder.Build(); 下,添加一个中间件,以便在适当时触发 Azure 应用程序配置刷新。

    // Use Azure App Configuration middleware for dynamic configuration refresh.
    app.UseAzureAppConfiguration();
    
  9. 在其下,添加以下代码,使 TargetingTelemetryInitializer 能够通过将其存储在 HttpContext 上来访问目标信息。

    // Add TargetingId to HttpContext for telemetry
    app.UseMiddleware<TargetingHttpContextMiddleware>();
    
  10. QuoteOfTheDay>Pages>Shared>_Layout.cshtml 中,在添加 QuoteOfTheDay.styles.css 的位置下,添加以下行来为 font-awesome 版本 5.15.3 添加 css。

    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css">
    
  11. 打开 QuoteOfTheDay>Pages>Index.cshtml.cs 并覆盖名言应用的内容。

    using Microsoft.ApplicationInsights;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.Mvc.RazorPages;
    using Microsoft.FeatureManagement;
    
    namespace QuoteOfTheDay.Pages;
    
    public class Quote
    {
        public string Message { get; set; }
    
        public string Author { get; set; }
    }
    
    public class IndexModel(IVariantFeatureManagerSnapshot featureManager, TelemetryClient telemetryClient) : PageModel
    {
        private readonly IVariantFeatureManagerSnapshot _featureManager = featureManager;
        private readonly TelemetryClient _telemetryClient = telemetryClient;
    
        private Quote[] _quotes = [
            new Quote()
            {
                Message = "You cannot change what you are, only what you do.",
                Author = "Philip Pullman"
            }];
    
        public Quote? Quote { get; set; }
    
        public bool ShowGreeting { get; set; }
    
        public async void OnGet()
        {
            Quote = _quotes[new Random().Next(_quotes.Length)];
    
            Variant variant = await _featureManager.GetVariantAsync("Greeting", HttpContext.RequestAborted);
    
            ShowGreeting = variant.Configuration.Get<bool>();
        }
    
        public IActionResult OnPostHeartQuoteAsync()
        {
            string? userId = User.Identity?.Name;
    
            if (!string.IsNullOrEmpty(userId))
            {
                // Send telemetry to Application Insights
                _telemetryClient.TrackEvent("Like");
    
                return new JsonResult(new { success = true });
            }
            else
            {
                return new JsonResult(new { success = false, error = "User not authenticated" });
            }
        }
    }
    

    PageModel 会选取随机名言,使用 GetVariantAsync 获取当前用户的变体,并将名为“ShowGreeting”的变量设置为变体的值。 PageModel 还处理 Post 请求,调用 _telemetryClient.TrackEvent("Like");,后者会向 Application Insights 发送名为 Like 的事件。 此事件自动绑定到用户和变体,并可由指标跟踪。

  12. 打开 index.cshtml 并覆盖名言应用的内容。

    @page
    @model IndexModel
    @{
        ViewData["Title"] = "Home page";
        ViewData["Username"] = User.Identity.Name;
    }
    
    <style>
        body {
            font-family: Arial, sans-serif;
            background-color: #f4f4f4;
            color: #333;
        }
    
        .quote-container {
            background-color: #fff;
            margin: 2em auto;
            padding: 2em;
            border-radius: 8px;
            max-width: 750px;
            box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.2);
            display: flex;
            justify-content: space-between;
            align-items: start;
            position: relative;
        }
    
        .vote-container {
            position: absolute;
            top: 10px;
            right: 10px;
            display: flex;
            gap: 0em;
        }
    
        .vote-container .btn {
            background-color: #ffffff; /* White background */
            border-color: #ffffff; /* Light blue border */
            color: #333
        }
    
        .vote-container .btn:focus {
            outline: none;
            box-shadow: none;
        }
    
        .vote-container .btn:hover {
            background-color: #F0F0F0; /* Light gray background */
        }
    
        .greeting-content {
            font-family: 'Georgia', serif; /* More artistic font */
        }
    
        .quote-content p.quote {
            font-size: 2em; /* Bigger font size */
            font-family: 'Georgia', serif; /* More artistic font */
            font-style: italic; /* Italic font */
            color: #4EC2F7; /* Medium-light blue color */
        }
    </style>
    
    <div class="quote-container">
        <div class="quote-content">
            @if (Model.ShowGreeting)
            {
                <h3 class="greeting-content">Hi <b>@User.Identity.Name</b>, hope this makes your day!</h3>
            }
            else
            {
                <h3 class="greeting-content">Quote of the day</h3>
            }
            <br />
            <p class="quote">“@Model.Quote.Message”</p>
            <p>- <b>@Model.Quote.Author</b></p>
        </div>
    
        <div class="vote-container">
            <button class="btn btn-primary" onclick="heartClicked(this)">
                <i class="far fa-heart"></i> <!-- Heart icon -->
            </button>
        </div>
    
        <form action="/" method="post">
            @Html.AntiForgeryToken()
        </form>
    </div>
    
    <script>
        function heartClicked(button) {
            var icon = button.querySelector('i');
            icon.classList.toggle('far');
            icon.classList.toggle('fas');
    
            // If the quote is hearted
            if (icon.classList.contains('fas')) {
                // Send a request to the server to save the vote
                fetch('/Index?handler=HeartQuote', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                        'RequestVerificationToken': document.querySelector('input[name="__RequestVerificationToken"]').value
                    }
                });
            }
        }
    </script>
    

    此代码对应于 UI,显示 QuoteOfTheDay 并使用名言上的心形操作进行处理。 它使用前面提到的 Model.ShowGreeting 值向不同用户显示不同的内容,具体取决于其变体。

生成并运行应用

  1. 在命令提示符下,在 QuoteOfTheDay 文件夹中,运行:dotnet build

  2. 运行:dotnet run --launch-profile https

  3. 在应用程序的输出中查找格式为 Now listening on: https://localhost:{port} 的消息。 导航到浏览器中包含的链接。

  4. 查看正在运行的应用程序后,选择右上角的“注册”以注册新用户。

    “今日名言”应用的屏幕截图,其中显示了“注册”。

  5. 注册名为 user@contoso.com 的新用户。 密码必须至少有 6 个字符,并且必须包含数字和特殊字符。

  6. 在输入用户信息后,选择“单击此处验证电子邮件”。

  7. 注册名为 userb@contoso.com 的第二个用户,输入另一个密码,并验证第二封电子邮件。

    注意

    对于本教程而言,准确使用这些名称非常重要。 只要该功能已按预期进行配置,两个用户应会看到不同的变体。

  8. 选择右上角的“登录”以 userb (userb@contoso.com) 身份登录。

    “今日名言”应用的屏幕截图,显示**登录**。

  9. 登录后,应会看到 userb@contoso.com 在查看应用时看到一条特殊消息。

    “今日名言”应用的屏幕截图,为用户显示了一条特殊消息。

    userb@contoso.com 是唯一看到特殊消息的用户。

启用遥测并在变体功能标志中创建试验

按照以下步骤在变体功能标志中启用遥测并创建试验:

  1. 在 Azure 应用程序配置存储中,转到“操作”>“功能管理器”。

  2. 选择变体功能标志“问候语”右侧的“...”上下文菜单,然后选择“编辑”。

    Microsoft Azure 门户的屏幕截图,编辑变体功能标志。

  3. 转到“遥测”选项卡,选中“启用遥测”框。

  4. 转到“试验”选项卡,选中“创建试验”框,并为试验命名。

  5. 选择“审阅 + 更新”,然后选择“更新”。

  6. 通知指示操作成功。 在“功能管理器”中,变体功能标志应在“试验”下显示“活动”。

为试验创建指标

拆分试验工作区中的指标是发送到 Application Insights 的事件的定量度量值。 此指标有助于评估功能标志对用户行为和结果的影响。

在早期更新应用时,你向应用程序代码添加了 _telemetryClient.TrackEvent("Like")Like 是表示用户操作的遥测事件,在本例中为心形按钮选择。 此事件将发送到 Application Insights 资源,你会将该资源连接到要创建的指标。 我们创建的应用只指定一个事件,但你可以有多个事件和随后的多个指标。 多个指标也可以基于单个 Application Insight 事件。

  1. 导航到拆分试验工作区资源。 在“配置”>“试验指标”下,选择“创建”。

  2. 在“创建试验指标”下选择或输入以下信息,并使用“创建”进行保存。

    Microsoft Azure 门户的屏幕截图,创建新的试验指标。

    设置 示例值 说明
    Name 心形投票 试验指标的名称。
    描述 统计当用户看到特殊消息时选择心形按钮的人数,而不是当他们没有看到时。 指标的可选说明。
    Application Insights 事件名称 Like Application Insights 事件的名称。 此名称区分大小写,是使用 _telemetryClient.TrackEvent("<Event-Name>") 在代码中指定的名称。
    度量值作为 计数 可用选项如下:
    • 计数:计算用户触发事件的次数。
    • 平均值:计算用户事件的平均值。
    • 总和:将用户的事件值相加。 显示平均求和值。
    • 百分比:计算触发事件的用户的百分比。
    所需影响 增加 此设置表示度量所创建指标背后的最终目标或目的。

    在本教程中,我们的假设是,当“今日名言”旁边有一条特殊消息时,更多用户单击心形按钮。 应用程序代码将此点击跟踪为名为“Like”的事件。 应用程序将 Like 事件作为遥测数据发送到 Application Insights,并且此试验的“所需影响”是在“心形投票”上看到用户点击数增加(度量为“计数”),以便能够验证给定假设。 如果尽管向分配的受众显示了特殊消息,但按钮的点击次数减少,则该假设对此试验无效。

  3. 创建后,新指标将显示在门户中。 可以通过选择屏幕右侧的 (...) 上下文菜单来编辑或删除它。

    Microsoft Azure 门户的屏幕截图,显示试验指标。

获取试验结果

若要对新设置的试验进行测试并生成结果以供分析,请模拟应用程序的某些流量,然后等待 10 到 15 分钟。

要查看试验的结果,请导航到“功能管理器”,然后在变体功能标志列表中单击“...”>“试验”,或在“试验”列中选择“活动”链接。 默认情况下不显示此列。 要显示它,请在“功能管理器”中,选择“管理视图”>“编辑列”>“添加列”>“试验”和“应用”

在结果页上,默认情况下选择试验的版本、要比较结果的基线比较变体。 如果需要,请根据喜好更改默认值,然后选择“应用”以查看试验结果。

Microsoft Azure 门户的屏幕截图,显示试验结果。

上面的屏幕截图显示,试验得到了所需的结果,“心形投票”的“打开”变体比“关闭”变体多 560.62% 的心形投票。

对变体功能标志的任何编辑都会生成新版本的试验,可以选择它们来查看其结果。

注意

若要获取试验结果,每个变体至少需要 30 个事件,但建议拥有的事件数超过最小采样大小,以确保试验产生可靠的结果。

注意

默认情况下已启用 Application Insights 采样,可能会影响试验结果。 在本教程中,建议在 Application Insights 中关闭采样。 详细了解 Application Insights 中的采样

下一步

要详细了解试验概念,请参阅以下文档。

有关 .NET 功能管理库的完整功能概要,请继续阅读以下文档。