配置AspectCore-Framework

首先通过Nuget安装AspectCore.Extensions.DependencyInjection

接下来我们可以配置拦截器

    //继承自AbstractInterceptorAttribute
    public class CustomInterceptorAttribute : AbstractInterceptorAttribute
    {
        public override async Task Invoke(AspectContext context, AspectDelegate next)
        {
            try
            {
                Console.WriteLine("调用前");
                await next(context);//执行调用的方法等
            }
            catch (Exception ex)
            {
                Console.WriteLine("捕获到异常");
                throw;
            }
            finally
            {
                Console.WriteLine("调用结束");
            }
        }
    }

然后在Startup.cs中的ConfigureServices方法中配置代理(.NET6中对应的是builder.Services)

    services.ConfigureDynamicProxy(config =>
    {
        services.AddTransient<IExmapleService, ExmapleService>();
        
        services.AddControllers();
        
        config.Interceptors.AddTyped<CustomInterceptorAttribute>(Predicates.ForService("*Service"));
        config.Interceptors.AddTyped<CustomInterceptorAttribute>(Predicates.ForService("*Controller"));
    });

以上配置是对名称以Service或Controller结尾的进行代理,还有其他一些规则如:

	//全部代理
	config.Interceptors.AddTyped<CustomInterceptorAttribute>();
	//名称以Execute开头的方法会被代理
	config.Interceptors.AddTyped<CustomInterceptorAttribute>(Predicates.ForMethod("Execute*"));
	//以Service结尾则不会被代理
	config.NonAspectPredicates.AddService("Service");
	//更多详细规则可以查阅官方文档
	//https://github.com/dotnetcore/AspectCore-Framework/blob/master/docs/1.%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97.md
	......

也可以通过NonAspectAttribute对Service或Method进行单独设置:

	//无论在Startup.cs中如何配置,该接口都不会通过代理
	[NonAspect]
	public interface IExampleService
	{
    	void Method();
	}

最后在Program.cs中,将依赖注入交由AspectCore处理:

	public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                })
        		//略
                .UseServiceProviderFactory(new DynamicProxyServiceProviderFactory());

.NET 6中可以使用:

builder.Host.UseServiceProviderFactory(new DynamicProxyServiceProviderFactory());

一般来说到这里就完成了,但是我在上面配置代理时,将Controller也配置为进行代理:config.Interceptors.AddTyped<CustomInterceptorAttribute>(Predicates.ForService("*Controller"));,因此,就需要进行一些额外的处理。

  • 首先是在Startup.cs中,将Controller作为Service注册:

    services.AddControllers().AddControllersAsServices(); //MVC的话AddControllers就是AddMvc()
    
  • 然后将action都标记为虚函数,如:

    	public class ExampleController
        {
            [HttpPost]
            public virtual ActionResult Test()
            {
                //......
            }
        }
    

这样就可以在Controller层进行拦截了。

配置Log4Net

依旧是通过Nuget安装包Microsoft.Extensions.Logging.Log4Net.AspNetCore

Startup.cs对Configure进行一些修改

	//这里多加一个参数
	public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
    {
        ...... //其他代码

        loggerFactory.AddLog4Net();
    }

然后添加配置文件log4net.config

<?xml version="1.0" encoding="utf-8"?>

<log4net>
    <!-- 日志记录的相关配置,可以针对不同记录方式和级别,配置多个appender节点 -->
    <appender name="ErrorLog" type="log4net.Appender.RollingFileAppender">
        <!-- 日志文件保存位置,可以相对路径也可以绝对路径,注意这里是斜杠而不是反斜杠 -->
        <file value="logs/" />
        <!-- 文件的命名规则,此处为日期加.log后缀 -->
        <datePattern value="yyyy-MM-dd'.log'" />
        <appendToFile value="true" />
        <rollingStyle value="Size" />
        <maxSizeRollBackups value="100" />
        <maximumFileSize value="3MB" />
        <!-- 按日期保存 -->
        <rollingStyle value="Date" />
        <!-- 静态文件名设为false -->
        <staticLogFileName value="false" />
        <!-- 日志文件最小锁定模式,尽量保证更多的同时写入 -->
        <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
        <layout type="log4net.Layout.PatternLayout">
            <conversionPattern
                value="%newline-----------------------%newline【时间】:%d%newline【级别】:%-5p%newline【对象】:%logger%newline【内容】:%m%newline" />
        </layout>
        <filter type="log4net.Filter.LevelRangeFilter">
            <!--设置该日志文件将记录的日志级别区间,如果希望每个级别都分开一个文件记录,可以将appender节点配置多个,设置不同的记录级别-->
            <param name="LevelMin" value="Error" />
            <param name="LevelMax" value="Error" />
        </filter>
    </appender>

    <root>
        <!--注意,当配置多个不同日志级别的appender节点时,务必按以下级别顺序排列appender-ref节点,否则低级别将不会执行-->
        <!--   All -> Debug -> Info -> Warn -> Error -> Fatal -> Off   -->
        <appender-ref ref="ErrorLog" />
        <level value="DEBUG" />
    </root>
</log4net>

最后再修改一下文章开头的拦截器部分,记录日志:

    public class CustomInterceptorAttribute : AbstractInterceptorAttribute
    {	
     	//在这里注入日志服务,其他所需服务也可以这样注入
        [FromServiceContext] private ILogger<CustomInterceptorAttribute> Logger { get; set; }
        
        public override async Task Invoke(AspectContext context, AspectDelegate next)
        {
            try
            {
                Console.WriteLine("调用前");
                await next(context);//执行调用的方法等
            }
            catch (Exception ex)
            {
                Console.WriteLine("捕获到异常");
                //记录日志,这里简单的ToString了,具体记录内容自行调整
                Logger.LogError(ex.ToString());
                //记录日志后是继续抛出还是进行其他处理,可自行调整
                throw;
            }
            finally
            {
                Console.WriteLine("调用结束");
            }
        }
    }

到这里应该就可以在Service或Controller中发生异常时进行记录了。其实通过AspectCore的这一功能,可以实现更多的功能,记录日志只是其中之一。