C# 对于在DI容器外部创建的类,有哪些选项可以让一个正常注入的记录器访问该类?

C# 对于在DI容器外部创建的类,有哪些选项可以让一个正常注入的记录器访问该类?,c#,.net,logging,dependency-injection,ninject,C#,.net,Logging,Dependency Injection,Ninject,我知道服务定位器模式是。当您的代码中有类似于Global.Kernel.Get()的内容时,顾问会检测到代码气味。因此,在可能的情况下,我们应该使用构造函数/属性或方法注入 现在让我们考虑一个常见的日志记录任务。有两个扩展,例如和 那“只是工作”。您在项目中引用它们,然后每当您注入ILogger时,就会得到一个包装好的log4net logger实例,该实例以您注入的类型命名。在代码中的任何地方,您甚至都不依赖log4net,只是在您提供配置的地方。太好了 但是,如果您需要一个非DI容器中创建的

我知道服务定位器模式是。当您的代码中有类似于
Global.Kernel.Get()
的内容时,顾问会检测到代码气味。因此,在可能的情况下,我们应该使用构造函数/属性或方法注入

现在让我们考虑一个常见的日志记录任务。有两个扩展,例如和 那“只是工作”。您在项目中引用它们,然后每当您注入

ILogger
时,就会得到一个包装好的log4net logger实例,该实例以您注入的类型命名。在代码中的任何地方,您甚至都不依赖log4net,只是在您提供配置的地方。太好了

但是,如果您需要一个非DI容器中创建的类中的记录器,该怎么办?你是怎么到那里的?它不能自动注入,因为类是在DI之外创建的,您可以执行Global.Kernel.Inject(myObject),因为依赖不在组合根目录中的DI是错误的,正如上面链接的文章中所解释的,那么您真正有哪些选项呢

下面是我的尝试,但我不喜欢它,因为它感觉非常尴尬。我实际上是将ILogger工厂而不是ILogger注入到在DI对象外部创建的类中,然后在构造函数中将ILogger工厂传递到DI对象外部

实现这一点的另一种方法是将log4net依赖项引入到DI类的外部,但这也感觉很落后

你将如何解决这个问题

下面是一个代码示例:

using System.Threading;
using Ninject;
using Ninject.Extensions.Logging;

namespace NjTest
{
    // This is some application configuration information
    class Config
    {
        public string Setting1 { get; set; }
    }

    // This represent messages the application receives
    class Message
    {
        public string Param1 { get; set; }
        public string Param2 { get; set; }
        public string Param3 { get; set; }
    }

    // This class represents a worker that collects and process information
    // in our example this information comes partially from the message and partially
    // driven by configuration
    class UnitCollector
    {
        private readonly ILogger _logger;
        // This field is not present in the actual class it's
        // here just to give some exit condition to the ProcessUnit method
        private int _defunct;
        public UnitCollector(string param1, string param2, ILoggerFactory factory)
        {
            _logger = factory.GetCurrentClassLogger();
            // param1 and param2 are used during processing but for simplicity we are
            // not showing this
            _logger.Debug("Creating UnitCollector {0},{1}", param1, param2);
        }

        public bool ProcessUnit(string data)
        {
            _logger.Debug("ProcessUnit {0}",_defunct);
            // In reality the result depends on the prior messages content
            // For simplicity we have a simple counter here
            return _defunct++ < 10;
        }
    }

    // This is the main application service
    class Service
    {
        private readonly ILogger _logger;
        private readonly Config _config;

        // This is here only so that it can be passed to UnitCollector
        // and this is the crux of my question. Having both
        // _logger and _loggerFactory seems redundant
        private readonly ILoggerFactory _loggerFactory;

        public Service(ILogger logger, ILoggerFactory loggerFactory, Config config)
        {
            _logger = logger;
            _loggerFactory = loggerFactory;
            _config = config;
        }

        //Main processing loop
        public void DoStuff()
        {
            _logger.Debug("Hello World!");
            UnitCollector currentCollector = null;
            bool lastResult = false;
            while (true)
            {
                Message message = ReceiveMessage();
                if (!lastResult)
                {
                    // UnitCollector is not injected because it's created and destroyed unknown number of times
                    // based on the message content. Still it needs a logger. I'm here passing the logger factory
                    // so that it can obtain a logger but it does not feel clean
                    // Another way would be to do kernel.Get<ILogger>() inside the collector
                    // but that would be wrong because kernel should be only used at composition root
                    // as per http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/
                    currentCollector = new UnitCollector(message.Param2, _config.Setting1,_loggerFactory);
                }
                lastResult = currentCollector.ProcessUnit(message.Param1);

                //message.Param3 is also used for something else
            }
        }
        private Message ReceiveMessage()
        {          
            _logger.Debug("Waiting for a message");
            // This represents receiving a message from somewhere
            Thread.Sleep(1000);
            return new Message {Param1 = "a",Param2 = "b",Param3 = "c"};
        }
    }

