How to access Log4Net after adding it to Dependency Injection - c#

This is my first time working with Microsoft.Extensions.Logging.Log4Net.AspNetCore.
I have a WPF application where I add the Log4Net provider within the CreateDefaultBuilder in App method of App.xaml.cs. Immediately after, I want to write to the log file using LogInformation saying "Starting Application". From what I can tell, it does not seem like I can do this because of the way it has been added to the Dependency Injection container. I must either call a method that accesses the DI container in the parameter list or add Log4Net to the ServiceProvider and then retrieve the service that way. But, that does not seem right because I will effectively have Log4Net added to DI twice.
Is there a way to immediately access the DI container after configuring Log4Net so I can write to the log file?
Host = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder()
.ConfigureAppConfiguration((context, appBuilder) =>
{
// Do some stuff here
}).ConfigureLogging(logBuilder =>
{
logBuilder.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
logBuilder.AddLog4Net("log4net.config");
})
.ConfigureServices((context, services) =>
{
Configuration = context.Configuration;
ConfigureServices(Configuration, services);
})
.Build();
ServiceProvider = Host.Services;
// How to access Log4Net here?
_ISomeLogger.LogInformation("Starting Application");
Update
I am using the following example: https://www.thecodebuzz.com/logging-using-log4net-net-core-console-application/

The solution is to access the Log4Net from the ServiceProvider right after building it:
logger = (ILogger<Program>)ServiceProvider.GetService(typeof(ILogger<Program>));

Related

Configure a logging provider for a different service collection

This is an ASP.NET application in .NET 6. There's a wizard interface where the user inputs some data and then based on the input, I set up a new dependency injection container with the services that are required to complete the task. My problem is that the ILogger<> instances coming out of this second container don't use the custom ILoggingProvider that I set up.
Program.cs:
var builder = WebApplication.CreateBuilder(args);
builder.Logging.ClearProviders();
builder.Logging.AddDebug();
builder.Logging.AddSignalRLogging();
public static class ILoggingBuilderExtensions
{
public static ILoggingBuilder AddSignalRLogging(this ILoggingBuilder builder)
=> builder.AddProvider(new SignalRLoggerProvider(builder.Services))
.AddFilter<SignalRLoggerProvider>("MyNamespace", LogLevel.Information);
}
The SignalRLoggerProvider comes from How to implement an ILogger to send messages to a SignalR Hub?.
Controller:
IServiceCollection services = new ServiceCollection();
services.AddLogging();
services.AddSignalR();
services.AddSingleton(_sheetsClient); // this was injected into the controller
services.AddSingleton<ITeamReaderService>(new PostedTeamReaderService(model.Divisions));
string[] divisionNames = model.Divisions.Keys.ToArray();
foreach (string divisionName in divisionNames)
{
services.AddSingleton<IDivisionSheetService>(provider => new DivisionSheetService(divisionName,
provider.GetRequiredService<StandingsRequestCreatorFactory>(),
provider.GetRequiredService<ISheetsClient>(),
provider.GetRequiredService<IOptionsMonitor<ScoreSheetConfiguration>>(),
provider.GetRequiredService<ILogger<DivisionSheetService>>())
);
}
I know my provider works because when I log things in a controller whose dependencies were injected from the HostBuilder's service collection (_sheetsClient), those messages work correctly. In classes that come from this other container (DivisionSheetService), those log messages go nowhere and when I view the ILogger instance in the debugger, it shows that it has no logger that it's writing to.
So it must be the case that my custom logging provider is unknown to the second container, but I can't figure out how to register it there.
Thanks in advance.
Since you're creating a new ServiceCollection from scratch, you also need to add the logging infrastructure from scratch:
IServiceCollection services = new ServiceCollection();
services.AddLogging(builder => builder.AddDebug().AddSignalRLogging());

DbContext connection string and hosted service

