基于 Filter 实现条件路由

Posted dotNET跨平台

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于 Filter 实现条件路由相关的知识,希望对你有一定的参考价值。

基于 filter 实现条件路由

Intro

在我们的项目有几个测试用的接口,有的接口我们往往不想在生产环境上使用,于是会在代码里判断当前环境是不是生产环境,如果不是生产环境才允许执行,否则就返回一个错误,这样的接口多了之后就会发现很多重复的代码,我们此时就可以使用一个 filter 来实现 API 接口的检查,如果是生产环境就不执行 API 接口的逻辑

Filter V1

MVC filter 有几种类型,AuthorizationFilterResourceFilterActionFilterResultFilterExceptionFilter, 首先我们要选择合适的类型,最合适的莫过于 ResourceFilterActionFilter,可能大多小伙伴对于 ActionFilter 更为熟悉一些,但是我觉得这种场景下 ResourceFilter 更好一些,从 MVC filter 的执行流程上来说,会依次执行 AuthorizationFilterResourceFilterActionFilter,而我们的条件并非一种授权,所以个人感觉 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 来测试一下返回结果:

Minimal API

controller

我们启动的时候默认的环境是 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)实现路由控制异常处理授权处理(获取客户端信息)

风洞稳定小球系统----- 基于MATLAB实现的Kalman filter距离检测

stream.filter 两个条件 怎么实现

使用filter的用户登陆

next js 中基于路由的条件组件渲染

用addRoutes实现动态路由