升级Autofac后,Log4Net中间件为何无法直接解析ILog?
原本运行正常的ASP.NET MVC应用,升级Autofac到7.1.0并按官方示例配置Log4Net中间件后,直接解析ILog时持续失败,抛出组件未注册异常。
相关代码
Log4Net中间件实现
public class Log4NetMiddleware : IResolveMiddleware { public PipelinePhase Phase => PipelinePhase.ParameterSelection; public void Execute(ResolveRequestContext context, Action<ResolveRequestContext> next) { // Add our parameters. context.ChangeParameters(context.Parameters.Union( new[] { new ResolvedParameter( (p, i) => p.ParameterType == typeof(ILog), (p, i) => LogManager.GetLogger(p.Member.DeclaringType) ), })); // Continue the resolve. next(context); // Has an instance been activated? if (context.NewInstanceActivated) { Type instanceType = context.Instance.GetType(); // Get all the injectable properties to set. // If you wanted to ensure the properties were only UNSET properties, // here's where you'd do it. IEnumerable<PropertyInfo> properties = instanceType .GetProperties(BindingFlags.Public | BindingFlags.Instance) .Where(p => p.PropertyType == typeof(ILog) && p.CanWrite && p.GetIndexParameters().Length == 0); // Set the properties located. foreach (PropertyInfo propToSet in properties) { propToSet.SetValue(context.Instance, LogManager.GetLogger(instanceType), null); } } } } public class MiddlewareModule : Autofac.Module { private readonly IResolveMiddleware middleware; public MiddlewareModule(IResolveMiddleware middleware) { this.middleware = middleware; } protected override void AttachToComponentRegistration(IComponentRegistryBuilder componentRegistryBuilder, IComponentRegistration registration) { // Attach to the registration's pipeline build. registration.PipelineBuilding += (sender, pipeline) => { // Add our middleware to the pipeline. pipeline.Use(middleware); }; } }
Global.asax.cs初始化代码
protected void Application_Start() { AreaRegistration.RegisterAllAreas(); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); ContainerBuilder builder = new ContainerBuilder(); var loggerMiddlewareModule = new MiddlewareModule(new Log4NetMiddleware()); builder.RegisterModule(loggerMiddlewareModule); IContainer container = builder.Build(); ILog log = container.Resolve<ILog>(TypedParameter.From(typeof(MvcApplication))); }
抛出的异常
The requested service 'log4net.Core.ILogger' has not been registered. To avoid this exception, either register a component to provide the service, check for service registration using IsRegistered(), or use the ResolveOptional() method to resolve an optional dependency.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: Autofac.Core.Registration.ComponentNotRegisteredException: The requested service 'log4net.Core.ILogger' has not been registered. To avoid this exception, either register a component to provide the service, check for service registration using IsRegistered(), or use the ResolveOptional() method to resolve an optional dependency.
解决方案
原因分析
Autofac的解析中间件是为其他组件注入ILog设计的(构造函数或属性注入场景),直接调用Resolve<ILog>时,中间件的参数匹配逻辑会失效(没有对应的Member.DeclaringType),且context.NewInstanceActivated为false,不会触发属性注入逻辑。此时Autofac会尝试解析ILog的底层依赖ILogger,但该类型未注册,因此报错。
方案1:直接注册ILog的解析逻辑
在容器构建前,添加ILog的注册逻辑,根据传入的类型参数生成日志实例:
ContainerBuilder builder = new ContainerBuilder(); var loggerMiddlewareModule = new MiddlewareModule(new Log4NetMiddleware()); builder.RegisterModule(loggerMiddlewareModule); // 添加ILog的直接注册 builder.Register((context, parameters) => { var targetType = parameters.TypedAs<Type>(); return LogManager.GetLogger(targetType); }).As<ILog>(); IContainer container = builder.Build(); ILog log = container.Resolve<ILog>(TypedParameter.From(typeof(MvcApplication)));
方案2:按中间件设计场景使用
中间件的核心作用是给业务服务自动注入ILog,而非直接解析ILog。例如定义一个业务服务:
public class SampleService { private readonly ILog _log; // 构造函数注入ILog public SampleService(ILog log) { _log = log; } // 或者属性注入ILog public ILog PropertyLog { get; set; } }
注册该服务后,解析时中间件会自动注入ILog:
builder.RegisterType<SampleService>(); var service = container.Resolve<SampleService>(); // service._log 和 service.PropertyLog 都会被正确注入
方案3:修改中间件支持直接解析ILog
如果需要保留直接解析ILog的场景,可以修改中间件逻辑,在开头处理直接解析ILog的请求:
public void Execute(ResolveRequestContext context, Action<ResolveRequestContext> next) { // 处理直接解析ILog的情况 if (context.Service.ServiceType == typeof(ILog)) { var typeParam = context.Parameters.OfType<TypedParameter>().FirstOrDefault(p => p.Type == typeof(Type)); Type targetType = typeParam?.Value as Type ?? typeof(object); context.Instance = LogManager.GetLogger(targetType); context.ResolutionComplete = true; return; } // 原有的参数注入逻辑 context.ChangeParameters(context.Parameters.Union( new[] { new ResolvedParameter( (p, i) => p.ParameterType == typeof(ILog), (p, i) => LogManager.GetLogger(p.Member.DeclaringType) ), })); next(context); // 原有的属性注入逻辑 if (context.NewInstanceActivated) { Type instanceType = context.Instance.GetType(); IEnumerable<PropertyInfo> properties = instanceType .GetProperties(BindingFlags.Public | BindingFlags.Instance) .Where(p => p.PropertyType == typeof(ILog) && p.CanWrite && p.GetIndexParameters().Length == 0); foreach (PropertyInfo propToSet in properties) { propToSet.SetValue(context.Instance, LogManager.GetLogger(instanceType), null); } } }
内容的提问来源于stack exchange,提问作者Dark_Knight

