Read appsettings.json keys in methods other than Main Program.cs .NetCore 3.1 console app - .net-core

Basically I have to call a REST API from a .netcore console app which will return me some xml and then this console app will need to create a csv file from that xml.
I need to keep the REST API url and csv path in my appsettings.config.
I have created two keys for that.
Now I don't want to read these keys in my main method but I am building ConfigurationBuilder in my main method and therefore its available only in Main method.
Code in my Main method looks like below.
configuration = new ConfigurationBuilder()
.AddJsonFile($"appsettings.json", true, true)
.AddEnvironmentVariables()
.Build();
where configuration is static IConfiguartion configuration = null at Program.cs class level.
For now I have made this ConfigurationBuilder as a class level static variable , initilaizing it in Main then reading it elsewhere but not sure if its a good approach or there's some better way of doing this?
Any suggestions would be appreciated.

In my opinion you should use Dependency Injection like described in #Marco Luzzaras comment.
But if it is not possible in you scenario you can also access the same configuration by encapsule everything in a Singleton
public class ConfigurationProvider
{
private static IConfiguration _instance;
public static IConfiguration Instance
{
get
{
if(_instance == null)
{
_instance = new ConfigurationBuilder()
.AddJsonFile($"appsettings.json", optional: true)
.AddEnvironmentVariables()
.Build();
}
return _instance;
}
}
}
... and use it everywhere you need it.
dotnet fiddle Example

Related

How to dump entire appsettings.json file in ASP.NET Core Web API

I am trying to create a simple GET API endpoint which will return the JSON of the current environment appsettings.json file, for example for the development environment it will return the contents of appsettings.Development.json file and for production environment it will return the contents of appsettings.Production.json file.
I don't know a nice way to dump the entire config file. What I know though is ways to read single config values through the injected config["Key"] or read a section through
config.GetSection("SectionName").Get<MyCustomSectionClass>()
approach. These options are not feasible as the file is big and the content may change.
This is an ASP.NET Core 6 Core Web API application, created through the default Visual Studio template.
If you want to return entire appsettings.xxx.json file, You can try this simple demo. I not sure if there is a better method, But this method works well in my project.
using Newtonsoft.Json;
private readonly IWebHostEnvironment _env;
public WeatherForecastController(IWebHostEnvironment env)
{
_env = env;
}
[HttpGet]
public IActionResult Get()
{
if (_env.IsDevelopment())
{
var path = Path.Combine(_env.ContentRootPath, "appsettings.Development.json");
string json = System.IO.File.ReadAllText(path);
object jsonObject = JsonConvert.DeserializeObject(json);
return Ok(jsonObject);
}else if (_env.IsProduction())
{
//read from appsettings.Production.json
return Ok();
}
else
{
//..........
return Ok();
}
}

How to replace IDbContextFactory in Service Injection for xUnit integration test

I am setting up an API using .NET 5 with dependency injection for my data access; using Entity Framework code first. I need to use IDbContextFactory because some of my controllers need to use more than one DbContext instance for proper Unit of Work scope.
I'm also trying to use xUnit for integration tests and am running into difficulties getting it to use in memory database.
Here is the relevant snippet from my Startup class
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContextFactory<ApplicationDbContext>(
options => options.UseSqlServer("connection string")
);
//other services configured
}
And this is the xUnit Application Factory that is supposed to remove the existing DbContextFactory and replace it with the InMemory factory.
public class TestApplicationFactory<TStartup>
: WebApplicationFactory<TStartup> where TStartup: class
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
var contextFactory = services.SingleOrDefault(
d => d.ServiceType ==
typeof(IDbContextFactory<ApplicationDbContext>)
);
services.Remove(contextFactory);
services.AddDbContextFactory<ApplicationDbContext>(
options => options.UseInMemoryDatabase("InMemoryDbForTesting")
);
var sp = services.BuildServiceProvider();
var dbf = sp.GetRequiredService<IDbContextFactory<ApplicationDbContext>>();
var db = dbf.CreateDbContext();
db.Database.EnsureCreated();
// Pass the context to a class that will add seed data
DbSeed.InitializeDbForTests(db);
});
}
When I step through in debug, it appears to be removing the existing factory as evidenced by the count of services dropping after the services.Remove(contextFactory); line and it also seems to add the new one, again by looking at the count.
This is not throwing any errors, it's just not using the new DbContextFactory.
What am I missing here?
Thank you in advance for any help.
I figured out the issue.
When the Startup class runs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContextFactory<ApplicationDbContext>(
options => options.UseSqlServer("connection string")
);
//other services configured
}
It is actually adding four descriptors to the service collection:
IDbContextFactory\<ApplicationDbContext>
IDbContextFactorySource\<ApplicationDbContext>
DbContextOptions
DbContextOptions\<ApplicationDbContext>
The solution is to remove all of these in the xUnit ApplicationFactory. I was only removing one. Once I removed the other three, I could continue on to add the InMemory version of the DbContextFactory and it now correctly points to the test database.
If you have a more efficient or elegant solution, I'd be interested to know.

