I have a series of .NET Core worker services (targeting .NET 6). I am accessing configuration data from appsettings.json through hostcontext and that works fine:
public static async Task Main(string[] args)
{
// Start the application
await CreateHostBuilder(args).Build().RunAsync();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseWindowsService()
.ConfigureServices((hostContext, services) =>
{
// I can access config data like this:
var connectionString = hostcontext.Configuration["ConnectionStrings:MyConnectionString"];
});
However, I now need to get configuration data from within Main() before CreateHostBuilder() is called.
I can do something like this, but is there a better way? It feels wrong to build a ConfigurationBuilder just to grab this one value, is there a more efficient way to do this?
public static async Task Main(string[] args)
{
// Get the configuration
var environmentName = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? "Production";
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{environmentName}.json", true, true);
var configuration = builder.Build();
// Start the application
await CreateHostBuilder(args).Build().RunAsync();
}
In a controller method, the generic logger doesn't seem to have the defined enrichers.
Here is the controller:
public class TestController : Controller
{
ILogger _logger;
public TestController(ILogger<TestController> logger)
{
_logger = logger;
}
public IActionResult action()
{
try
{
throw new NullReferenceException();
}
catch(Exception e)
{
Serilog.Log.Error(ex, "action KO");
_logger.LogError("action KO", ex);
}
}
}
The appsettings.json:
{
"Serilog": {
"MinimumLevel": {
"Default": "Debug",
},
"WriteTo": [
{
"Name": "Console"
},
{
"Name": "File",
"Args": {
"path": "Log/api.log",
"outputTemplate": "{Timestamp} [{Level:u3}] ({SourceContext}) {Message}{NewLine}{Exception}",
"rollingInterval": "Day",
"retainedFileCountLimit": 7
}
}
],
"Enrich": [
"FromLogContext",
"WithExceptionDetails"
]
}
}
Host building:
IHost host = Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
webBuilder.UseUrls($"http://*:12345");
})
.UseSerilog((hostingContext, loggerConfiguration) => loggerConfiguration
.ReadFrom.Configuration(hostingContext.Configuration)
)
.Build()
;
Output in file / console:
02/18/2021 12:24:57 +01:00 [ERR] () action KO
System.NullReferenceException: Object reference not set to an instance of an object.
at App.TestController`1.action()
02/18/2021 12:24:57 +01:00 [ERR] (App.TestController) action KO
So when I try to use a generic logger, the exception is omitted. Wheras the static logger writes it.
Am I missing something like a provider for controllers logger or is it meant to be done by UseSerilog?
EDIT
Tried UseSerilog with writeToProviders: true => no effect
Tried AddSerilog as a logging builder => no effect
services.AddLogging(loggingBuilder => loggingBuilder.AddSerilog(
new LoggerConfiguration().ReadFrom.Configuration(Configuration).CreateLogger(), true));
Tried AddSerilogServices => no effect
public static IServiceCollection AddSerilogServices(
this IServiceCollection services,
LoggerConfiguration configuration)
{
Log.Logger = configuration.CreateLogger();
AppDomain.CurrentDomain.ProcessExit += (s, e) => Log.CloseAndFlush();
return services.AddSingleton(Log.Logger);
}
First, please change
_logger.LogError("action KO", ex);
to
_logger.LogError(ex, "action KO");
Testing the following try/catch
try
{
throw new NullReferenceException();
}
catch (Exception ex)
{
_logger.LogError(ex, "action KO");
}
... writes this to log file:
02/27/2021 22:55:59 +01:00 [ERR] (MyMicroservice.Controllers.WeatherForecastController) action KO
System.NullReferenceException: Object reference not set to an instance of an object.
at MyMicroservice.Controllers.WeatherForecastController.Get() in C:\Prosjekter\MyMicroservice\WebApp\Controllers\WeatherForecastController.cs:line 51
After checking your configuration with doc, and some testing, the part you've added to your question, seems OK to me.
I've added some words about an interesting finding during testing and reading the docs, and finally there is a Program.cs you may want to have a look at.
TL;DR: Serilog recommends two-stage initialization in order to have a
temporary logger before starting the host. The code below shows
how to skip stage #1 with a tiny change and still get a logger before starting the host.
Serilog.AspNetCore doc:
https://github.com/serilog/serilog-aspnetcore#inline-initialization
At the very beginning of Program#Main, you will have a Serilog.Core.Pipeline.SilentLogger.
If you follow the recommendations, you will have a Serilog.Extensions.Hosting.ReloadableLogger after stage #1.
Stage #1 looks like this and requires Nuget Serilog.Extensions.Hosting
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
.Enrich.FromLogContext()
.WriteTo.Console()
.CreateBootstrapLogger();
In order to try to save us for some code lines and an additional dependency, comment out stage #1, and let's try the following approach to see if we can get an initialized logger before starting the web host.
var webHost = CreateHostBuilder(args).Build();
After this line, we do have an instance of Serilog.Core.Logger, which is the same as we'll end up with when using CreateHostBuilder(args).Build().Run(). Hence, I ended up with the below Program.cs where I omitted stage #1 entirely, but kept stage #2.
This should not have any side-effects, doc says:
To address this, Serilog supports two-stage initialization. An initial
"bootstrap" logger is configured immediately when the program starts,
and this is replaced by the fully-configured logger once the host has
loaded.
Please note that after commenting out lines in code from doc, UseSerilog part is now equal to config from question.
I'm using appsettings.json from your question.
I have no Serilog config in Startup.cs.
using System;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Serilog;
using Microsoft.Extensions.DependencyInjection;
namespace MyMicroservice
{
public class Program
{
public static void Main(string[] args)
{
// Serilog.Core.Pipeline.SilentLogger at this stage
var webHost = CreateHostBuilder(args).Build();
// Serilog.Core.Logger at this stage
// proof-of-concept: This will log to file before starting the host
var logger = webHost.Services.GetService<ILogger<Program>>();
logger.LogWarning("Hello from Program.cs");
try
{
Log.Information("Starting up");
webHost.Run();
}
catch (Exception ex)
{
Log.Fatal(ex, "Application start-up failed");
}
finally
{
Log.CloseAndFlush();
}
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseSerilog((context, services, configuration) => configuration
.ReadFrom.Configuration(context.Configuration)
/*.ReadFrom.Services(services) not required for this case */
/*.Enrich.FromLogContext() already configured in appsettings.json */
/*.WriteTo.Console() already configured in appsettings.json */
)
.ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup<Startup>());
}
}
I had the following error in package manager console when Add-Migration
Value cannot be null. Parameter name: connectionString
This is my startup:
namespace MyProject
{
public class Startup
{
public IConfiguration Configuration { get; set; }
public Startup(IConfiguration config)
{
Configuration = config;
}
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContextPool<AppDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddTransient<IDevRepo, DevRepo>();
services.AddMvc();
services.AddMemoryCache();
services.AddSession();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseStatusCodePages();
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
app.Run(async (context) =>
{
await context.Response.WriteAsync(Configuration["Message"]);
});
}
}
}
program class:
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, builder) => builder.SetBasePath(context.HostingEnvironment.ContentRootPath)
.AddJsonFile("appsettings.json")
.Build())
.UseStartup<Startup>()
.Build();
}
appsettings.json:
{
"Message": "Hello World",
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\MSSQLLocalDB;Database=NotMyFault;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
Interestingly if I run the app, it displays "Hello World", but when add migration it cannot find connectionString. Can someone please shed some lights here? Thanks.
This problem occurred when the connection string can't be found.
Probably you have the following code in Startup class:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<BenchmarkContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("yourConnectionString name from appsettings.json")));
}
These methods solve your problem:
1- Instead of Configuration.GetConnectionString("yourConnectionString name from appsettings.json(in develop mode: 'appsettings.Development.json')") just put your connectionstring.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<BenchmarkContext>(options =>
options.UseSqlServer("Data Source=.;Initial Catalog=Benchmark;Persist Security Info=True;User ID=****;Password=****"));
}
2- If you are going to use the Configuration file add these codes to Startup class:
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration;
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<BenchmarkContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("TestConnection")));
}
Appsettings.json file(in develop mode: 'appsettings.Development.json'):
{
"ConnectionStrings": {
"TestConnection": "Data Source=.;Initial Catalog=Benchmark;Persist Security Info=True;User ID=****;Password=****"
}
}
After that execute 'add-migration name' command in Package Manager Console
I had the same issue, but my solution was a lot simpler. All I did was to change the order of the appsettings.json from:
{
"Message": "Hello World",
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\MSSQLLocalDB;Database=NotMyFault;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
to:
{
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\MSSQLLocalDB;Database=NotMyFault;Trusted_Connection=True;MultipleActiveResultSets=true"
},
"Message": "Hello World"
}
I have a suspicion that there is a sequence/order of parameters in the appsettings.json file.
I had such issue when load tesing the service (I recommend it to all) and had ~3/1000 requests with errors,
so I changed
services.AddDbContextPool<AppDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
to
string connectionString = Configuration.GetConnectionString("DefaultConnection");
services.AddDbContextPool<AppDbContext>(options =>
options.UseSqlServer(connectionString));
So it reads connections string 1 time and doesn't use Configuration on every request. And now 100% requests are successful.
But it seems to be a bug in .Net Core
I found my own problem.
I have an AppDbContextFactory class which inherits IDesignTimeDbContextFactory. Deleting this class resolves this issue.
I had the same problem, because I was using the default value in Startup.cs.
I just edited Configuration property from:
public IConfiguration Configuration { get; }
to:
public IConfiguration Configuration;
and it worked!
If someone say why would be appreciated.
I had had a similar issue because of the following reasons:
appsettings.json was not included in the project
I was running the project from the path which did not contain appsettings.json
I had the same error and resolved it by moving "ConnectionStrings" to be the first variable in the appsettings.json file.
Probably, the issue is with your DotNetCliToolReference from the csproj file. If you migrate the project from an older version of asp.net core, the DotNetCliToolReference is not automatically updated.
Update the yourproject.csproj file to use the 2.0.0 version of the CLI as shown in the snippet bellow:
<ItemGroup>
...
<DotNetCliToolReference
Include="Microsoft.EntityFrameworkCore.Tools.DotNet"
Version="2.0.0" />
</ItemGroup>
Rerun, from the project folder, the dotnet command with -v switch to see results
dotnet ef database update -v
Also, recheck your Microsoft.EntityFrameworkCore nuget packages to reference the 2.0.0 version. Remove or update older EF packages. The minimum are:
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.EntityFrameworkCore.Design
both 2.0.0 at this moment.
I had the same problem and what it is the I had to make sure that the name of the connection matches:
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddDbContext<MyContext>(options => options.UseSqlServer(Configuration["ConnectionStrings:DefaultConnection"]));
which ****ConnectionStrings:DefaultConnection*** it was where I had the whole problem.
Make sure that is the same in Startup.cs and appsettings.json(appsettings.Development.json in Vs 2019)
After I fixed this, everything was fine.
I had a similar issue. I had a typo in my appsettings.json. Changing ConnectionsStrings to ConnectionStrings did it for me!
I have solved my issue by setting right base path. The problem is the migrations or anything else from different packages uses wrong path to the appsetting.json file. Not sure if it's an official issue.
I have just changed my Startup.cs as follows:
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
After that you just need to copy your appsettings.json to the right place if it's missing there.
This worked flawlessly for me:
public IConfiguration Configuration;
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext.ApplicationDbContext>(options =>
//options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
options.UseSqlServer("Server=serverAddress; Database=dbName; user=username; password=pswd"));
}
The commented part is just as reference where to replace.
Another scenario can be where you set the configuration. set the connection string in appsettings.json instead of appsettings.Development.json
I had a similar problem when I specified the ".UseContentRoot" as the current process path.
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseUrls("http://*:3001")
.UseStartup<Startup>()
.UseContentRoot(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName));
thus when running Add-Migration the process path is different from the project bin path therefore the process can't find the appsettings.json file.
when I removed the ".UseContentRoot" line the migration was successful
I'm stupid and I had typo
{
"Conn'ce'tionStrings": {
"DefaultConnection": "Data source=datingapp.db"
},
changed it to
{
"ConnectionStrings": {
"DefaultConnection": "Data source=datingapp.db"
},
I had a similar problem after trying to use new created project for ASP.NET Core 2.0 Web Api. As far as I found, the cause of the problem was that application settings specified for development environment were not added. I fixed it by updating startup file to the following:
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
this.Configuration = builder.Build();
}
In my case program class looks like the following:
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
}
My problem was when I was trying to run App.dll within netcoreapp2.1 folder, but the right folder is netcoreapp2.1\publish\
If you have previously renamed your connection string in appsettings file and you have omitted to rename it in DesignTimeDbContextFactory class (if you have it in your project) and that is checked by Entity framework, then you may run in this issue.
If you are using an IDesignTimeDbContextFactory, you will need to add a default constructor to it with no parameters. Try something like this:
public DbContextFactory()
{
IConfigurationRoot configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddEnvironmentVariables()
.AddJsonFile("appsettings.json", false, true)
.Build();
_connectionString = configuration.GetConnectionString("ConnectionStringName");
}
For me it was that I had appSettings.json instead of appsettings.json for some reason (not quite sure if VS did that with a newly created project or I had renamed it to that). Once I changed the name, it worked fine.
I figured I would add what it was for me. I had followed a popular tutorial to add appsettings.json and dependency injection to a console application. I did not realize in the setup that it referenced the current directory and was using that to set the base path of the configuration builder. It worked fine when I was running locally, but as soon as I tried to deploy and have a SQL scheduled job run the command it was taking the directory where the command was being entered, not where the DLL was so it wasn't finding my appsettings.json file. I simply removed the lines that dealt with getting the current directory and setting that as the base path and it works fine. It seems like it defaults to the same folder as the DLL.
I had this problem due to a difference in connectionstring of appsetting.json file, and the GetConnectionString(connectionstrings) parameter in startup.cs. Once I removed extra s in startup.cs, the problem disappeared.
Check if ASPNETCORE_ENVIRONMENT variable is set up on the server correctly. Depending on that environment it may be taking appsettings.json instead of appsettings.Staging.json or appsettings.Production.json.
In my case i was using configuration["DbContext"]
services.AddDbContext<AstroBhaskarDbContext>(option =>
{
option.UseSqlServer(configuration["DbContext"]);
});
then i replaced configuration["DbContext"] to configuration.GetConnectionString("DbContext") as below
services.AddDbContext<AstroBhaskarDbContext>(option =>
{
option.UseSqlServer(configuration.GetConnectionString("DbContext"));
});
I'm trying to pass command line arguments to Startup class. Following this example, I modified my Program class and it looks like this:
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("generalsettings.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables(prefix: "ASPNETCORE_")
.AddCommandLine(args);
var config = builder.Build();
var host = new WebHostBuilder()
.UseUrls("http://*:5000")
.UseConfiguration(config)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
host.Run();
generalsettings.json contains following data:
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
}
}
Therefore I commented out default Startup class' constructor. Then I noticed that that's where IConfigurationRoot Configuration is assigned, and therefore, when I'm trying to use it from ConfigureServices it's null. I'm trying to use (I think) configuration built in Program's Main. What am I missing here?
UPDATE
To make it clear: I'm trying to use args in Startup class.
It turns out that there has been a fair amount of discussion about this sort of thing in GitHub Aspnet hosting repo issues area. The GitHub repositories are always a good place to look on an interesting problem like this one.
To sum things up for you, do not set up IConfigurationRoot in Program.Main, set it up in Startup. You can pass the command line arguments to the Startup constructor from Program.cs like so:
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.ConfigureServices(services => services
.AddSingleton(new ConsoleArgs(args))
)
.Build();
host.Run();
}
where ConsoleArgs is a holder class you can create yourself that looks like:
public class ConsoleArgs
{
public ConsoleArgs(string[] args)
{
Args = args;
}
public string[] Args { get; }
}
Obviously, configuring a ConsoleArgs service is the key. This will allow it to be injected into the Startup constructor. Then your Startup class constructor looks something like
public Startup(IHostingEnvironment env, ConsoleArgs args)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("generalsettings.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables(prefix: "ASPNETCORE_")
.AddCommandLine(args.Args);
var config = builder.Build();
}
The pattern here is "configure you hosting environment Program, configure you application in Startup"
I am building my first .NETCoreApp using 1.0.0-rc2-final. I am trying to insert a copy of Model into TempData so that it is accessible after postback.
I added Microsoft.AspNetCore.Session to my project.
I altered my Startup.cs to look like...
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
namespace GamesCore
{
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
if (env.IsDevelopment())
{
// For more details on using the user secret store see http://go.microsoft.com/fwlink/?LinkID=532709
builder.AddUserSecrets();
}
builder.AddEnvironmentVariables();
Configuration = builder.Build();
}
public static IConfigurationRoot Configuration { get; private set; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
services.AddSession();
services.AddAuthentication(
SharedOptions => SharedOptions.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseCookieAuthentication();
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
ClientId = Configuration["Authentication:AzureAd:ClientId"],
Authority = Configuration["Authentication:AzureAd:AADInstance"] + Configuration["Authentication:AzureAd:TenantId"],
CallbackPath = Configuration["Authentication:AzureAd:CallbackPath"]
});
app.UseSession(new SessionOptions { IdleTimeout = TimeSpan.FromMinutes(60) });
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}
I have the following in one of my Controllers:
public IActionResult Index(Models.ScoreModel scoreModel)
{
if (string.IsNullOrWhiteSpace(scoreModel.Username))
{
scoreModel.GameID = new System.Guid("b90ae557-7e03-4efa-9da1-1a4e89c1f629");
scoreModel.Username = User.Identity.Name;
scoreModel.Score = 0;
scoreModel.ScoringStep = 1;
TempData.Add("scoreModel", scoreModel);
}
return View(scoreModel);
}
When I have the line with TempData in there, the page loads completely blank -- no error, no Shared Layout, etc. If I remove that line, the View loads fine within the Shared Layout. If I look at the debugger, the scoreModel is getting successfully added to TempData so that doesn't seem to be a problem.
I figured this out.
I moved to storing it in Session instead of TempData, using this page as an example:
http://benjii.me/2015/07/using-sessions-and-httpcontext-in-aspnet5-and-mvc6/
I serialized the Model using the extension class outlined on the above page.
One note, is that since this page was written, services.AddCaching(); has changed to services.AddDistributedMemoryCache();
See this link for another sample: https://github.com/aspnet/Session/blob/dev/samples/SessionSample/Startup.cs