Structure Map - Collection of Dependencies in Constructor - c#

I have the following code in my Registry:
Scan(x =>
{
x.Assembly(Assembly.GetExecutingAssembly());
x.AddAllTypesOf<IXmlExtractor>();
});
This code adds all instances of IXmlExtractor in my assembly to the IoC container.
Now I want to use all found instances in my class, but I prefer to inject via constructor than to use ObjectFactory in a method. Hopefully code below illustrates what I want to do:
class Engine : IEngine
{
private readonly ILog _logger;
private readonly ICurveConfigRepository _configRepo;
private readonly IDataExtractor _dataExtractor;
public Engine(ILog logger,
ICurveConfigRepository configRepo,
IDataExtractor dataExtractor
/* SomeCollectionOfIXmlExtractors allExtractors */)
{
_logger = logger;
_configRepo = configRepo;
_dataExtractor = dataExtractor;
}
public void Run(string mode)
{
_logger.Info(string.Format("About to run retrieve config for '{0}' mode.", mode));
var config = _configRepo.LoadConfig(mode);
_logger.Info("Config retrieved, extracting data");
var data = _dataExtractor.GetCurves(config);
_logger.Info("Data extracted");
// Foreach IXmlExtractor instance, call .DoStuff(data) with it.
// I don't like using ObjectFactory here - see comment in constructor
var extractors = ObjectFactory.GetAllInstances<IXmlExtractor>();
}
}
Hopefully this is clear, please comment back if further explanation is required.
C#: 4.0
StructureMap: 2.5.4.0
Thanks,
Graeme

You have already correctly registered all instances with the container.
Scan(x =>
{
x.Assembly(Assembly.GetExecutingAssembly());
x.AddAllTypesOf<IXmlExtractor>();
});
All you need to do is declare an array variable and StructureMap will take care of the constructor injection automatically.
class Engine : IEngine
{
private readonly ILog _logger;
private readonly ICurveConfigRepository _configRepo;
private readonly IDataExtractor _dataExtractor;
private readonly IXmlExtractors[] _allExtractors;
public Engine(ILog logger,
ICurveConfigRepository configRepo,
IDataExtractor dataExtractor
IXmlExtractors[] allExtractors)
{
_logger = logger;
_configRepo = configRepo;
_dataExtractor = dataExtractor;
_allExtractors = allExtractors;
}
public void Run(string mode)
{
_logger.Info(string.Format("About to run retrieve config for '{0}' mode.", mode));
var config = _configRepo.LoadConfig(mode);
_logger.Info("Config retrieved, extracting data");
var data = _dataExtractor.GetCurves(config);
_logger.Info("Data extracted");
// Use extractors...
foreach (var extractor in _allExtractors)
{
extractor.DoStuff(data);
}
}
}

Related

Injecting logger from DI to object created with factory

I need to add logger (ILogger) to existing object of MyDbConnection, this object is created from Factory which is registered in NET Core DI together with MyOptions class
public class MyFactory : IMyFactory
{
private readonly MyOptions _options;
public MyFactory(MyOptions options)
{
_options = options;
}
public MyDbConnection CreateDbA() => new MyDbConnection(_options.ConnStrA);
public MyDbConnection CreateDbB() => new MyDbConnection(_options.ConnStrB);
public MyDbConnection CreateDbC() => new MyDbConnection(_options.ConnStrC);
}
Factory is then injected into service, which then use it to create object and do something
public class MyService : IMyService
{
public MyService(IMyFactory factory)
{
var a = factory.CreateDbA();
var b = factory.CreateDbB();
a.DoSomething();
b.DoSomething();
}
}
MyDbConnection looks like this:
public class MyDbConnection
{
private string connStr;
//private ILogger logger;
//public MyDbConnection(string connStr, ILogger logger)
public MyDbConnection(string connStr)
{
this.connStr = connStr;
//this.logger = Logger;
}
public void DoSomething()
{
//logger.LogWarning();
}
}
Problem is that I can't just add ILogger and inject it from DI container because factory is using 'new' to create MyDbConnection, without using DI. My only solution currently is to use static logger instead of injected one, but that seems to be a bad solution. Is there another way around this? How it should be done properly?
The design will need to be refactored to be able to get the desired behavior.
First MyDbConnection should be refactored accordingly to depend on the appropriate logger
For example
public class MyDbConnection {
private string connStr;
private ILogger logger;
public MyDbConnection(string connStr, ILogger<MyDbConnection> logger) {
this.connStr = connStr;
this.logger = logger;
}
public void DoSomething() {
logger.LogWarning();
}
}
Then the factory refactored to use ActivatorUtilities to initialize and inject the necessary dependencies via an injected service provider.
public class MyFactory : IMyFactory {
private readonly MyOptions options;
private readonly IServiceProvider services;
public MyFactory(MyOptions options, IServiceProvider services) {
this.options = options;
this.services = services;
}
public MyDbConnection CreateDbA() => ActivatorUtilities.CreateInstance<MyDbConnection>(services, options.ConnStrA);
public MyDbConnection CreateDbB() => ActivatorUtilities.CreateInstance<MyDbConnection>(services, options.ConnStrB);
public MyDbConnection CreateDbC() => ActivatorUtilities.CreateInstance<MyDbConnection>(services, options.ConnStrC);
}
In the example above the specific dependencies are provided just as before when they were manually initialized. All other dependencies (like the logger) will be resolved via the service provider and injected into the target class when it is being initialized.
This way dependency injection can be maintained and manual initialization of MyDbConnection can be avoided.