How to set environment variables from appsettings.json for .net core console app?

I'm working with a C# class that reads from appsettings using
Environment.GetEnvironmentVariable("setting")
I want to make another console program (.net core 3.0) that reads settings from appsettings.json and loads them into environment variables.
static void Main(string[] args)
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables();
IConfiguration config = builder
.Build();
var businessLogic = new BusinessLogic(); // uses GetEnvironmentVariable to get configuration
}
In my business logic class, the environment variables aren't getting pulled back correctly. What might be the problem?
appsettings.json is in this format:
{
"SETTING" : "setting value"
}
Environment.GetEnvironmentVariable("setting") will always try to read the data from an environment variable, not from your loaded config.
You need to change your BusinessLogic class to accept the config object you built and use that to access the settings. An example:
public class BusinessLogic
{
public BusinessLogic(IConfiguration config)
{
var value = config.GetValue<string>("SETTING");
}
}
In this example value will contain the correct value of SETTING if it was last set in appsettings.json or using an environment variable.

ConfigurationManager.AppSettings.Get(key).ConvertTo error in asp.net core

Below is my code. I try to convert below code from asp.net to asp.net core. But in asp.net core in last line ConvertTo is showing error because Get(key) does not have definition of ConvertTo. Don't know what is the problem.
I am unable to find any solution how can i write below code in asp.net core?
public static T Get<T>(string key)
{
if (!Exists(key))
{
throw new ArgumentException(String.Format("No such key in the AppSettings: '{0}'", key));
}
return ConfigurationManager.AppSettings.Get(key).ConvertTo<T>(new CultureInfo("en-US"));
}
Thanks in advance.
I suggest you carefully reading the documentation first. In .NET Core the way how we work with configuration is changed significantly ( different source using, mapping to POCO objects , etc).
In your case you may simply use ConfigurationBinder’s GetValue<T> extension method instead of implementing own method for value conversion:
IConfiguration.GetValue - extracts the value with the specified key
and converts it to type T.
Configuration in .net Core is now built on top of POCO's or IOptions for the most part. You don't get individual keys but instead you build up settings classes. Previously you had to either build a CustomConfiguration class or you would prefix AppSettings to "group them". Not anymore! If you take the approach of using IOptions it works something like the following.
You have your appSettings.json look like the following :
{
"myConfiguration": {
"myProperty": true
}
}
You then make up a POCO that matches your configuration. Something like this :
public class MyConfiguration
{
public bool MyProperty { get; set; }
}
Then in your startup.cs you need to load your configuration into an options object. It will end up looking pretty similar to the following.
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)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.Configure<MyConfiguration>(Configuration.GetSection("myConfiguration"));
}
}
Then the DI is all set up to inject an IOptions objects. You would then inject it into a controller like so :
public class ValuesController : Controller
{
private readonly MyConfiguration _myConfiguration;
public ValuesController(IOptions<MyConfiguration> myConfiguration)
{
_myConfiguration = myConfiguration.Value;
}
}
There is other ways to do this that don't use the IOptions object and you only inject in the POCO to your controllers. Some people (including me) prefer this method. You can read more here : http://dotnetcoretutorials.com/2016/12/26/custom-configuration-sections-asp-net-core/
And of course the documentation link for the official docs are here : https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration

