[料理佳餚] ASP.NET Core 的 Feature Flags(Feature Toggle)

Feature Toggle 這個議題最近挺夯的,它達到的效果就是我們透過設定,就可以輕易地開關應用程式上的功能,讓開發好的功能可以真正地發佈到 Production 上,但是不相關的使用者不會受到該功能的影響,也方便我們去測試只有在 Production 上才能測試的案例,而 ASP.NET Core 已經有套件支援 Feature Toggle,我們來看一下怎麼做?

Microsoft.FeatureManagement.AspNetCore

這個套件名稱就叫做 Microsoft.FeatureManagement.AspNetCore,是真正由微軟官方所開發的,我們從 NuGet 上安裝之後,就在 Startup.cs 裡面加上 AddFeatureManagement(),就可以開始使用了。

public class Startup
{
    ...
    
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddFeatureManagement();
        
        ...
    }
    
    ...
}

設定

接下來,我們要在 appsettings.json 新增一個名稱為 FeatureManagement 的區段,FeatureManagement 是預設的名稱,如果想要取另一個名稱,就要在 AddFeatureManagement() 方法中去另外指定。

public class Startup
{
    ...
    
    public IConfiguration Configuration { get; }
    
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddFeatureManagement(this.Configuration.GetSection("MyFeatures"));
        
        ...
    }
    
    ...
}

再來我們就在 FeatureManagement 這個區段裡面,去設定幾組我們想要 Feature Flags。

{
  "FeatureManagement": {
    "FeatureA": true,
    "FeatureB": false,
    "FeatureC": true 
  },
  "Logging": {
    ...
  },
  "AllowedHosts": "*"
}

一個 Feature Flag 可以是一個功能或是一組功能,它是抽象的,上面這樣的設定就表示 FeatureA 跟 FeatureC 開啟,FeatureB 則關閉,這些 Feature Flags 我們可以套用在 Controller 及 Action 上,也可以套用在 MVC View 裡面,甚至是套用在 Middleware 上,或是我們可以自己寫套用的邏輯

套用在 Controller 及 Action 上

要套用在 Controller 或 Action 上,我們需要借用一個內建的 Action Filter - FeatureGate,它可以填入多個 Feature Flags,預設要全部開啟,該 Controller 或 Action 才會開啟,否則預設會回傳 404。

[FeatureGate("FeatureA")]
public class HomeController : Controller
{
    ...

    [FeatureGate("FeatureB", "FeatureC")] // 404
    public IActionResult Index()
    {
        return View();
    }

    ...
}

如果我們要調成任一 Feature Flag 開啟,該功能就開啟的話,那就填入 RequirementType.Any 參數即可。

[FeatureGate("FeatureA")]
public class HomeController : Controller
{
    ...

    [FeatureGate(RequirementType.Any, "FeatureB", "FeatureC")]
    public IActionResult Index()
    {
        return View();
    }

    ...
}

還有,如果我們不想要回傳 404,我們有自己的回應邏輯,那可以使用 UseDisabledFeaturesHandler() 方法覆寫它,UseDisabledFeaturesHandler() 方法允許我們傳入一個 Action<IEnumerable<string>, ActionExecutingContext> 的委派方法。

public class Startup
{
    ...
    
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddFeatureManagement().UseDisabledFeaturesHandler(
            (features, context) =>
                {
                    context.Result = new StatusCodeResult(500);
                });
        
        ...
    }
    
    ...
}

或是,也可以傳入一個實作了 IDisabledFeaturesHandler 的類別實例。

public class MyDisabledFeaturesHandler : IDisabledFeaturesHandler
{
    public async Task HandleDisabledFeatures(IEnumerable<string> features, ActionExecutingContext context)
    {
        context.Result = new StatusCodeResult(500);

        await Task.CompletedTask;
    }
}

public class Startup
{
    ...
    
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddFeatureManagement().UseDisabledFeaturesHandler(new MyDisabledFeaturesHandler());
        
        ...
    }
    
    ...
}

套用在 MVC View

Microsoft.FeatureManagement.AspNetCore 套件內建有 TagHelper,可以讓我們在 View 裡面套用 Feature Flag,首先,我們就在 View 裡面透過 @addTagHelper 把 TagHelper 加進來,我們可以選擇加在單一的 View 上,或是加在 _ViewImports.cshtml 裡面,讓所有的 View 都能使用。

之後,我們就可以用 feature 這個 TagHelper 把 View 的部分內容包起來,標上 Feature Flags,然後一樣可以設定 RequirementType,而且它還多了一個 negate 屬性可以用,代表反向的意思。

套用在 Middleware

Microsoft.FeatureManagement.AspNetCore 套件提供兩個方法讓我們套用在 Middleware 上:UseForFeature()UseMiddlewareForFeature<T>,不過要注意的是,它目前只能指定單一個 Feature Flag,不支援多個,使用方式如下範例:

public class Startup
{
    ...
    
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        ...

        app.UseForFeature(
            "FeatureA",
            appBuilder =>
                {
                    appBuilder.Use(
                        async (ctx, next) =>
                            {
                                await next();

                                await ctx.Response.WriteAsync("<!--Enable FeatureA-->");
                            });
                });

        app.UseMiddlewareForFeature<SomeMiddleware>("FeatureB");

        ...
    }
    
    ...
}

自行撰寫套用邏輯

如果我們想在我們的服務裡面套用 Feature Flag,只要將 IFeatureManager 注入進來,透過它來判斷 Feature Flag 的開啟或關閉,使用起來大概就像下面這樣,不過它目前只有非同步的 API,最好在非同步的環境下使用:

public class HomeController : Controller
{
    private readonly IFeatureManager _featureManager;

    public HomeController(IFeatureManager featureManager)
    {
        _featureManager = featureManager;
    }

    public async Task<IActionResult> Index()
    {
        if (await _featureManager.IsEnabledAsync("FeatureB"))
        {
            return new ContentResult { Content = "Enable FeatureB" };
        }

        return View();
    }

    ...
}

以上,Microsoft.FeatureManagement.AspNetCore 這個套件最基本的使用方式分享給大家,希望對大家有幫助。

參考資料

C# 指南 ASP.NET 教學 ASP.NET MVC 指引
Azure SQL Database 教學 SQL Server 教學 Xamarin.Forms 教學