How to resolve a type with Autofac that requires an instance that has that an instance of the type as ctor parameter

Currently I solve the problem with a delegate in this way:
builder.Register(componentContext =>
{
var logger = componentContext.Resolve<ILogger>();
var logger1 = logger.ForContext(typeof(MainWindow));
var view = new MainWindow(logger1);
var logger2 = logger.ForContext(typeof(MainWindowModel));
var model = new MainWindowModel(logger2, view);
view.DataContext = model;
return view;
});
Is there a more elegant way to solve the problem?
EDIT:
The problem is not how to resolve ILogger. The problem is, that I have the MainWindowModel class that requires the same MainWindow instance I created in the first step (the instance that get passed in logger1) as a constructor parameter. If I would leave the instantiation of view to Autofac, I would have one instance resolved for MainWindow and an instance of MainWindowModel with a different instance of view passed in as constructor parameter (which would be wrong, as I need the same instance of MainWindow passed into MainWindowModel.
I hope that clarifies my question a bit more.
The problem you describe is injecting services based on the type of the component being activated. The Autofac documentation contains a great log4net example on how you can use pipeline middleware to inject parameters based on the component type.
Based on the example, your middleware would look something like this:
public class LoggerMiddleware : IResolveMiddleware
{
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(ILogger),
(p, i) => {
var logger = context.Resolve<ILogger>();
return logger.ForContext(p.Member.DeclaringType);
}
),
}));
next(context);
}
}
this is you want?
Register
builder.RegisterType<MainWindow>()
.InstancePerLifetimeScope();
builder.RegisterType<MainWindowModel>()
.InstancePerLifetimeScope();
Model
public class MainWindowModel
{
private readonly ILogger<MainWindowModel> _logger;
private MainWindow _mainWindow;
public MainWindowModel(ILogger<MainWindowModel> logger)
{
_logger = logger;
}
public void SetMainWindow(MainWindow mainWindow)
{
_mainWindow = mainWindow;
}
}
public class MainWindow
{
private readonly ILogger<MainWindowModel> _logger;
public MainWindow(ILogger<MainWindowModel> logger, MainWindowModel dataContext)
{
_logger = logger;
this.DataContext = dataContext;
this.DataContext.SetMainWindow(this);
}
public MainWindowModel DataContext { get; set; }
}

How do I pass a ILogger<T> into a object created with new?

