Unity container factory with dependency on outside data - c#

I am writing a C# .NET Core 5.0 console application. This application uses CommandLineUtils to process command line arguments, Unity Container for DI, and Serilog for logging.
I am registering Serilog in my composition root:
public static void Setup(IUnityContainer container)
{
container.RegisterFactory<ILogger>(_ => new LoggerConfiguration()
.WriteTo.Console()
.CreateLogger(),
new SingletonLifetimeManager());
}
However, I have a command line argument, --debug, that should reveal DEBUG level logs. If this option is not specified, it should keep the default INFO level. From the Serilog examples, the way they set DEBUG level is by adding calls to LoggerConfiguration object (i.e. MinimumLevel.Debug())
But I don't know if I need to call this until after CLI processing takes place, which happens after I define the composition root.
Seems like I'm in a catch 22 situation. I know it's bad practice to RegisterType() outside of the root of the application. How should I solve this circular dependency?
EDIT
Here is my Program class which shows the order of things:
internal static class Program
{
private static IUnityContainer Container { get; } = new UnityContainer();
private static void Main(string[] args)
{
CompositionRoot.Setup(Container);
var app = new CommandLineApplication<TrashCommand>();
app.Conventions
.UseDefaultConventions()
.UseConstructorInjection(new UnityServiceProvider(Container));
app.Execute(args);
Log.CloseAndFlush();
}
}

Perhaps I misinterpreted your question, but doesn't a construct such as the following answer your question?
public static void Setup(IUnityContainer container, bool logDebug)
{
LogEventLevel level = logDebug ? LogEventLevel.Debug : LogEventLevel.Info.
container.RegisterFactory<ILogger>(_ => new LoggerConfiguration()
.WriteTo.Console()
.MinimumLevel.Is(level)
.CreateLogger(),
new SingletonLifetimeManager());
}
Or, alternatively:
public static void Setup(IUnityContainer container, bool logDebug)
{
LogEventLevel level = logDebug ? LogEventLevel.Debug : LogEventLevel.Info.
container.RegisterFactory<ILogger>(_ =>
{
var config = new LoggerConfiguration().WriteTo.Console();
if (logDebug) config = config.MinimumLevel.Is(level);
return config.CreateLogger();
},
new SingletonLifetimeManager());
}

Related

How Can I Retrieve Command Line Arguments Injected Into IConfigurationBuilder?

I'd like to pass args (which are of type string[]) to my IConfigurationBuilder available via the Microsoft.Extensions.Hosting NuGet package, and then retrieve it later on. I notice that IConfigurationBuilder has AddCommandLine, but I'm not sure where these args end up, or how I can access them. Ideally I'd like them dependency injected into a constructor of a class I later defined in ConfigureServices, as shown below (SomeHostedService).
For example, I have the following piece of code.
public static class HostBuilder
{
public static IHost BuildHostContext(string[] args) =>
Host.CreateDefaultBuilder(args).ConfigureHostConfiguration(c =>
{
c.AddCommandLine(args); // Where can I access args later on?
}).ConfigureServices((context, services) =>
{
// Add some services...
services.AddHostedService<SomeHostedService>();
// Would ideally like args dependency injected in SomeHostedService
}).Build();
}
How can I dependency inject these args into the constructor of SomeHostedService.
If I wanted to explicitly gain access to them, how can I retrieve them? Are they attached to the IServiceCollection somewhere?
EDIT: Constructor of SomeHostedService should take in a string[] which corresponds to args passed into the application.
public class SomeHostedService
{
public SomeHostedService(string[] files)
{
_files = files;
}
public void ReadFiles()
{
foreach (var file in _files)
DoSomething(file);
}
private void DoSomething(string filename)
{
// Do something...
}
private readonly string[] _files;
}
My entry point to the application would look as such (C# 9):
using Microsoft.Extensions.DependencyInjection;
using System;
using var host = HostBuilder.BuildHostContext(args);
using var scope = host.Services.CreateScope();
await host.StartAsync().ConfigureAwait(false);
To access the Configuration Providers, include the IConfiguration in your constructor for any of your Dependency Injection served classes.
public class MyClass:IMyClass{
private IConfiguration _configuration;
public MyClass(IConfiguration configuration){
_configuration=configuration;
}
}
To bind your arguments to a model, see this article on Microsoft Learn: https://learn.microsoft.com/en-us/dotnet/standard/commandline/model-binding

How do you inject in dotnet core project in static method an IConfiguration object?

I have a Redis store similar to this one. My problem is, since I am using .Net Core, on line 15, I should use configuration object that I normally inject in the constructor.
However, one cannot inject the configuration object in the static constructor, as static constructor should be parameterless in C#.
I tried adding a static method to initialise the config object, but then the constructor throws NullReferenceException because obviously the ctor is still called first, before the Init method, and it needs the config object... so what to do?
Doesn't seem like a good workaround.
Instead of doing all that work with statics and trying to get it to work (hint: it'd never work with a static constructor), I'd suggest you to move to newer patterns and use DI correctly.
If you don't really need the lazyness, this is as simple as injecting IConnectionMultiplexer:
services.AddScoped<IConnectionMultiplexer>(s => ConnectionMultiplexer.Connect(configuration["someSettings"]));
If you do need the lazyness:
// public interface IRedisStore { IConnectionMultiplexer RedisConnection { get; } }
public class RedisStore : IRedisStore
{
private readonly Lazy<ConnectionMultiplexer> LazyConnection;
public RedisStore(IConfiguration configuration)
{
var configurationOptions = new ConfigurationOptions
{
EndPoints = { configuration["someSettings"] }
};
LazyConnection = new Lazy<ConnectionMultiplexer>(() => ConnectionMultiplexer.Connect(configurationOptions));
}
public IConnectionMultiplexer RedisConnection => LazyConnection.Value;
}
and you'd inject it with:
services.AddScoped<IRedisStore, RedisStore>());

PostSharp - logger as an aspect argument

Want to use PostSharp diagnostics with an aspect for exception handling encapsulated in a library assembly.
At the same time trying to setup and initialize a Serilog logger in the consumer assembly of that diagnostics library.
[AspectTypeDependency(AspectDependencyAction.Order, AspectDependencyPosition.After,
typeof(AddContextOnExceptionAttribute))]
[PSerializable]
public sealed class ReportAndSwallowExceptionAttribute : OnExceptionAspect
{
public ILogger TheLogger { get; set; }
public ReportAndSwallowExceptionAttribute(ILogger logger)
{
TheLogger = logger;
}
public override void OnException(MethodExecutionArgs args)
{
TheLogger.Error("Error happened ...");
}
}
In the main class:
class Program
{
// below line is the critical part which seems ILogger is not allowed
[ReportAndSwallowException(Log.Logger)]
public static void TryExceptionMethod()
{
throw new NotImplementedException();
}
static void Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
.WriteTo.File(#"HERE\Logs\log-x.log",
rollingInterval: RollingInterval.Day)
.CreateLogger();
Console.WriteLine("Hello World!");
TryExceptionMethod();
}
}
Seems passing ILogger to that attribute is illegal, how can I achieve this scenario?
Current Error:
Error CS0181: Attribute constructor parameter 'logger' has type 'ILogger', which is not a valid attribute parameter type
Think this error needs a constant to be solved, but the main question is how to achieve this scenario: have the logger in the consumer proj, have the aspects in a library.
The easiest option here is that your aspect directly references Log.Logger.
There are several other more complex options that are documented here: https://doc.postsharp.net/consuming-dependencies