I have a. NET core console app that implement IHostedService and a reference to another project with a DbContext definition.
This is the configuration of DbContext in console app:
IHost host = new HostBuilder()
.ConfigureHostConfiguration(configHost =>
{
configHost.SetBasePath(Directory.GetCurrentDirectory());
configHost.AddEnvironmentVariables(prefix: "ASPNETCORE_");
configHost.AddCommandLine(args);
})
.ConfigureAppConfiguration((hostContext, configApp) =>
{
configApp.SetBasePath(Directory.GetCurrentDirectory());
configApp.AddEnvironmentVariables(prefix: "ASPNETCORE_");
configApp.AddJsonFile($"appsettings.json", true);
configApp.AddJsonFile($"appsettings.{hostContext.HostingEnvironment.EnvironmentName}.json", true);
configApp.AddCommandLine(args);
})
.ConfigureServices((hostContext, services) =>
{
services.AddDbContext<MyAppDbContext>(options => options.UseNpgsql(hostContext.Configuration.GetConnectionString("DefaultConnection")));
services.AddHostedService<ApplicationLifetimeHostedService>();
})
.Build();
Now, in the OnStarted() method of ApplicationLifetimeHostedService I have:
using (var _context = new MyAppDbContext())
{
...
_context.SaveChanges();
}
Why MyAppDbContext take the connection string value from OnConfiguring method of dbcontext definition class (hard-coded, generated from scaffolding), and not from appsettings.{ASPNETCORE_ENVIROMENT}.json ()?
Thank you in advance!
Based on your configuration, currently the IHostBuilder is for non web applications and simulates a generic configuration, eventually this will replace the IWebHostBuilder. However, you do not need those... In your instance you would be better off with CreateDefaultBuilder.
Host Configuration
App Configuration
Both are provided by default, with more granular control. The primary item is the default services provided by the builder and what they compile or build.
To directly answer your issue though, in your top line, you are missing the following:
var host = new HostBuilder()
.ConfigureHostConfiguration(configuration =>
{
// For brevity, removed some.
configuration.AddJsonFile("appsettings.json", false, true);
}
That is why your appsettings.json is not working. The ConfigureHostConfiguration will carry through to the ConfigureAppConfiguration.
Host configuration automatically flows to app configuration
(ConfigureAppConfiguration and the rest of the app).
No providers are included by default. You must explicitly specify
whatever configuration providers the app requires in
ConfigureHostConfiguration, including:
File configuration (for example, from a hostsettings.json file).
Environment variable configuration.
Command-line argument configuration.
Any other required configuration providers.

How to get the ServiceContext of a Reliable Service in Service Fabric?

The ServiceContext of a reliable service in Service Fabric is registered with the runtime (DI container) in the program.cs of the service:
ServiceRuntime.RegisterServiceAsync("RelDictQuerySvcType",
context => new RelDictQuerySvc(context)).GetAwaiter().GetResult();
How can I get that ServiceContext back from the DI container? There is no property on the ServiceRuntime to get it back. Also, I did not find it via the FabricClient. Do I need to put the context on an own static class in the service constructor to be able to get a reference to it somewhere else in my code?
Service Fabric does not really has a build-in DI mechanism, at least it is a very simple one.
If you want to inject dependencies into you services itself you can use a factory. for example:
ServiceRuntime.RegisterServiceAsync("MyStatelessType",
context =>
{
var loggerFactory = new LoggerFactoryBuilder(context).CreateLoggerFactory(applicationInsightsKey);
ILogger logger = loggerFactory.CreateLogger<MyStateless>();
return new MyStateless(context, logger);
}).GetAwaiter().GetResult();
this is a way to inject concrete implementations in your service. This mechanism is used to inject the context as well. Unfortunately, since it is not a full fledged DI container you cannot get this context outside the service instance itself.
So, you have to bring your own DI container to really use it, for example in a stateless web api you can do something like:
protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
{
return new[]
{
new ServiceInstanceListener(serviceContext =>
new WebListenerCommunicationListener(serviceContext, "ServiceEndpoint", (url, listener) =>
{
logger.LogStatelessServiceStartedListening<WebApi>(url);
return new WebHostBuilder().UseWebListener()
.ConfigureServices(
services => services
.AddSingleton(serviceContext)
.AddSingleton(logger)
.AddTransient<IServiceRemoting, ServiceRemoting>())
.UseContentRoot(Directory.GetCurrentDirectory())
.UseServiceFabricIntegration(listener, ServiceFabricIntegrationOptions.None)
.UseStartup<Startup>()
.UseUrls(url)
.Build();
}))
};
}
Otherwise you have to do it yourself. There are some initiatives already, see this one for an AutoFac extensions and there is also a Unity extensions.

.NET Core with Autofac DI

I want to integrate Autofac to my API. Solution is split on several projects so that everything stays decoupled. I have set up my configure services like this:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
// Add framework services.
...
...
// Autofac
var builder = new ContainerBuilder();
builder.RegisterType<RouteRepository>().As<IRouteRepository>();
builder.Populate(services);
ApplicationContainer = builder.Build();
return new AutofacServiceProvider(ApplicationContainer);
}
However now with this code integrated, my API won't start anymore. If I start it in debug mode, I get no errors, but I don't get response either.
API landing route is pretty straightforward:
public IActionResult GetIndex()
{
return Ok("You are seeing this because controller is working!");
}
Also, what might be connected to the problem is that RouteRepository takes one variable as an argument in the constructor and I don't know where can I define what will be passed through? There is no config file by default.
If you're saying that you have one dependency for your RouteRepository, then you have to notify Autofac container how to resolve that:
// singletone
builder.RegisterInstance(new TaskRepository())
.As<ITaskRepository>();
// or instance based creation
builder.Register(c => new LogManager(DateTime.Now))
.As<ILogger>();
Or Autofac couldn't resolve your type.

