【aspnetcore】在过滤器(Filter)中使用注入服务(ServiceFilter|TypeFilter)

本文介绍了ASP.NET Core中AOP的应用,详细讲解了如何在ActionFilter中使用依赖注入,并对比了ServiceFilter与TypeFilter的区别及应用场景。

在MVC中,AOP是很常用的功能,我们经常会使用如 ActionFilterIAuthorizeFilter 等描述对ControllerAction进行约束和扩展,一般做法如下:

public class TestActionFilterAttribute : Attribute, IActionFilter
{
 
    public void OnActionExecuted(ActionExecutedContext context)
    {
        if (context.HttpContext.Request.Query.TryGetValue("id", out StringValues value))
        {
            Console.WriteLine(value.First());
        }
        else
        {
            context.HttpContext.Response.Redirect("/Error/404");
        }
    }
 
    public void OnActionExecuting(ActionExecutingContext context)
    { }
}

上面的代码很简单,就是判断请求中是否包含id参数,如果有,则打印id;如果没有,则跳转到错误页面。用法也很简单,在需要约束的Action上添加[TestActionFilter]即可。

[TestActionFilter]       
public IActionResult Index()
{
    return View();
}

这是Filter最基本的用法,但是,如果我们需要在Filter中使用注入的服务怎么办?比如说修改下 TestActionFilterAttribute

public class TestActionFilterAttribute : Attribute, IActionFilter
{
    private readonly ILogger _logger;
 
    public TestActionFilterAttribute(ILoggerFactory logger)
    {
        _logger = logger.CreateLogger("TestActionFilterAttribute");
    }
 
    public void OnActionExecuted(ActionExecutedContext context)
    {
        var path = context.HttpContext.Request.Path;
        _logger.LogDebug($"{path} 开始运行了");
    }
 
    public void OnActionExecuting(ActionExecutingContext context)
    { }
}

我们在Filter的构造函数中注入ILoggerFactory参数,这是系统默认提供的日志工厂服务,用来在控制台打印消息。

回到Controller文件,发现[TestActionFilter]报错:未提供与"TestActionFilterAttribute"的必需形参logger对应的实参。好吧,下面我们尝试构造一个logger对象

public class HomeController : Controller
{
    private readonly ILoggerFactory _loggerFactory;
 
    public HomeController(ILoggerFactory factory)
    {
        _loggerFactory = factory;
    }
 
    [TestActionFilter(_loggerFactory)]
    public IActionResult Index()
    {
        return View();
    }
}

修改过后,继续报错:特性构造函数参数"logger"具有类型ILoggerFactory,这不是有效特性参数类型。由此可见,如果在Filter中需要注入服务,常规的方式是无法实现的。

如果一定需要调用注入服务该怎么实现呢?其实框架已经为我们提供了两种途径:TypeFilterServiceFilter

public class TestTypeFilter : IActionFilter
{
    private readonly ILogger _logger;
 
    public TestTypeFilter(ILoggerFactory logger)
    {
        _logger = logger.CreateLogger("TestTypeFilter");
    }
 
    public void OnActionExecuted(ActionExecutedContext context)
    {
        var path = context.HttpContext.Request.Path;
        _logger.LogDebug($"{path} 开始运行了");
    }
 
    public void OnActionExecuting(ActionExecutingContext context)
    { }
}

这里的代码和上面修改过的TestActionFilterAttribute一模一样,修改下Controller文件:

[TypeFilter(typeof(TestTypeFilter))]
public IActionResult Index()
{
    return View();
}

运行测试,效果如下:
在这里插入图片描述
可以看到,代码运行正常。

下面再看看ServiceFilter的用法,新建文件 TestServiceFilter

public class TestServiceFilter : IActionFilter
{
    private readonly ILogger _logger;
 
    public TestServiceFilter(ILoggerFactory logger)
    {
        _logger = logger.CreateLogger("TestServiceFilter");
    }
 
    public void OnActionExecuted(ActionExecutedContext context)
    {
        var path = context.HttpContext.Request.Path;
        _logger.LogDebug($"{path} 开始运行了");
    }
 
    public void OnActionExecuting(ActionExecutingContext context)
    { }
}

修改Controller文件:

//[TypeFilter(typeof(TestTypeFilter))]
[ServiceFilter(typeof(TestServiceFilter))]
public IActionResult Index()
{
       return View();
}