I want to pass a logger into a constructor of an object. Normally, the logger is dependency injected into a class, but I want to log inside a class that is not dependency injected.
I have a .NET Core worker service. In that service, I have 0-many instances of a class that are created at runtime. I also want to be able to log messages to the logger inside that class.
In my example, I can pass the ILogger that I get from inside the TimedWorker, but the type is wrong.
Currently, I have:
class TimedWorker : BackgroundService
{
private List<Dog> m_dogs;
private ILogger<TimedWorker> m_logger;
private IConfiguration m_configuration;
public TimedWorker(ILogger<TimedWorker> logger, IConfiguration configuration)
{
m_logger = logger;
m_configuration = configuration;
}
public override Task StartAsync(CancellationToken cancellationToken)
{
m_dogs= new List<Dogs>();
List<DogOption> dogOptions= new List<DogOption>();
m_configuration.GetSection("Dogs").Bind(dogOptions);
foreach(DogOption option in dogOptions)
{
m_dogs.Add(new Dog(option, m_logger));
}
}
///...
}
class Dog {
public string Name { get; }
private int Age;
private ILogger m_logger;
public Dog(DogOption option, ILogger logger)
{
m_logger = logger;
Name = option.Name;
Age = option.Age;
m_logger.LogInformation($"Dog '{Name}' created");
}
}
The problem is that when it logs, the logger is of type TimedWorker, not Dog. So when I look at the logs, they are labeled under TimedWorker instead of Dog. I do not know how to pass in a ILogger into the Dog class. Any advice?
You can inject the IServiceProvider and resolve the dependency manually.
You can do it like this:
public class Worker
{
private readonly IServiceProvider _serviceProvider;
public Worker(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public void Start()
{
var specificLogger = _serviceProvider.GetService(typeof(ILogger<Dog>)) as ILogger<Dog>;
var dog = new Dog(specificLogger);
}
}
public class Dog
{
public Dog(ILogger<Dog> logger)
{
logger.LogInformation("Hello");
}
}
This creates the specific log based on the class.
2020-11-18 18:41:58.1275||INFO|ConsoleApp.Dog|Hello
You can create a logger directly. For example:
var logger = LoggerFactory.Create(options => {}).Create<Worker>();

Serilog writes duplicate entries in it's output

I have written a Windows Service which logs its operations using Serilog
The application also uses Autofac for dependency Injection support and Seq for structured logging.
using Autofac;
public class ContainerInitiator
{
public static IContainer BuildContainer()
{
var _builder = new ContainerBuilder();
var logger = LoggerUtility.CreateLogger();
_builder.RegisterInstance(logger).As<ILogger>().SingleInstance();
var container = _builder.Build();
ContainerFactory.SetContainer(container);
return container;
}
}
Here is Log utility class
public class LoggerUtility
{
private static string connectionString = ConfigurationManager.AppSettings["applicationName"];
public static ILogger CreateLogger()
{
return GetLoggerConfiguration().CreateLogger();
}
private static LoggerConfiguration GetLoggerConfiguration()
{
var config = new LoggerConfiguration()
.ReadFrom.AppSettings();
config = config.
Enrich.WithProperty("ApplicationName", connectionString).
Enrich.WithExceptionDetails().
Enrich.WithMachineName().
Enrich.WithProcessId().
Enrich.WithThreadId().
ReadFrom.AppSettings();
return config;
}
}
Here is my service class, which is logging the activities
public class Engine : IEngine
{
private readonly ILogger _logger;
public RedistributeEngine(IContainerFactory containerFactory)
{
_logger = containerFactory.GetInstance<ILogger>();
}
public void Start()
{
_logger.Information("Engine started!");
}
}
As you see below Seq logs, it has two entries on the same time!
You're reading the configuration from App.Settings twice:
private static LoggerConfiguration GetLoggerConfiguration()
{
var config = new LoggerConfiguration()
.ReadFrom.AppSettings(); // <<<<<<<<<<<< *#*#*#*#*#*#*#*#
config = config.
Enrich.WithProperty("ApplicationName", connectionString).
Enrich.WithExceptionDetails().
Enrich.WithMachineName().
Enrich.WithProcessId().
Enrich.WithThreadId().
ReadFrom.AppSettings(); // <<<<<<<<<<<< *#*#*#*#*#*#*#*#
return config;
}

IHttpContextAccessor null after injecting

I'm currently developing a system where dependent on what domain a request comes from different website settings need to be loaded (eg. default language id) which is then used in the rest of the application. These settings are stored in a class WebsiteSettings which are injected into the rest of the application when needed.
The first option I tried was registering a service to access the HttpContext by doing this in my ConfigureServices method:
public void ConfigureServices(IServiceCollection services)
{
//Register other services
services.TryAddScoped<IHttpContextAccessor, HttpContextAccessor>();
services.TryAddScoped<WebsiteSettingsFiller>();
services.TryAddScoped(typeof(WebsiteSettings), s =>
{
var settingFiller = s.GetService<WebsiteSettingsFiller>();
return settingFiller.Create();
});
}
Next, in my WebsiteSettingsFiller service, I inject the IHttpContextAccessor and some other services that I need to load the site settings.
public class WebsiteSettingsFiller
{
protected readonly IRepository Database;
private readonly IHttpContextAccessor _accessor;
private readonly StartupSitePropertyService _sitePropertyService;
private IQueryable<Site> AllSites => Database.All<Site>();
private IQueryable<SiteLanguage> AllSiteLanguages => Database.All<SiteLanguage>();
public WebsiteSettingsFiller(IRepository db, StartupSitePropertyService siteProperties, IHttpContextAccessor accessor)
{
Database = db;
_accessor = accessor;
_sitePropertyService = siteProperties;
}
public WebsiteSettings Create()
{
var domain = _accessor.HttpContext.Request.Host.Host; //null exception on this line
#if DEBUG
domain = "www.somewebsite.com";
#endif
var config = GetConfigByDomain(domain);
return config;
}
private WebsiteSettings GetConfigByDomain(string domain)
{
var site = AllSites.OrderByDescending(s => s.Created).FirstOrDefault(t => t.Host == domain);
if (site == null) return null;
var languages = AllSiteLanguages.Where(sl => sl.SiteId == site.Id).ToList();
//get more variables
return new WebsiteSettings
{
/* Set variables */
}
}
}
Example injection of WebsiteSettings:
public class RouteService : BaseService
{
private IDictionary<int, string> _routeLanguages = null;
private readonly WebsiteRedisService _websiteRedisService;
public RouteService(IRepository db,
WebsiteSettings settings,
WebsiteRedisService websiteRedisService)
: base(db, settings)
{
_websiteRedisService = websiteRedisService;
}
public async Task<IDictionary<int, string>> RouteLanguagesAsync()
{
return _routeLanguages ??
(_routeLanguages = await _websiteRedisService.SiteLanguagesToAsync(Settings.SiteId));
}
}
Sadly, no matter what I try the HttpContext reference is always null. Does anyone have any idea what I can try to resolve this? Or am I just approaching this problem the wrong way? Any suggestions are greatly appreciated!

Categories

Resources