Inject DataProtection based on injected user options IOptions<> in ConfigureServices

I'm a little bit confused here regarding how to inject DataProtection in ConfigureServices(IServiceCollection services) based on user settings being as well injected from services.Configure<UserSettingsConfig>(Configuration.GetSection("UserSettings"));.
The values appName_from_appsettings_json and dirInfo_from_appsettings_json below should be coming from the injected UserSettingsConfig and would be accessible anywhere else by injecting IOptions<UserSettingsConfig> but not here.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.Configure<UserSettingsConfig>(Configuration.GetSection("UserSettings"));
services.AddMvc();
services.AddScoped<DevOnlyActionFilter>();
services.AddDataProtection()
.SetApplicationName(appName_from_appsettings_json)
.PersistKeysToFileSystem(dirInfo_from_appsettings_json);
}
I've found ways to achieve my goals without using DI with code like var sharedDataProtectionAppName = configuration.GetValue<string>("UserSettings:SharedDataProtection:ApplicationName");
I had the feeling I have found the solution in this article http://andrewlock.net/access-services-inside-options-and-startup-using-configureoptions/ by it seems like I can't figure out how to apply it to my case. I would need a way to inject DataProtection based on values from the injected IOptions<UserSettingsConfig>. What would be the cleanest way to do that in your opinion?
UPDATE: I found a solution based on that type of code that I could be calling from ConfigureServices, but I still wonder if it's the best way.
var userSettingsConfig = services.BuildServiceProvider().GetServices<IOptions<UserSettingsConfig>>().First();
You could also use the extension method .Bind(). This method will try to bind the value to the Configuration object by matching the keys from the configuration.
// Add framework services.
var userSettingsConfig = new UserSettingsConfig();
Configuration.GetSection("UserSettings").Bind(userSettingsConfig);
services.Configure<UserSettingsConfig>(Configuration.GetSection("UserSettings"));
services.AddMvc();
services.AddScoped<DevOnlyActionFilter>();
services.AddDataProtection()
.SetApplicationName(userSettingsConfig.appName)
.PersistKeysToFileSystem(userSettingsConfig.DirInfo);

Categories

Resources