GetRequiredService<DbContextOptions<MovieContext>> v.s. GetRequiredService<MovieContext>

I am reading this tutorial and found two approaches used by the author to obtain MovieContext.
In SeedData.Initialize, MovieContext is obtained as follows.
public static class SeedData
{
public static void Initialize(IServiceProvider isp)
{
DbContextOptions<MovieContext> options =
isp.GetRequiredService<DbContextOptions<MovieContext>>();
using (var context = new MovieContext(options))
{
// trimmed for simplicity
}
}
}
But in Program.Main, the context is obtain as follows.
public class Program
{
public static void Main(string[] args)
{
IWebHost iwh = BuildWebHost(args);
using (IServiceScope iss = iwh.Services.CreateScope())
{
IServiceProvider isp = iss.ServiceProvider;
try
{
MovieContext context = isp.GetRequiredService<MovieContext>();
// trimmed for simplicity
}
}
}
}
Question
Is there any difference between
new MovieContext(isp.GetRequiredService<DbContextOptions<MovieContext>>());
and
isp.GetRequiredService<MovieContext>();
where isp is of type IServiceProvider ?
Is there any difference between the two approaches.
In the first example you manually instantiate the context and inject its explicit dependency by using the container to resolve and instantiate the options (Service Locator).
In the second example the container handles everything. It will resolve the option and inject it into the context when it is being resolved.
When do we need to do the former and the latter approach?
Totally a matter of preference. Both can be done as the end result is the same depending on how the context was registered with the IoC container.

Accessing a static variable from different assemblies

I have an ASP.NET application and a Windows Service. I am using Unity as the IoC container. I placed the Composition Root in a seperate class library, because both applications are supposed to use the same DI container.
DI bootstrapper:
namespace CompositionRoot {
public static class DiBootstrapper {
private static IUnityContainer _container;
public static IUnityContainer Initialize() {
return _container ?? (_container = BuildUnityContainer());
}
private static IUnityContainer BuildUnityContainer() {
var container = new UnityContainer();
container.AddNewExtension<ContainerExtension>();
return container;
}
}
public class ContainerExtension: UnityContainerExtension {
protected override void Initialize() {
var connectionString = ConfigurationManager.ConnectionStrings["KSecureEntities"].ConnectionString;
var sqlCtorParam = new InjectionConstructor(connectionString);
this.Container.RegisterType < Acquaintance.RSS.IRssRepository, RssRepository > (new ContainerControlledLifetimeManager());
this.Container.RegisterType < IRssFeedRepository, RssFeedRepository > (new TransientLifetimeManager(), sqlCtorParam);
this.Container.RegisterType<IRssTicker, RssTicker>(new ContainerControlledLifetimeManager());
this.Container.RegisterType < RssTickerHub > (new InjectionFactory(RssTickerHub));
....
}
private static object RssTickerHub(IUnityContainer p) {
var rssRepository = p.Resolve < IRssFeedRepository > ();
var rssTicker = p.Resolve < IRssTicker > ();
var rssTickerHub = new RssTickerHub(rssRepository, rssTicker);
return rssTickerHub;
}
}
}
The first project to run Initialize() on the DiBootstrapper is the Windows Service. When the method is run, the _container variable is set.
Afterwards the ASP.NET application runs Initialize() from Application_Start(), but this time the variable is null, and the container gets instantiated again.
How can I share the same container across both projects?
You cannot.
It does not work that way. You have two different processes, the Windows Service and the process your webserver spuns up when your ASP.NET is called. They both load the library assembly, but each loads his own copy into memory. That's how processes work with libraries.
There is no easy solution. You cannot share objects that way. You will need to find another solution. If you don't have singleton lifetimes in your DI container, just having the same configuration for both processes should be enough. if you want to share objects between processes, you might be best served by asking a new question on how to do that in a specific situation because there are many ways to achieve a goal without doing it, but we need to know your goal to give such an answer.

Categories