Nat*_*els 4 c# asp.net-mvc-routing asp.net-core
我的 Web API 使用 ASP.NET Core 3.1。我有多个控制器都使用基于属性的路由,一切都很好。
我们希望能够在应用程序配置中切换一个或多个带有功能标志的控制器。理想情况下,如果未设置标志,则相应的控制器在 API 眼中应该不复存在。我正在努力想出最好的(或任何)方法来做到这一点。
在使用属性路由时,似乎没有配置扫描哪些控制器的内置方法,也无法修改路由找到的控制器或端点的集合。这是有问题的 Startup.cs 片段:
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
app.UseRouting();
app.UseEndpoints(e =>
{
if (!this.FeatureEnabled)
{
// DO SOMETHING?
}
e.MapControllers();
});
}
Run Code Online (Sandbox Code Playgroud)
我意识到我可以切换到更手动的手写路由并在 Startup 类中指定每个控制器、动作和参数,但我宁愿放弃这个功能标志要求,也不愿走那条凌乱的道路。
使用基于属性的路由时,有什么方法可以选择 API 中使用哪些控制器?
Nat*_*els 11
其他答案是可能的解决方案,但是我们发现了一个更简单的解决方案,它使用 Microsoft 为 ASP.NET Core 提供的特性标志功能,只需要几行代码。
https://docs.microsoft.com/en-us/azure/azure-app-configuration/use-feature-flags-dotnet-core
PM> install-package Microsoft.FeatureManagement.AspNetCore
Run Code Online (Sandbox Code Playgroud)
所以我们的初创公司有这样一行:
public void ConfigureServices(IServiceCollection services)
{
// ...
// By default this looks at the "FeatureManagement" config section
services.AddFeatureManagement();
}
Run Code Online (Sandbox Code Playgroud)
我们的功能门控控制器在顶部有一个新属性:
[ApiController]
[Route("api/v{version:apiVersion}/customers/{token}")]
// Feature.FooService is an enumeration we provide whose name is used as the feature flag
[FeatureGate(Feature.FooService)]
public class FooController : ControllerBase
{
...
}
Run Code Online (Sandbox Code Playgroud)
我们的 appsettings.json 有以下部分:
{
"FeatureManagement": {
"FooService" : false
}
}
Run Code Online (Sandbox Code Playgroud)
当标志被禁用时,整个控制器为任何操作返回 404,当标志被启用时它工作得很好。
这种方法有两个突出的小问题:
如果您使用 Nathan Daniels 答案的功能管理。您可以使用此 DocumentFilter 来隐藏 Swashbucke 中的控制器。
services.AddSwaggerGen(c =>
{
c.DocumentFilter<FeatureGateDocumentFilter>();
});
Run Code Online (Sandbox Code Playgroud)
FeatureGateDocumentFilter.cs
using Microsoft.FeatureManagement;
using Microsoft.FeatureManagement.Mvc;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using System.Linq;
namespace Portalum.Sales.WebShopApi.OperationFilters
{
public class FeatureGateDocumentFilter : IDocumentFilter
{
private readonly IFeatureManager _featureManager;
public FeatureGateDocumentFilter(IFeatureManager featureManager)
{
this._featureManager = featureManager;
}
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
foreach (var apiDescription in context.ApiDescriptions)
{
var filterPipeline = apiDescription.ActionDescriptor.FilterDescriptors;
var filterMetaData = filterPipeline.Select(filterInfo => filterInfo.Filter).SingleOrDefault(filter => filter is FeatureGateAttribute);
if (filterMetaData == default)
{
continue;
}
var featureGateAttribute = filterMetaData as FeatureGateAttribute;
var isActive = this._featureManager.IsEnabledAsync(featureGateAttribute.Features.Single()).GetAwaiter().GetResult();
if (isActive)
{
continue;
}
var apiPath = swaggerDoc.Paths.FirstOrDefault(o => o.Key.Contains(apiDescription.RelativePath));
swaggerDoc.Paths.Remove(apiPath.Key);
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
您可以实现自己的ControllerFeatureProvider控制器并决定在应用程序中使用哪些控制器。
public class CustomControllerFeatureProvider : ControllerFeatureProvider
{
private readonly IConfiguration _configuration;
public CustomControllerFeatureProvider(IConfiguration configuration)
{
_configuration = configuration;
}
protected override bool IsController(TypeInfo typeInfo)
{
var isController = base.IsController(typeInfo);
if (isController)
{
var enabledController = _configuration.GetValue<string[]>("EnabledController");
isController = enabledController.Any(x => typeInfo.Name.Equals(x, StringComparison.InvariantCultureIgnoreCase));
}
return isController;
}
}
Run Code Online (Sandbox Code Playgroud)
并将其添加到 startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers()
.ConfigureApplicationPartManager(manager =>
{
manager.FeatureProviders.Add(new CustomControllerFeatureProvider(_configuration));
});
}
Run Code Online (Sandbox Code Playgroud)