I have the following .net core 2.0 code and the NLog is not output anything.
private static IServiceProvider BuildDi()
{
var services = new ServiceCollection();
services.AddTransient<Runner>();
services.AddSingleton<ILoggerFactory, LoggerFactory>();
services.AddSingleton(typeof(ILogger<>), typeof(Logger<>));
services.AddLogging((builder) => builder.SetMinimumLevel(LogLevel.Trace));
var serviceProvider = services.BuildServiceProvider();
var loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();
//configure NLog
loggerFactory.AddNLog(new NLogProviderOptions { CaptureMessageTemplates = true, CaptureMessageProperties = true });
NLog.LogManager.LoadConfiguration("nlog.config");
return serviceProvider;
}
And it starts to output after the following line is removed?
services.AddLogging((builder) => builder.SetMinimumLevel(LogLevel.Trace));
What is AddLogging for?
AddLogging basically does exactly what the two lines above it does in your original example.
And the delegate is used to configure the log builder.
In this case the minimum log level is trace.
If lets say it was set to Information the it would have ignored Trace and Debug logs.
The following should provide the same desired functionality if using .net core 2.0
private static IServiceProvider BuildDi() {
var services = new ServiceCollection();
services.AddTransient<Runner>();
// Adds logging services to the service collection
// If you don't explicitly set the minimum level, the default value is
// Information, which means that Trace and Debug logs are ignored.
services.AddLogging();
var serviceProvider = services.BuildServiceProvider();
var loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();
/// Enable NLog as logging provider in .NET Core.
loggerFactory.AddNLog(new NLogProviderOptions { CaptureMessageTemplates = true, CaptureMessageProperties = true });
// Apply NLog configuration from XML config.
loggerFactory.ConfigureNLog("nlog.config");
return serviceProvider;
}
Related
Im failing to build the following service, since configStoreService need to be injected to the AddInMemoryConfiguration, ConfigurationBuilder. So, is there a way to retrieve configStoreService and use it before building the service provider.
var services = new ServiceCollection();
var configStoreService = services.AddSingleton<ConfigurationStore>();
var configuration = new ConfigurationBuilder()
.AddInMemoryConfiguration(configStoreService)
.Build();
services.AddOptions();
services.Configure<Temp>(configuration.GetSection(typeof(Temp).Name));
services.RegisterOptionsType<Temp>(configuration);
_serviceProvider = services.BuildServiceProvider();
Foreword:
This seems a problematic request. You need configuration to configure services, but you need to configure services to get configuration. A chicken and egg problem.
What to do:
Create an instance of configuration use it to configure your services and also add it to your service collection
var configStoreService = new ConfigurationStore(location, sku);
services.AddSingleton(configStoreService);
var configuration = new ConfigurationBuilder()
.AddInMemoryConfiguration(configStoreService)
.Build();
services.AddOptions();
services.Configure<Temp>(configuration.GetSection(typeof(Temp).Name));
services.RegisterOptionsType<Temp>(configuration);
_serviceProvider = services.BuildServiceProvider();
FYI:
I also have to add you can't get anything from a IServiceCollection. You can get things from IServiceProviderwhich, you get AFTER registration is done and BuildServiceProvider called. So before that you can't access items in the collection.
The current version of the Microsoft.Azure.Functions.Extensions package exposes an additional property that allows you easy access to the IConfiguration provided to the function. Previously this required manually building a service provider, which was obviously problematic.
Using that package my FunctionsStartup.cs looks like this:
public override void Configure(IFunctionsHostBuilder builder)
{
base.Configure(builder);
var config = builder.GetContext().Configuration; // new in v1.1.0 of Microsoft.Azure.Functions.Extensions
var mySetting = config["MySetting"];
int.Parse(mySetting, out var mySetting);
// ... use mySetting...
}
In order to test my HTTP-triggered functions I've used this article as a base, which details how to manually build and start a host to execute my function as if it was running in Azure, similar to how TestServer works in ASP.NET Core:
var host = new HostBuilder()
.ConfigureWebJobs(new FunctionsStartup().Configure)
.Build();
var functionsInstance = ActivatorUtilities.CreateInstance<MyFunctions>(host.Services);
I can then execute the function methods defined on MyFunctions to test their responses:
var request = new DefaultHttpRequest(new DefaultHttpContext());
var response = (OkObjectResult)functionsInstance.HttpTriggerMethod(request);
... assert that response is valid
The problem is that when I run my tests, builder.GetContext().Configuration is returning null in FunctionsStartup.Configure, which of course causes those tests to fail. How can I work around this?
The article I linked to hasn't been updated to take into account the existence of builder.GetContext().Configuration, but you can make this work for testing purposes with a little tweaking. Instead of using:
var host = new HostBuilder()
.ConfigureWebJobs(new FunctionsStartup().Configure)
.Build();
you need to explicitly copy the host's settings into a new WebJobsBuilderContext that you then pass to your function's startup:
var host = new HostBuilder()
.ConfigureWebJobs((context, builder) => new FunctionsStartup().Configure(new WebJobsBuilderContext
{
ApplicationRootPath = context.HostingEnvironment.ContentRootPath,
Configuration = context.Configuration,
EnvironmentName = context.HostingEnvironment.EnvironmentName,
}, builder))
.Build();
I'm not sure if this is the completely correct way to achieve this, but it has worked well for me.
I need to change parameter value for IdentityOptions dynamically from db. So, in my ConsigureServices(...) method in Startup.cs:
services.AddIdentity<AppUser, IdentityRole>(option =>
{
option.Lockout.MaxFailedAccessAttempts = 3; // I need to set this value dynamically from database when server starts
}).AddEntityFrameworkStores<DataContext>()
.AddDefaultTokenProviders();
I have tried to inject IdentityOptions in my Configure(...) method but with no success:
public void Configure(
IApplicationBuilder app,
DataContext dataContext,
IdentityOptions identityOptions)
{
var sysPolicy = dataContext.SysPolicy.FirstOrDefault();
identityOptions.Lockout.MaxFailedAccessAttempts = sysPolicy.DisablePwdLoginFail;
}
It throws an exception like this (it seems that I can't inject it on my Configure):
System.Exception: Could not resolve a service of type 'Microsoft.AspNetCore.Identity.IdentityOptions' for the parameter 'identityOptions' of method 'Configure' on type 'App.Startup'.
You can try out this:
services.AddIdentity<AppUser, IdentityRole>(
options =>
{
var scopeFactory = services.BuildServiceProvider().GetRequiredService<IServiceScopeFactory>();
using var scope = scopeFactory.CreateScope();
var provider = scope.ServiceProvider;
using var dataContext = provider.GetRequiredService<DataContext>();
options.Lockout.MaxFailedAccessAttempts = dataContext.SysPolicy.FirstOrDefault();
})
.AddEntityFrameworkStores<DataContext>()
.AddDefaultTokenProviders();
NOTE: Building a service provider is an antipattern and will result in creating an additional copy of singleton services. I would suggest reading the configs from appsettings.json for example, then you can implement it without building the service provider
I am testing out the code directly out of here for a console app: https://learn.microsoft.com/en-us/azure/azure-monitor/app/ilogger#
I basically copied the code and pointed it to a new azure app insights instance. However, none of the logs are showing up in app insights. Am I missing anything?
static void Main(string[] args)
{
// Create DI container.
IServiceCollection services = new ServiceCollection();
// Add the logging pipelines to use. We are using Application Insights only here.
services.AddLogging(loggingBuilder =>
{
// Optional: Apply filters to configure LogLevel Trace or above is sent to ApplicationInsights for all
// categories.
loggingBuilder.AddFilter<ApplicationInsightsLoggerProvider>("", LogLevel.Trace);
loggingBuilder.AddApplicationInsights(******);
});
// Build ServiceProvider.
IServiceProvider serviceProvider = services.BuildServiceProvider();
ILogger<Program> logger = serviceProvider.GetRequiredService<ILogger<Program>>();
logger.LogCritical("critical message working");
// Begin a new scope. This is optional. Epecially in case of AspNetCore request info is already
// present in scope.
using (logger.BeginScope(new Dictionary<string, object> { { "Method", nameof(Main) } }))
{
logger.LogWarning("Logger is working - warning"); // this will be captured by Application Insights.
}
}
The code is correct, but you are hitting a known issue with ApplicationInsights and Console apps - the app is dying before ApplicationInsights can send the data to the backend. (data is not sent immediately, but batched and sent at intervals.)
Adding a sleep of ~30 secs should help your case.
Thread.Sleep(31000);
In regular console apps, docs suggest doing an explicit flush.
https://learn.microsoft.com/en-us/azure/azure-monitor/app/console#full-example
But in the ILogger case, you don't control the TelemetryClient instance. So your best alternative is to control the channel, and call flush on the channel followed by a small sleep. Modified code is given below.
class Program
{
static void Main(string[] args)
{
// Create DI container.
IServiceCollection services = new ServiceCollection();
var channel = new InMemoryChannel();
services.Configure<TelemetryConfiguration>(
(config) =>
{
config.TelemetryChannel = channel;
}
);
// Add the logging pipelines to use. We are using Application Insights only here.
services.AddLogging(loggingBuilder =>
{
// Optional: Apply filters to configure LogLevel Trace or above is sent to ApplicationInsights for all
// categories.
loggingBuilder.AddFilter<ApplicationInsightsLoggerProvider>("", LogLevel.Trace);
loggingBuilder.AddApplicationInsights("***");
});
// Build ServiceProvider.
IServiceProvider serviceProvider = services.BuildServiceProvider();
ILogger<Program> logger = serviceProvider.GetRequiredService<ILogger<Program>>();
logger.LogCritical("critical message working");
// Begin a new scope. This is optional. Epecially in case of AspNetCore request info is already
// present in scope.
using (logger.BeginScope(new Dictionary<string, object> { { "Method", nameof(Main) } }))
{
logger.LogWarning("Logger is working - warning"); // this will be captured by Application Insights.
}
channel.Flush();
Thread.Sleep(1000);
}
}
I've made some research, and there isn't much info about registering standard classes for manual DI. Experiencing lots of issues trying to implement unit tests in asp net core 2.1 app, here is the last one. Getting null refrence exception when trying to SignInAsync user created by UserManager - it seems it can't inject IServiceProvider instance
var userManager = Resolve<UserManager<ApplicationUser>>();
await signInManager.SignInAsync(userManager.FindByIdAsync(adminId), false); // here
Value cannot be null.
Parameter name: provider
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
at Microsoft.AspNetCore.Identity.SignInManager1.SignInAsync(TUser user, AuthenticationProperties authenticationProperties, String authenticationMethod)
at UnitTests.TestBase1.ConfigureIdentity() in C:\Users\alexa\source\repos\octopusstore\UnitTests\TestBase.cs:line 72
resolving like this
protected static ServiceCollection services = new ServiceCollection();
protected T Resolve<T>()
{
var serviceProvider = services.BuildServiceProvider();
return serviceProvider.GetRequiredService<T>();
}
how I am registering dependencies, it worked so far
services.AddDbContext<AppIdentityDbContext>();
var conf = new Mock<IConfiguration>();
services.AddSingleton<IConfiguration>(conf.Object);
services.AddSingleton<IServiceProvider, ServiceProvider>() ; // except for this
services.AddSingleton(services.BuildServiceProvider()); // and this
services.AddScoped<SignInManager<ApplicationUser>> ();
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<AppIdentityDbContext>()
.AddDefaultTokenProviders();
services.AddScoped<IAuthorizationService, DefaultAuthorizationService> ();
services.AddScoped<IAuthorizationPolicyProvider, DefaultAuthorizationPolicyProvider>();
services.AddScoped<IAuthorizationHandlerProvider, DefaultAuthorizationHandlerProvider>();
services.AddScoped<IAuthorizationHandlerContextFactory, DefaultAuthorizationHandlerContextFactory>();
services.AddScoped<IAuthorizationEvaluator, DefaultAuthorizationEvaluator>();
interesting that UserManager worked fine
To register the service provider itself, you can do it like this:
services.AddSingleton<IServiceProvider>(sp => sp);
This uses the factory function which you can use to get an instance using the service provider sp. Since you want to register the service provider itself, you can just return sp directly.