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);
}
}
}