在解析期间确定依赖项的目标类型

Determine the target type for a dependency during resolution

似乎无法确定解析依赖项的类型:

containerBuilder.Register(context =>
{
   // What is the type for which this component is resolved?
   var type = default(Type); // TBD
   return context.Resolve<ILoggerFactory>().CreateLogger(type);
});

此处的目标是创建 .NET Core 记录器,并为其应用的类型设置正确的类别。

Autofac documentation 中的示例描述了如何使用中间件组件完成此操作,我成功了。但是似乎向每个注册添加管道都会对性能产生影响,而且我还没有发现一种方法可以仅将管道应用于依赖于 ILogger 的组件的注册。

动机:显而易见的选择似乎是将依赖项更改为 ILogger<T> 类型,其中 T 是应用此依赖项的类型,如下所示:

public class Component
{
    public Component(ILogger<Component> logger)...
}

但经验告诉我,很多开发者草草复制粘贴组件而忘记更改类型参数,导致日志混乱。在当前代码中,我们仍然使用 Common.Logging,我们的组件只需要一个非通用 ILog:

public class Component
{
    public Component(ILog log)...
}

在我们之前的 DI 容器中,Castle.Windsor,就这么简单:

public class LoggerSubDependencyResolver : ISubDependencyResolver
{
    public bool CanResolve(CreationContext context, ISubDependencyResolver contextHandlerResolver, ComponentModel model, DependencyModel dependency)
    {
        return dependency.TargetType == typeof(ILog);
    }

    public object Resolve(CreationContext context, ISubDependencyResolver contextHandlerResolver, ComponentModel model, DependencyModel dependency)
    {
        return CanResolve(context, contextHandlerResolver, model, dependency) ? LogManager.GetLogger(model.Implementation) : null;
    }
}

有没有更简单的方法来完成这个?或者这就是这样做的方式,我过于担心性能影响?

是的,但它没有记录,所以使用它需要您自担风险

containerBuilder.Register(ctx =>
{
    var rc = ctx as ResolveRequestContext;    
    var operation = rc.Operation as IDependencyTrackingResolveOperation;
    //this is not going to work for controllers, unless you register them as services
    var service = operation.RequestStack.Skip(1).First().Service as TypedService;   
    return LogManager.GetLogger(service.ServiceType);
});

如果您坚持使用文档,中间件方法就是实现此目的的方法。在这种情况下,它几乎是 CastleWindsor 解析器的直接替代品(注意:在 CW 中,每次注册也会调用解析器)。并且您只能为 类 设置使用反射依赖 ILog 的中间件。此外,如果性能是一个问题,您可能需要缓存 LogManager.GetLogger 调用,如前所述 in the documentation.

public class Log4NetMiddleware : IResolveMiddleware
{
    //Caching LogManager.GetLogger(type)
    private ILog _log;

    public Log4NetMiddleware(ILog log)
    {            
        _log = log;
    }

    public PipelinePhase Phase => PipelinePhase.ParameterSelection;

    public void Execute(ResolveRequestContext context, Action<ResolveRequestContext> next)
    {
        context.ChangeParameters(context.Parameters.Union(
            new[]
            {
                new ResolvedParameter(
                    (p, i) => p.ParameterType == typeof(ILog), //This is your CanResolve
                    (p, i) => _log //Resolve
                ),
            }));

        next(context);

        //This code below can be removed if you don't need injection via properties
        if (context.NewInstanceActivated)
        {
            var instanceType = context.Instance.GetType();

            //This is your CanResolve
            var properties = instanceType
                .GetProperties(BindingFlags.Public | BindingFlags.Instance)
                .Where(p => p.PropertyType == typeof(ILog) && p.CanWrite && p.GetIndexParameters().Length == 0);

            foreach (var propToSet in properties)
            {
                //This is your Resolve
                propToSet.SetValue(context.Instance, _log, null);
            }
        }
    }
}

注册看起来像这样

public void ConfigureContainer(ContainerBuilder containerBuilder)
{

    containerBuilder.ComponentRegistryBuilder.Registered += (sender, args) =>
    {
        var type = args.ComponentRegistration
            .Activator
            .LimitType;

        var constructors = type
            .GetConstructors(BindingFlags.Instance | BindingFlags.Public);

        if (constructors.Any(x => x.GetParameters().Any(p => p.ParameterType == typeof(ILog))))
        {
            args.ComponentRegistration.PipelineBuilding += (sender2, pipeline) =>
            {
                pipeline.Use(new Log4NetMiddleware(LogManager.GetLogger(type)));
            };
            return;
        }

        //the code below can be removed if you don't inject via properties
        var properties = type
          .GetProperties(BindingFlags.Public | BindingFlags.Instance)
          .Where(p => p.PropertyType == typeof(ILog) && p.CanWrite && p.GetIndexParameters().Length == 0);

        if (properties.Any())
        {
            args.ComponentRegistration.PipelineBuilding += (sender2, pipeline) =>
            {
                pipeline.Use(new Log4NetMiddleware(LogManager.GetLogger(type)));
            };
        }
    };
}