仅仅这样是不够的,顾名思义,ServiceFilter(服务过滤器),我们需要到startup.csConfiguraionServices中注册TestServiceFilter

services.AddSingleton<TestServiceFilter>();

运行测试,效果如下:
在这里插入图片描述

OK,运行正常!

下面是补充内容,添加一个全局异常过滤器:

新建文件 MvcGlobalExceptionFilter.cs

public class MvcGlobalExceptionFilter : IExceptionFilter
{
    private readonly ILogger _logger;
 
    public MvcGlobalExceptionFilter(ILoggerFactory logger)
    {
        _logger = logger.CreateLogger("MvcGlobalExceptionFilter");
    }
 
    public void OnException(ExceptionContext context)
    {
        // 全局异常的错误处理
        _logger.LogError(context.Exception, "全局异常");
    }
}

修改Startup.cs中的ConfigurationServices

services.AddMvc(options =>
{
    // 添加全局异常
    options.Filters.Add<MvcGlobalExceptionFilter>();
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

修改Controller文件,手动抛出异常:

[ServiceFilter(typeof(TestServiceFilter))]
public IActionResult Index()
{
    throw new Exception("异常测试,这是手动抛出的异常");
    return View();
}

运行测试,效果如下:
在这里插入图片描述
可以看到,我们定义的过滤器捕获并打印了异常信息。


ASP.NET Core-ActionFilter实现依赖注入(ServiceFilterAttributeTypeFilterAttribute)

一、简介

前几篇文章都是讲ASP.NET Core MVC中的依赖注入(DI)与扩展点的,也许大家都发现在ASP.NET CORE中所有的组件都是通过依赖注入来扩展的,而且面向一组功能就会有一组接口或抽象工厂来扩展功能,就如IControllerActivator这样的功能点在上篇文章(查看.NET Core源代码通过Autofac实现依赖注入到Controller属性)中也提到了,今天我们主要介绍一个大类似的扩展点,ASP.NET Core MVC中为我们提供了新的机制为Action Filters(也就是过滤器)进行依赖注入的扩展。

二、过滤器依赖注入

在ASP.NET Core MVC中,框架中为我们提供了类型为 IFilterAttributes 来装饰Action,用于拦截Action请求,这有在以前的版本中就有了,但是如果我们想结合依赖注入使用的话要使用IFilterFactory接口来扩展Action Filter的创建过程。

2.1 IFilterFactory接口定义

public interface IFilterFactory : IFilter
{
    IFilter CreateInstance([NotNull] IServiceProvider serviceProvider);
}

我们想要创建一个Filter Attribute并需要依赖注入的话一般想要的代码为:

public class FilterClass : ActionFilterAttribute  
{
  public FilterClass(IDependency1 dependency1, IDependency2 dependency2)
  {
    // ...use dependencies
  }
}

ASP.NET Core MVC中为我们提供了两种简单的IFilterFactory : ServiceFilterAttributeTypeFilterAttribute 。来个例子看看怎么使用。

public class HomeController: Controller  
{
    [TypeFilter(typeof(FilterClass))]
    [ServiceFilter(typeof(FilterClass))]
    public IActionResult Index()
    {
        return View();
    }
}

2.2 ServiceFilterAttribute

其实看到名字,有些朋友就能想到了,它是基于依赖注入的一个IFilterFactoryService这个词强化了它是一个通过获取服务来实现依赖注入的,大家想到了什么?是不是GetService()? 没错,其实它的机制就是这个。

要使用ServiceFilter就必须在依赖注入容器里注册对应的类型,比如下面的例子就要先将FilterClass类型注册到IOC容器里。

public void ConfigureServices(IServiceCollection services)
{
      services.AddSingleton<FilterClass>();
 
      services.AddMvc()
}

当然如果FilterClass类型的构造器需要注入类型时,也需要在IOC容器里进行注册才可以使用。

我们来看下ServiceFilterAttribute的源代码:

public class ServiceFilterAttribute : Attribute, IFilterFactory, IOrderedFilter
{
    public ServiceFilterAttribute([NotNull] Type type)
    {
        ServiceType = type;
    }

    public Type ServiceType { get; private set; }

    public int Order { get; set; }

    public IFilter CreateInstance([NotNull] IServiceProvider serviceProvider)
    {
        var service = serviceProvider.GetRequiredService(ServiceType);

        var filter = service as IFilter;
        if (filter == null)
        {
            throw new InvalidOperationException(Resources.FormatFilterFactoryAttribute_TypeMustImplementIFilter(
                typeof(ServiceFilterAttribute).Name,
                typeof(IFilter).Name));
        }

        return filter;
    }
}

2.3 TypeFilterAttribute

当然你也可以选择使用这个类似于ServiceFilter过滤器的TypeFilter过滤器,它也同样实现了IFilterFactory接口,并可以通过它创建出可使用依赖注入的过滤器来。之所以叫TypeFilter就是因为它并不需要在依赖注入容器里注册类型就能创建出过滤器, 我们来看下它的代码:

public class TypeFilterAttribute : Attribute, IFilterFactory, IOrderedFilter
{
    private ObjectFactory factory;

    public TypeFilterAttribute([NotNull] Type type)
    {
        ImplementationType = type;
    }

    public object[] Arguments { get; set; }

    public Type ImplementationType { get; private set; }

    public int Order { get; set; }

    public IFilter CreateInstance([NotNull] IServiceProvider serviceProvider)
    {
        if (this.factory == null)
        {
            var argumentTypes = Arguments?.Select(a => a.GetType())?.ToArray();

            this.factory = ActivatorUtilities.CreateFactory(ImplementationType, argumentTypes ?? Type.EmptyTypes);
        }

        return (IFilter)this.factory(serviceProvider, Arguments);
    }
}

三、结语

相信看过上一篇文章的朋友都注意到了ServiceProviderActivatorUtilities 的不同,本文中的ServiceFilterAttributeTypeFilterAttribute 原理上也是通过它们来创建Filter的,所以使用场景就看大家如何来使用。其实最近看.NET Core的源代码,看到的到处都是接口、工厂使用依赖注入形成扩展点的例子,其实微软以前代码的扩展点也挺多的,只是API并不那么开放,ASP.NET Core中我们看到了一个"开放"的框架。

是因为没有配置mysql持久化问题?using System; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.AspNetCore.Http; using SecurityCenter.Filter; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Rewrite; using Quartz; using Quartz.Impl; using Quartz.Spi; using BaronBusinessModule.Util.Quartz.Crontab; using BaronBusinessModule.Util.Quartz; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Distributed; using System.Threading.Tasks; using System.Threading; using System.Net.Sockets; using Microsoft.AspNetCore.HttpOverrides; using BaronBusinessModule.Util; using Minio; using BaronBusinessModule.Util.Files; using Google.Protobuf.WellKnownTypes; using DotNetCore.CAP; using Savorboard.CAP.InMemoryMessageQueue; using BaronBusinessModule.AttributeFile; using System.Net.Http; using BaronBusinessModule.Util.WX; using BaronBusinessModule.DataBase; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Storage; using WorkflowCore; using WorkflowCore.Interface; namespace BoranManageWeb { public class Startup { private readonly IWebHostEnvironment env; public Startup(IConfiguration configuration, IWebHostEnvironment env) { Configuration = configuration; this.env = env; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { // services.AddDbContextFactory<MySqlContext>(options => //options.UseMySql( // Configuration.GetConnectionString("DefaultConnection12"), // new MySqlServerVersion(new Version(5, 6, 0)), // o => o.ExecutionStrategy(c => new NonRetryingExecutionStrategy(c)) //)); services.AddMvc(opt => { opt.EnableEndpointRouting = false; }); try { using TcpClient tcpClient = new TcpClient(); tcpClient.Connect("127.0.0.1", 6379); services.AddStackExchangeRedisCache(opt => { opt.Configuration = "127.0.0.1:6379"; }); } #pragma warning disable CS0168 // 声明了变量“ex”,但从未使用过 catch (Exception ex) #pragma warning restore CS0168 // 声明了变量“ex”,但从未使用过 { } ///设置跨域 //services.Configure<CookiePolicyOptions>(opt => //{ // opt.MinimumSameSitePolicy = SameSiteMode.None; //}); services.AddSession(o => { o.IdleTimeout = TimeSpan.FromDays(2); }); services.AddSingleton<ITempDataProvider, CookieTempDataProvider>(); services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>(); services.AddSingleton<Microsoft.AspNetCore.Http.IHttpContextAccessor, Microsoft.AspNetCore.Http.HttpContextAccessor>(); //添加cors 服务 配置跨域处理 services.AddCors(options => { options.AddPolicy("any", builder => { builder.AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader(); //.AllowCredentials(); }); }); //注册过滤器 services.AddAuthorization(options => { options.AddPolicy("CheckLogin", policy => policy.Requirements.Add(new HandlerLoginManageAttribute())); }); services.AddSingleton<IAuthorizationHandler, HandlerLoginManageAttribute>(); services.AddHttpClient(); services.AddDataProtection(); services.AddOpenApiDocument(); services.AddMvc(d => { d.EnableEndpointRouting = false; }).AddRazorRuntimeCompilation(); #region quartz services.AddQuartz(q => { q.UseMicrosoftDependencyInjectionJobFactory(); q.UsePersistentStore(s => { s.UseProperties = true; s.RetryInterval = TimeSpan.FromSeconds(15); s.UseMySql(ado => { ado.ConnectionString = Configuration.GetConnectionString("DefaultConnection12"); ado.TablePrefix = "QRTZ_"; }); s.UseJsonSerializer(); s.UseClustering(c => { c.CheckinMisfireThreshold = TimeSpan.FromSeconds(20); c.CheckinInterval = TimeSpan.FromSeconds(10); }); }); q.AddJob<GiveMoneyJob>(j => j .StoreDurably() .WithIdentity(GiveMoneyJob.Key) .WithDescription("定时补贴任务")); }); services.AddQuartzServer(options => { options.WaitForJobsToComplete = true; }); #endregion services.AddSingleton(s => { var uri = new Uri(MyContext.Settings.MinioHttpUrl); var minioBuild = new MinioClient().WithEndpoint($"{uri.Host}:{uri.Port}") .WithCredentials(MyContext.Settings.MinioUser, MyContext.Settings.MinioPass); if (uri.Scheme.ToLower() == "https") { minioBuild = minioBuild.WithSSL(); } var minio = minioBuild.Build(); return minio; }); services.AddCap(x => { var dbConnectString = Configuration.GetConnectionString("DefaultConnection12"); x.UseMySql(dbConnectString); x.UseInMemoryMessageQueue(); //x.UseDashboard(); }); // 注册工作流配置服务 start services.AddSingleton<BaronBusinessModule.Dao.Workflow_NodeConfigDao>(); services.AddSingleton<BaronBusinessModule.Dao.Workflow_ConfigDao>(); services.AddSingleton<BaronBusinessModule.Dao.Workflow_ApprovalTaskDao>(); services.AddSingleton<BaronBusinessModule.Dao.Workflow_InstanceDao>(); services.AddSingleton<BaronBusinessModule.Dao.Workflow_ApprovalActionDao>(); // 添加审批操作DAO注册 services.AddSingleton<BaronBusinessModule.Service.Workflow_NodeConfigService>(); services.AddSingleton<BaronBusinessModule.Service.Workflow_ConfigService>(); services.AddSingleton<BaronBusinessModule.Service.Workflow_InstanceService>(); services.AddSingleton<BaronBusinessModule.Service.Workflow_ApprovalActionService>(); // 添加审批操作服务注册 services.AddSingleton<BaronBusinessModule.Service.Workflow_ApprovalService>(); services.AddSingleton<BaronBusinessModule.Service.Workflow_BillContextService>(); // 配置WorkflowCore使用MySQL持久化存储 var connectionString = Configuration.GetConnectionString("DefaultConnection12"); services.AddWorkflow() .UseMySQL(connectionString, true, true); services.AddSingleton<BaronBusinessModule.Workflow.WorkflowDynamicFactory>(); // 注册Workflow Core服务 end // 注册账单审批工作流启动器 // 同时注册为单例服务,以便在控制器中直接注入 services.AddSingleton<BaronBusinessModule.Workflow.WorkflowBillApprovalStarter>(); // 关闭定时检查:注释掉后台服务注册 // services.AddHostedService<BaronBusinessModule.Workflow.WorkflowBillApprovalStarter>(); //services.AddAuthentication(AccessTokenAuthenticationOptions.AccessTokenAuthenticationScheme) // .AddScheme<AccessTokenAuthenticationOptions, AccessTokenAuthenticationHandler>(AccessTokenAuthenticationOptions.AccessTokenAuthenticationScheme, ctx => { }); //services.AddAuthentication(WechatAccessTokenAuthenticationOptions.AuthenticationScheme) // .AddScheme<WechatAccessTokenAuthenticationOptions, WechatAccessTokenAuthenticationHandler>(WechatAccessTokenAuthenticationOptions.AuthenticationScheme, ctx => { }); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env, MinioClient minioClient, ISchedulerFactory schedulerFactory, IHttpClientFactory httpClientFactory, IWorkflowHost workflowHost ) { //BaronBusinessModule.DataBase.MySql.ContextFactory=contextFactory; WXOper.HttpClientFactory = httpClientFactory; // 禁用TRACE请求 app.Use(async (context, next) => { if (context.Request.Method == "TRACE") { context.Response.StatusCode = 405; // Method Not Allowed return; } if (context.Request.Method == "TRACK") { context.Response.StatusCode = 405; // Method Not Allowed return; } await next(); }); MinioHelper.Default = minioClient; //app.UseDeveloperExceptionPage(); //app.UseBrowserLink(); GiveMoneyJob.SchedulerFactory = schedulerFactory; OperJob.SchedulerFactory = schedulerFactory; app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto }); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); //app.UseBrowserLink(); } else { app.UseExceptionHandler("/Home/Error"); } //配置Cors //app.UseCors("any"); app.UseCors(builder => { builder.AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader(); //.AllowCredentials(); }); app.UseStaticFiles(); app.UseSession(); //var rewrite = new RewriteOptions() //.AddRedirect(@"c/(.*)", "c?id=$1"); //app.UseRewriter(rewrite); //app.Use(async (ctx, next) => //{ // Console.WriteLine(DateTime.Now.ToString() + " " + ctx.Request.Method + " " + ctx.Request.Path + ctx.Request.QueryString.Value); // //await ctx.SignInAsync(new ClaimsIdentity(new System.Security.Principal.GenericIdentity(""))); // //await Task.Delay(3000); // await next.Invoke(); //}); app.UseAuthentication(); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}" ); routes.MapAreaRoute( name: "Sys", areaName: "00.System", template: "Sys/{controller=Menu}/{action=Index}" ); routes.MapAreaRoute( name: "Data", areaName: "01.Data", template: "Data/{controller=Menu}/{action=Index}" ); routes.MapAreaRoute( name: "Order", areaName: "02.Order", template: "Order/{controller=Menu}/{action=Index}" ); routes.MapAreaRoute( name: "Food", areaName: "03.Food", template: "Food/{controller=Menu}/{action=Index}" ); routes.MapAreaRoute( name: "Vip", areaName: "04.Vip", template: "Vip/{controller=Menu}/{action=Index}" ); routes.MapAreaRoute( name: "Recharge", areaName: "05.Recharge", template: "Recharge/{controller=Menu}/{action=Index}" ); routes.MapAreaRoute( name: "Activity", areaName: "06.Activity", template: "Activity/{controller=Menu}/{action=Index}" ); routes.MapAreaRoute( name: "Sales", areaName: "07.Sales", template: "Sales/{controller=Menu}/{action=Index}" ); routes.MapAreaRoute( name: "Finance", areaName: "08.Finance", template: "Finance/{controller=Menu}/{action=Index}" ); routes.MapAreaRoute( name: "Dishes", areaName: "09.Dishes", template: "Dishes/{controller=Menu}/{action=Index}" ); routes.MapAreaRoute( name: "Super", areaName: "10.Supermarkets", template: "Super/{controller=Menu}/{action=Index}" ); routes.MapAreaRoute( name: "Material", areaName: "11.Material", template: "Material/{controller=Menu}/{action=Index}" ); }); //app.UseOpenApi(); ////app.UseSwaggerUi3(); //app.UseReDoc(opt => //{ // opt.DocumentPath = "/redoc"; //}); BaronBusinessModule.Util.MyHttpContext.Configure(app.ApplicationServices.GetRequiredService<Microsoft.AspNetCore.Http.IHttpContextAccessor>()); // 启动工作流宿主 start workflowHost.Start(); // 启动工作流宿主 end } } }
最新发布
12-18
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值