    class Program
    {
        static void Main()
        {
            log4net.Config.XmlConfigurator.Configure();
            IKernel kernel = new StandardKernel();
            kernel.Bind<Config>().ToMethod(ctx => new Config {Setting1 = "hey"});
            Service service = kernel.Get<Service>();
            service.DoStuff();

        }
    }
}
使用系统线程;
使用Ninject;
使用Ninject.Extensions.Logging;
名称空间测试
{
//这是一些应用程序配置信息
类配置
{
公共字符串设置1{get;set;}
}
//这表示应用程序接收的消息
类消息
{
公共字符串Param1{get;set;}
公共字符串Param2{get;set;}
公共字符串Param3{get;set;}
}
//此类表示收集和处理信息的工作者
//在我们的示例中,此信息部分来自消息,部分来自
//由配置驱动
类单元收集器
{
专用只读ILogger\u记录器;
//该字段不存在于实际的类中
//这里只是给ProcessUnit方法一些退出条件
私人互联网已不复存在;
公共UnitCollector(字符串参数1、字符串参数2、iLogger工厂)
{
_logger=factory.GetCurrentClassLogger();
//param1和param2在处理过程中使用,但为了简单起见,我们使用了param1和param2
//不显示这个
_Debug(“创建UnitCollector{0},{1}”,param1,param2);
}
公共布尔处理单元(字符串数据)
{
_Debug(“ProcessUnit{0}”,_已失效);
//实际上,结果取决于先前的消息内容
//为了简单起见,我们这里有一个简单的计数器
返回_失效+++<10;
}
}
//这是主要的应用程序服务
班级服务
{
专用只读ILogger\u记录器;
私有只读配置_Config;
//此处仅用于将其传递给UnitCollector
//这是我问题的关键。两者兼备
//_logger和_loggerFactory似乎是多余的
私人只读iLogger工厂(loggerFactory);
公共服务(ILogger logger、ILogger工厂loggerFactory、配置)
{
_记录器=记录器;
_loggerFactory=loggerFactory;
_config=config;
}
//主处理回路
公共空间
{
_Debug(“你好,世界!”);
UnitCollector currentCollector=null;
bool lastResult=false;
while(true)
{
消息消息=接收消息();
如果(!lastResult)
{
//UnitCollector未被注入,因为它的创建和销毁次数未知
//基于消息内容。它仍然需要一个记录器。我在这里经过记录器工厂
//因此,它可以获得记录器,但感觉不干净
//另一种方法是在收集器中执行kernel.Get()
//但这是错误的,因为内核应该只在复合根上使用
//依照http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/
currentCollector=new UnitCollector(message.Param2,_config.Setting1,_loggerFactory);
}
lastResult=currentCollector.ProcessUnit(message.Param1);
//message.Param3还用于其他用途
}
}
私有消息接收消息()
{          
_调试(“等待消息”);
//这表示从某处接收消息
睡眠(1000);
返回新消息{Param1=“a”,Param2=“b”,Param3=“c”};
}
}
班级计划
{
静态void Main()
{
log4net.Config.XmlConfigurator.Configure();
IKernel kernel=新的标准内核();
kernel.Bind().ToMethod(ctx=>newconfig{Setting1=“hey”});
Service=kernel.Get();
service.DoStuff();
}
}
}
App.Config:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net, Version=1.2.13.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a" />
  </configSections>
  <startup> 
      <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
  </startup>
  <log4net>
    <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%date [%thread] %-5level %logger - %message%newline" />
      </layout>
    </appender>
    <root>
      <level value="ALL" />
      <appender-ref ref="ConsoleAppender" />
    </root>
  </log4net>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="log4net" publicKeyToken="669e0ddf0bb1aa2a" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-1.2.13.0" newVersion="1.2.13.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>

</configuration>

您应该看看。您可以创建具有相应绑定的接口工厂:

internal interface IUnitCollectorFactory
{
    IUnitCollector Create(Param2Type param2);
}

Bind<IUnitCollectorFactory>().ToFactory();
请注意,您不再需要日志工厂
private readonly Func<string, string, UnitCollector> _unitCollertorFactory;        
public Service(ILogger logger, Config config, Func<string, string, UnitCollector> unitCollertorFactory)
{
    _logger = logger;
    _config = config;
    _unitCollertorFactory = unitCollertorFactory;
}
currentCollector = _unitCollertorFactory(message.Param2, _config.Setting1);
Bind<ILog>().ToMethod(context => LogFactory.CreateLog(context.Request.Target.Type));
kernel.Bind<UnitCollector>().ToMethod(c => new UnitCollector(
    c.Get<Config>().Setting1,
    LogFactory.CreateLog(typeof(UnitCollector))));

class UnitCollector {
    private readonly string _param2;
    private readonly ILogger _logger;
    public UnitCollector(string param2, ILogger logger) {
        _param2 = param2;
        _logger = logger;
    }

    public bool ProcessUnit(string data, string param1) {
        // Perhaps even better to extract both parameters into a
        // Parameter Object: https://bit.ly/1AvQ6Yh
    }
}

class Service {
    private readonly ILogger _logger;
    private readonly Func<UnitCollector> _collectorFactory;

    public Service(ILogger logger, Func<UnitCollector> collectorFactory) {
        _logger = logger;
        _collectorFactory = collectorFactory;
    }

    public void DoStuff() {
        UnitCollector currentCollector = null;
        bool lastResult = false;
        while (true) {
            Message message = ReceiveMessage();
            if (!lastResult) {
                currentCollector = this.collectorFactory.Invoke();
            }
            lastResult = currentCollector.ProcessUnit(message.Param1, message.Param2);
        }
    }
}