How and where to call Database.EnsureCreated and Database.Migrate?

I have a ASP.NET MVC 6 application, and i need to call the Database.EnsureCreated and Database.Migrate methods.
But where should I call them?
I think this is an important question and should be well answered!
What is Database.EnsureCreated?
context.Database.EnsureCreated() is new EF core method which ensures that the database for the context exists. If it exists, no action is taken. If it does not exist then the database and all its schema are created and also it ensures it is compatible with the model for this context.
Note:
This method does not use migrations to create the database. In addition, the database that is created cannot later be updated using migrations. If you are targeting a relational database and using migrations, you can use the DbContext.Database.Migrate() method to ensure the database is created and all migrations are applied.
How did we do that with EF 6?
context.Database.EnsureCreated() is equivalent to the below listed approaches of EF 6:
Package Manager Console:
Enable-Migrations -EnableAutomaticMigrations. Add-Migration/Update-Database.
From code:
Database.SetInitializer CreateDatabaseIfNotExists
or
With DbMigrationsConfiguration and set AutomaticMigrationsEnabled = true;
What is Database.Migrate?
Applies any pending migrations for the context to the database. Will create the database if it does not already exist.
How did we do that with EF 6?
context.Database.Migrate() is equivalent to the below listed approaches of EF 6:
Package Manager Console:
Update-Database -TargetMigration
With a custom DbMigrationsConfiguration:
AutomaticMigrationsEnabled = false; or with DbMigrator.
Conclusion:
If you are using migrations there is context.Database.Migrate(). If you don't want migrations and just want a quick database (usually for testing) then use context.Database.EnsureCreated()/EnsureDeleted().
With the information that James P and Bassam Alugili provided, what I ended up doing was to add these lines of code to the Configure method in the Startup class (Startup.cs):
using (var scope =
app.ApplicationServices.CreateScope())
using (var context = scope.ServiceProvider.GetService<MyDbContext>())
context.Database.Migrate();
Ordinarily, the DbContext will be added to the dependency injection container in Startup.ConfigureServices() like so:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add DbContext to the injection container
services.AddDbContext<MyDbContext>(options =>
options.UseSqlServer(
this.Configuration.GetConnectionString("DefaultConnection")));
}
}
However, the IServiceCollection doesn't act as a service provider, and since the DbContext was not registered with the injection container before the current scope (Startup.ConfigureServices), we can't access the context through dependency injection here.
Henk Mollema discusses manually resolving services during startup here, but mentions that...
manually resolving services (aka Service Locator) is generally
considered an anti-pattern ... [and] you should avoid it as much
as possible.
Henk also mentions that the Startup constructor's dependency injection is very limited and does not include services configured in Startup.ConfigureServices(), so DbContext usage is easiest and most appropriate through the injection container used throughout the rest of the app.
The runtime's hosting service provider can inject certain services into the constructor of the Startup class, such as IConfiguration, IWebHostEnvironment (IHostingEnvironment in pre-3.0 versions), ILoggerFactory and IServiceProvider. Note that the latter is an instance built by the hosting layer and contains only the essential services for starting up an application.
In order to call Database.EnsureCreated() or Database.Migrate(), we can, and want to, have the DbContext resolve automatically in Startup.Configure(), where our configured services are now available through DI:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add DbContext to the injection container
services.AddDbContext<MyDbContext>(options =>
options.UseSqlServer(
this.Configuration.GetConnectionString("DefaultConnection")));
}
public static void Configure(IApplicationBuilder app, IWebHostEnvironment env, MyDbContext context)
{
if (env.IsDevelopment())
{
context.Database.EnsureCreated();
//context.Database.Migrate();
}
}
}
Please remember as Bassam Alugili's answer referenced from EF Core documentation that Database.EnsureCreated() and Database.Migrate() are not meant to be used together because one ensures your existing migrations are applied to the database, which is created if needed. The other just ensures a database exists, and if not, creates one that reflects your DbContext, including any seeding done through the Fluent API in the context.
Just as a foreward you should read this from Rowan Miller:
... EnsureCreated totally bypasses migrations and just creates the
schema for you, you can't mix this with migrations. EnsureCreated is
designed for testing or rapid prototyping where you are ok with
dropping and re-creating the database each time. If you are using
migrations and want to have them automatically applied on app start,
then you can use context.Database.Migrate() instead.
According to answer here you need to add Globals.EnsureDatabaseCreated(); it to Startup.cs:
Startup function in Startup.cs:
public Startup(IHostingEnvironment env)
{
// Set up configuration sources.
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables();
if (env.IsDevelopment())
{
// This will push telemetry data through Application Insights pipeline faster, allowing you to view results immediately.
builder.AddApplicationInsightsSettings(developerMode: true);
}
Configuration = builder.Build();
Globals.Configuration = Configuration;
Globals.HostingEnvironment = env;
Globals.EnsureDatabaseCreated();
}
And define Globals.EnsureDatabaseCreated() as follows:
public static void EnsureDatabaseCreated()
{
var optionsBuilder = new DbContextOptionsBuilder();
if (HostingEnvironment.IsDevelopment()) optionsBuilder.UseSqlServer(Configuration["Data:dev:DataContext"]);
else if (HostingEnvironment.IsStaging()) optionsBuilder.UseSqlServer(Configuration["Data:staging:DataContext"]);
else if (HostingEnvironment.IsProduction()) optionsBuilder.UseSqlServer(Configuration["Data:live:DataContext"]);
var context = new ApplicationContext(optionsBuilder.Options);
context.Database.EnsureCreated();
optionsBuilder = new DbContextOptionsBuilder();
if (HostingEnvironment.IsDevelopment()) optionsBuilder.UseSqlServer(Configuration["Data:dev:TransientContext"]);
else if (HostingEnvironment.IsStaging()) optionsBuilder.UseSqlServer(Configuration["Data:staging:TransientContext"]);
else if (HostingEnvironment.IsProduction()) optionsBuilder.UseSqlServer(Configuration["Data:live:TransientContext"]);
new TransientContext(optionsBuilder.Options).Database.EnsureCreated();
}
To use context.Database.Migrate() see here or here.
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddDbContext<YourDbContext>(option => option.UseSqlServer(#"Data source=(localdb)\ProjectModels;Initial Catalog=YourDb;Integrated Security=True"));
var app = builder.Build();
// Configure the HTTP request pipeline.
YourDbContext dbcontext = app.Services.GetRequiredService<YourDbContext>();
dbcontext.Database.EnsureCreated();
Additionally you may see a performance hit if you call this in the constructor of your context... After moving EnsureCreated to the setup.cs utility, I noticed considerable improvements to my response times.
Note: I am using EFC and UWP.
If you are working with VS 2022 / .Net Version 6 and you are trying to find a way to create your database..then
Do these following steps
Add Microsoft.EntityFramework.Tools reference through Package manager
from Package Manager Console
Run Step 1
Add-Migration InitialMigration
InitialMigration here is custom name you can type anything you want..
let it run
Step 2
Update-Database
This should create your database.
I am developing ASP .NET Core 6 Web API.
In Program.cs after building the app I wrote the code below.
var app = builder.Build();
using (var scope = app.Services.CreateScope())
{
using var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
context.Database.EnsureCreated();
}
context.Database.EnsureCreated() ensures that the database for the context exists.
If the database exists and has any tables, then no action is taken. Nothing is done to ensure the database schema is compatible with the Entity Framework model.
If the database exists but does not have any tables, then the Entity Framework model is used to create the database schema.
If the database does not exist, then the database is created and the Entity Framework model is used to create the database schema.
I hope I helped.

Resources