Featurify - ASP.NET Core 2.0+ MVC 应用程序中的用户特定功能标志





5.00/5 (6投票s)
用户特定的功能标志实现,可用于在不进行多次发布的情况下针对特定用户推出功能
什么是 Featurify?
Featurify 是一个功能标志的实现,它非常易于设置和使用。它旨在解决一个且仅一个问题,即功能标志是用户特定的。因此,对于有问题的 MVC 应用程序,绝对不需要依赖于应用程序设置。
背景
Featurify 被设计为轻量级且易于与 .NET Core 2.0+ Web 应用程序集成。这是两部分难题的一部分。如果您对简单的基于应用程序设置的功能标志感兴趣,那么这个库不适合您!为此目的有许多其他出色的选项,并且它们已在此文章的最后部分列出。
此难题的第一部分是在应用程序级别实现用户特定的功能。此难题的第二部分是设计一个用户界面,帮助管理员定义各种功能并将它们与用户关联。
这是我目前正在进行的一个项目中的一个需求,用于选择性地推出功能。已经有一个提供此功能的(功能标志即服务)服务,称为Launch Darkly(文档)。但有一个问题——它不是免费的!“不是免费的”促成了这个包的创建,用很少的精力就可以实现相同的功能。当然,它不像 Launch Darkly 那样全面,是为了满足“简单”的标准。
难题的第一部分
难题的第一部分几乎完全涉及 Featurify 的使用方式。因此,用户的功能标志设置是根据几个简单的步骤动态生成的。但在下一节中,我将深入探讨用更贴近实际的方法替换这种简单的元数据生成实现。那么,让我们开始吧!
项目设置
为了演示 Featurify,我将使用 .NET Identity 框架模板。要创建使用 Identity 框架的项目,请按照以下步骤操作:
- 选择 ASP.NET Core Web 应用程序
- 点击“更改身份验证”按钮
- 选择“个人用户帐户”选项
- 在项目根文件夹中打开命令提示符,运行以下命令:
创建项目后,将功能标志添加到应用程序的第一步是使用 NuGet 包管理器或包管理器控制台添加
Featurify
NuGet 包。
下面提供的代码片段来自 Featurify 的一个示例项目,您可以在线这里查看。
> Install-Package Featurify
在包管理器控制台中输入此命令将在当前项目中添加最新版本(1.0.1)。
基础设施类
要在项目中实现功能标志,您只需要两个实现特定接口的类。完成此操作后,您可以使用基于 IServiceCollection
的扩展之一来使用它们。
您必须定义的第一个类是一个有助于 Featurify 识别当前登录用户的id。这可以是任何东西,一个 GUID、一个电子邮件地址或任何可以唯一标识登录用户的标识符。这个类必须实现 IUserInfoStrategy
接口。对于基于 .NET Identity 框架的项目,用户标识信息是登录用户声明的一部分。更具体地说,类型为 ClaimTyes.Name
的声明包含登录用户的电子邮件地址。为了简单起见,我们将以此作为用户的唯一 ID。以下是这个类的实现:
public class IdentityUserInfoStrategy : IUserInfoStrategy
{
private readonly IHttpContextAccessor accessor;
public IdentityUserInfoStrategy(IHttpContextAccessor accessor)
{
this.accessor = accessor;
}
public async Task<string> GetCurrentUserId()
{
var claims = accessor.HttpContext.User.Claims.ToList();
var claim = claims.Single(c => c.Type == ClaimTypes.Name);
await Task.CompletedTask;
return claim.Value;
}
}
根据基础设施要求定义类的下一步是定义一个类,该类将充当根据用户 ID 和功能名称提供功能标志元数据的类。唯一的要求是它应该实现 IToggleMetadata
接口,如下所示:
public class ToggleMetadata : IToggleMetadata
{
public string Name { get; set; }
public bool Value { get; set; }
public string UserId { get; set; }
}
下一步是定义提供方法以接收功能名称和用户 ID 并返回相应元数据的类。此类应实现 IToggleMetadataFinder
接口,如下所示。在此之前,您可能想知道功能名称将如何传递,我们很快就会讲到!
public class UserBasedToggleMetadataFinder : IToggleMetadataFinder
{
public async Task<IToggleMetadata> FindToggleStatus(string featureName, string userId)
{
var required = toggleMetadatas.SingleOrDefault(t => t.Name == featureName && t.UserId == userId);
await Task.CompletedTask;
return required;
}
private static List<ToggleMetadata> toggleMetadatas = new List<ToggleMetadata>
{
new ToggleMetadata { Name = "Featurify.ImportFeature", Value = true,
UserId = "john.doe@company.com" },
new ToggleMetadata { Name = "Featurify.ExportFeature", Value = false,
UserId = "john.doe@company.com" },
new ToggleMetadata { Name = "Featurify.EmailFeature", Value = true,
UserId = "john.doe@company.com" }
};
}
如果您注意到,本地的 List<ToggleMetadata>
被用作元数据的来源,因为本文的重点是演示 Featurify。在实际实现中,您可以提供一个页面,管理员可以在其中选择用户和功能来决定 Value
。借此机会指出代码片段中列出的条目的 Name
属性 - Featurify.{feature-name}
。很快,我将解释这些内容是如何组合在一起的!
有了这个类,所有必要的基础设施类都已准备就绪。我们现在可以使用 AddFeaturify
fluent 扩展将 Featurify 添加到我们的 .NET Core MVC 应用程序中。
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddFeaturify<UserBasedToggleMetadataFinder, IdentityUserInfoStrategy>(options =>
{
options.AnyUserVerifier = "*";
options.UseStrict = false;
});
services.AddMvc();
}
同样,这非常简单——我正在使用 IServiceCollection
上的 AddFeaturify
扩展,并传递元数据查找器和用户标识策略的类型。您还必须传递一个具有 2 个属性的 options
对象。AnyUserVerifier
属性标识用于识别某个功能是否对所有用户都启用的字符串(与 IdentityUserInfoStrategy
返回的 ID 无关)。第二个属性是 UseStrict
,它指示如果找不到用户的功能元数据,是否应抛出异常。因此,如果将其设置为 true
并且未识别到登录用户的元数据,则会抛出异常。
通过最后一步,我们就可以开始使用 Featurify 来实现功能标志了!我将通过三个我们希望根据登录用户选择性公开的功能来演示这一点——一个导入功能、一个导出功能和一个电子邮件功能。要定义这些功能,您只需创建以下类。请注意,所有这些类都实现了 IFeatureToggle
接口。
public class ImportFeature : IFeatureToggle
{
}
public class ExportFeature : IFeatureToggle
{
}
public class EmailFeature : IFeatureToggle
{
}
现在让我们看看所有这些内容是如何结合在一起的!有两种方式可以使用 Featurify。我将逐一详细说明。
直接从视图
第一步是添加必需的 using
语句。下一步是在视图中注入 IFeaturifyServer
。最后,使用对应于功能的类将其选择性地公开给登录用户。下面的代码片段说明了这一点:
@using Featurify
@using Featurify.Contracts
@using AspNetIdentityWithFeaturify.Features
@inject IFeaturifyServer Featurify
<strong>ImportFeature</strong>: Enabled
@if (await Featurify.Is<ImportFeature>().Enabled())
{
<button class="btn btn-success"> Import Users</button>
}
else
{
<button class="btn btn-danger" disabled="disabled"> Import Users</button>
}
您也可以将 using
语句移到您的 _ViewImports.cshtml 文件中,这样它就不必出现在每个视图中。在 using
语句之后是 @inject
行,我在这里注入了 Featurify 服务器,它将允许我验证当前登录用户的功能是否已启用。注入的 IFeaturifyServer
有几种使用方式。如果您不介意冗余,可以使用上面显示的第一种方法。在这里,我使用 Is<IFeatureToggle>()
扩展来指示我正在查询的功能,然后调用 Enabled()
方法来获取基于登录用户的状态。
许多事情共同作用才能使其正常工作。它们是:
- 登录用户的 ID 由
IdentityUserInfoStrategy.GetCurrentUserId
方法返回。 - 使用
Is<T>
方法调用期间使用的static
类,功能名称派生为Featurify.ImportFeature
(在下一节中,我将解释如何自定义这一点)。 - 使用来自步骤 (1) 和 (2) 的用户 ID 和功能名称,
UserBasedToggleMetadataFinder.FindToggleStatus
方法用于识别元数据。
利用所有这些信息(以及在初始设置期间传递的选项),Featurify 服务器可以推断出该用户功能的最终状态并将其返回到视图。
您也可以通过调用 Featurify 服务器的 Enabled<T>()
方法来保持简单,如下所示:
@using Featurify
@using Featurify.Contracts
@using AspNetIdentityWithFeaturify.Features
@inject IFeaturifyServer Featurify
<strong>ImportFeature</strong>: Enabled
@if (await Featurify.Enabled<ImportFeature>())
{
<button class="btn btn-success"> Import Users</button>
}
else
{
<button class="btn btn-danger" disabled="disabled"> Import Users</button>
}
从控制器
您也可以在控制器中注入 Featurify 服务器(显然!)以达到相同的效果,如下所示:
[Authorize]
public class HomeController : Controller
{
private readonly IFeaturifyServer server;
public HomeController(IFeaturifyServer server)
{
this.server = server;
}
public async Task<IActionResult> Contact()
{
var model = new ContactViewModel
{
CanImport = await server.Is<ImportFeature>().Enabled()
};
return View(model);
}
}
在这种情况下,我正在使用构造函数中注入的 IFeaturifyServer
来确定 ImportFeature
是否已启用,并将结果设置在模型属性中。现在可以在视图中使用此属性选择性地显示该功能。
自定义功能名称生成
如果您回想一下 UserBasedToggleMetadataFinder
类有一个 private
List<ToggleMetadata>
来为各种用户提供元数据信息。在这里,指定的功能名称遵循 Featurify.{feature-name} 格式。这是默认设置,可以根据您的需求进行自定义。为此,您必须创建一个实现 IFeatureNameTransformer
接口的类作为基础设施类的一部分,并在调用 AddFeaturify
时指定它,如下所示:
public class CustomNameTransformer : IFeatureNameTransformer
{
public string TransformFeatureName(string featureName)
{
return $"MyProduct.{featureName}";
}
}
考虑 CustomNameTransformer
。在这里,TransformFeatureName
方法返回自定义的功能名称。既然这个类可用,我将在 AddFeaturify
调用中指定它。
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddFeaturify<UserBasedToggleMetadataFinder,
IdentityUserInfoStrategy,
CustomNameTransformer>(options =>
{
options.AnyUserVerifier = "*";
options.UseStrict = false;
});
services.AddMvc();
}
基于应用程序设置的实现
致谢
- Feature Toggle - 旨在消除魔法字符串(https://github.com/jason-roberts/FeatureToggle)
结论
此文章不包含难题的“第二部分”,即设计用户界面,因为它有点超出 Featurify 的范围。但它应该很简单直接,因为只需要一个管理功能的页面,一个将功能链接到用户的页面,以及一个列出它们的页面。欢迎任何反馈/评论。感谢您的阅读!