基于 Filter 实现条件路由
Posted dotNET跨平台
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于 Filter 实现条件路由相关的知识,希望对你有一定的参考价值。
基于 filter 实现条件路由
Intro
在我们的项目有几个测试用的接口,有的接口我们往往不想在生产环境上使用,于是会在代码里判断当前环境是不是生产环境,如果不是生产环境才允许执行,否则就返回一个错误,这样的接口多了之后就会发现很多重复的代码,我们此时就可以使用一个 filter 来实现 API 接口的检查,如果是生产环境就不执行 API 接口的逻辑
Filter V1
MVC filter 有几种类型,AuthorizationFilter
、ResourceFilter
、ActionFilter
、ResultFilter
、ExceptionFilter
, 首先我们要选择合适的类型,最合适的莫过于 ResourceFilter
和 ActionFilter
,可能大多小伙伴对于 ActionFilter
更为熟悉一些,但是我觉得这种场景下 ResourceFilter
更好一些,从 MVC filter 的执行流程上来说,会依次执行 AuthorizationFilter
、ResourceFilter
、ActionFilter
,而我们的条件并非一种授权,所以个人感觉 ResourceFilter
更合适一些,我们可以使用 IAsyncResourceFilter
来实现,实现代码如下:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public sealed class NonProductionOnlyFilter : Attribute, IAsyncResourceFilter
public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
var environment = context.HttpContext.RequestServices.GetRequiredService<IWebHostEnvironment>();
if (environment.IsProduction())
context.Result = new NotFoundResult();
else
await next();
Filter V2
为了更加的通用,我们可以把检查的逻辑和返回值逻辑封装成一个委托
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class ConditionalFilter : Attribute, IAsyncResourceFilter
public Func<HttpContext, bool> ConditionFunc get; init; = _ => true;
public Func<HttpContext, IActionResult> ResultFactory get; init; = _ => new NotFoundResult();
public virtual async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
var condition = ConditionFunc.Invoke(context.HttpContext);
if (condition)
await next();
else
var result = ResultFactory.Invoke(context.HttpContext);
context.Result = result;
再在这个 ConditionalFilter
的基础上实现上面的逻辑:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public sealed class NonProductionEnvironmentFilter : ConditionalFilter
public NonProductionEnvironmentFilter()
ConditionFunc = c => c.RequestServices.GetRequiredService<IWebHostEnvironment>()
.IsProduction() == false;
看起来是不是简单了很多,对于别的情况也比较容易扩展,比如我们实现一个指定环境生效的条件
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public sealed class EnvironmentFilter : ConditionalFilter
public EnvironmentFilter(params string[] environmentNames)
Guard.NotNull(environmentNames);
var allowedEnvironments = environmentNames.ToHashSet(StringComparer.OrdinalIgnoreCase);
ConditionFunc = c =>
var env = c.RequestServices.GetRequiredService<IWebHostEnvironment>().EnvironmentName;
return allowedEnvironments.Contains(env);
;
Filter V3
在前面的文章中我们有提到在 .NET 7 中针对于 Minimal API,引入了 EndpointFilter
,我们也可以为我们的 ConditionalFilter
添加 EndpointFilter
的支持
public class ConditionalFilter : Attribute, IAsyncResourceFilter
#if NET7_0_OR_GREATER
, IEndpointFilter
#endif
public Func<HttpContext, bool> ConditionFunc get; init; = _ => true;
public Func<HttpContext, object> ResultFactory get; init; = _ =>
#if NET7_0_OR_GREATER
Results.NotFound()
#else
new NotFoundResult()
#endif
;
public virtual async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
var condition = ConditionFunc.Invoke(context.HttpContext);
if (condition)
await next();
else
var result = ResultFactory.Invoke(context.HttpContext);
context.Result = result switch
IActionResult actionResult => actionResult,
IResult httpResult => new HttpResultActionResultAdapter(httpResult),
_ => new OkObjectResult(result)
;
#if NET7_0_OR_GREATER
public virtual async ValueTask<object> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
var result = ConditionFunc.Invoke(context.HttpContext);
if (result)
return await next(context);
return ResultFactory.Invoke(context.HttpContext);
#endif
这里有个需要注意的地方就是 EndpointFilter
的返回和 Resource filter 的返回值不同,返回的类型不是 IActionResult
而且不能正确的处理 IActionResult
类型,针对 IResult
会有处理,所以我们针对 .NET 7 及以上返回的是 IResult
类型,在 ResourceFilter
中处理逻辑中针对 IResult
再转成了 IActionResult
, 也就是上面的 HttpResultActionResultAdapter
,实现也很简单,实现如下:
internal sealed class HttpResultActionResultAdapter : IActionResult
private readonly IResult _result;
public HttpResultActionResultAdapter(IResult result)
_result = result;
public Task ExecuteResultAsync(ActionContext context)
return _result.ExecuteAsync(context.HttpContext);
Demo
测试代码分为 Minimal API 的 endpoint API 和 MVC controller,示例代码如下:
var envGroup = app.MapGroup("/env-test");
envGroup.Map("/dev", () => "env-test")
.AddEndpointFilter(new EnvironmentFilter(Environments.Development));
envGroup.Map("/prod", () => "env-test")
.AddEndpointFilter(new EnvironmentFilter(Environments.Production));
[HttpGet("EnvironmentFilterTest/Dev")]
[EnvironmentFilter("Development")]
//[EnvironmentFilter("Production")]
public IActionResult EnvironmentFilterDevTest()
return Ok(new Title = ".NET is amazing!" );
[HttpGet("EnvironmentFilterTest/Prod")]
[EnvironmentFilter("Production")]
public IActionResult EnvironmentFilterProdTest()
return Ok(new Title = ".NET is amazing!" );
访问我们的 API 来测试一下返回结果:
我们启动的时候默认的环境是 Development
,所以 Production
返回的都是 404,而 Development
相关的 API 则是正常返回了~
References
https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/filters
https://github.com/WeihanLi/WeihanLi.Web.Extensions
https://github.com/WeihanLi/WeihanLi.Web.Extensions/tree/dev/samples/WeihanLi.Web.Extensions.Samples
以上是关于基于 Filter 实现条件路由的主要内容,如果未能解决你的问题,请参考以下文章
MVC 自定义过滤器(Filter)实现路由控制异常处理授权处理(获取客户端信息)