aspnet core dependency injection with IOptions in startup - asp.net-core

What is going on gang?
I am looking for a proper and clean way of injecting configuration into my classes using the IOptions package. Currently, I am registering all the configuration object in one place and everything is just fine. The problem is with some classes that I need to register in the startup method.
The initial method implementation looks like this: (extension method)
public static void RegisterHttpClients(this IServiceCollection services, AppSettings appSettings)
{
services.AddHttpClient<IPdfService, PdfService>(
httpClient => httpClient.DefaultRequestHeaders.Add("PdfApiKey", appSettings.PdfApiKey));
}
This AppSettings object I am also injecting into other classes using IOptions so would like to somehow reuse the object from there instead of having to get same object from json again like that:
var appSettings = Configuration.GetSection("AppSettings").Get<AppSettings>();
services.RegisterHttpClients(appSettings);
In other words - I already have the thing registered with services.Configure<AppSettings>(configuration.GetSection("AppSettings")); so would kind of like to piggy-back on this if possible.
Any thoughts/suggestions for possible directions on how to configure this?
EDIT: (added JSON and AppSettings files)
public class AppSettings
{
public string PdfApiKey {get; set;}
public string AnotherProperty {get; set;}
}
{
"AppSettings": {
"PdfApiKey": "SomeKey",
"AnotherProperty": "HollyTheCow"
}
}
Thanks in advance!

You can accomplish this by registering your options with Configure (which you've done), then using the second overload to AddHttpClient() to get the registered options from the DI container (Service Locator pattern). Note I've used IServiceProvider.GetRequiredService, but you can use IServiceProvider.GetService()?.Value if it's an optional setting.
services.Configure<AppSettings>(_configuration.GetSection("AppSettings"));
services.AddHttpClient<YourService>((serviceProvider, config) => {
var settings = serviceProvider.GetRequiredService<IOptions<AppSettings>>().Value;
config.BaseAddress = new Uri(settings.BaseUri);
});

Your code has no any error and will run successfully
There may be many appSetting.json files on your project.
Check the ASPNETCORE_ENVIRONMENTvariable on your project.
Right click on your project > Properties > Debug > Environment variables
if ASPNETCORE_ENVIRONMENT is set on Development IConfiguration read data from
appSettings.development.json and you should add AppSettigs json in appSettings.development.json
if ASPNETCORE_ENVIRONMENT is set on Production IConfiguration read data from
appSettings.production.json and you should add AppSettigs json in appSettings.production.json
if you want IConfiguration read data from appSetting.json delete both appSettings.development.json and appSettings.production.json file.
in this case IConfiguration by default read and bind data from appSetting.json
Another options
You can config your IConfiguration to load data dynamically from
appSettings.json like this
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();
this.configuration = builder.Build();
}

Related

ASP.net Core 2.2 configuration

Coming from a webforms background, I'm trying to understand how configuration and environment translation works in .net core 2.2 MVC web apps. Gone are the web.config files and the ConfigurationSettings.AppSettings property. I'm finding the documentation a little unclear.
The documentation states I need to call AddJsonFile or AddXmlFile during application startup. Like this:
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.SetBasePath(Directory.GetCurrentDirectory());
config.AddJsonFile(
"config.json", optional: true, reloadOnChange: true);
})
.UseStartup<Startup>();
The project template I use already has the following logic:
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
My project has appsettings.json and appsettings.development.json files. When I put a breakpoint on the Startup method of the Startup class, I can inspect the configuration parameter and see the two json configuration files exposed as what looks to be a dictionary.
Questions
So do I have to explicitly call AddJSonFile, or is this actually done for me somehow by the framework?
How do I handle transforming configuration for different deployments?
What is the best way to access this configuration in a controller?
So do I have to explicitly call AddJSonFile, or is this actually done for me somehow by the framework?
This is done in the framework. Most notably the "DefaultBuilder" adds in both appsettings.json and appsettings.{Environment}.json, among other things. https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.webhost.createdefaultbuilder?view=aspnetcore-2.2
How do I handle transforming configuration for different deployments?
You need to set the Environment variable on the host machine (This is the easiest way althought here are other ways to do it). So for example if you set the environment to be Production, then it will first load appsettings.json, then it will load appsettings.Production.json and override the default settings. More info here : https://dotnetcoretutorials.com/2017/05/03/environments-asp-net-core/
What is the best way to access this configuration in a controller?
There are two ways. You can use the Options pattern built into the framework : https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-2.2
Or you can use good old fashioned POCO's (https://dotnetcoretutorials.com/2016/12/26/custom-configuration-sections-asp-net-core/).
All you need to do there is load out your configuration in your ConfigureServices method and bind it to a singleton :
services.AddSingleton(Configuration.GetSection("myConfiguration").Get<MyConfiguration>());
Then you can simply request it in your controller via DI:
public class ValuesController : Controller
{
private readonly MyConfiguration _myConfiguration;
public ValuesController(MyConfiguration myConfiguration)
{
_myConfiguration = myConfiguration;
}
}

appsettings.json file not in .net core console project

I understand that .net core has replaced the app.config file with appsetting.json. However this file seems to be added for ASP.net projects only. In fact it is not even available in the add items list.
I found this post that list packages needed to be added:
Microsoft.Extensions.Configuration
Microsoft.Extensions.Configuration.FileExtensions
Microsoft.Extensions.Configuration.Json
I added all these and it does give me the option of adding a json configuration file but still not the App Settings File which is only available under ASP.Net Core.
I am trying to understand why, doesn't a non web project need configuration and what is the recommended way to configure a .net core console application.
Thanks in advance.
Non-web project may or may not need configuration. But, as you noticed, Visual Studio doesn't scaffold console projects with appsettings.json. Obviously, you can add it to the project as json file. Once you have it, the challenge is to make use of it. I frequently use Configuration object and dependency injection in Entity Framework utilities.
For example,
public static class Program
{
private static IConfigurationRoot Configuration { get; set; }
public static void Main()
{
IConfigurationBuilder builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables();
Configuration = builder.Build();
IServiceCollection services = new ServiceCollection();
services.AddDbContext<MyDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddScoped<IMyService, MyService>();
IServiceProvider provider = services.BuildServiceProvider();
IMyService myService = provider.GetService<IMyService>();
myService.SomeMethod();
}
public class TemporaryDbContextFactory : IDesignTimeDbContextFactory<MyDbContext>
{
public MyDbContext CreateDbContext(string[] args)
{
IConfigurationBuilder configBuilder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables();
IConfigurationRoot configuration = configBuilder.Build();
DbContextOptionsBuilder<MyDbContext> builder = new DbContextOptionsBuilder<MyDbContext>();
builder.UseSqlServer(configuration.GetConnectionString("DefaultConnection"));
return new MyDbContext(builder.Options);
}
}
}
This allows me to both run migrations and console-based utilities against DbContext. You don't specify what kind of configuration you are going to need - so this is just one example. But hopefully, you can adjust it to your needs.

How to configure Kestrel urls in startup class

I am trying to figure out the proper way to modify the URL(s) that kestrel listens on from the Startup class constructor.
Update: In addition to the answer below, I've come to understand that Startup class isn't used to configure Kestrel in the way I had thought. I had thought that Startup would create a single application-wide configuration object that would be located through convention, it is not. As #Tseng points out, the configuration of the Application and Hosting are separate concerns. The linked answer and accepted answer provide working examples.
I spun up a brand new Ubuntu 14 box in Vagrant and installed ASP.NET Core 1.1 according to the current instructions from Microsoft: https://www.microsoft.com/net/core#linuxubuntu
I ran:
dotnet new -t web
dotnet restore
dotnet run
This listens on http://localhost:5000 by default. In Program.cs I can call app.UseUrls("http://*:5001) and this works to change the URL and port.
What I really want is to change the URLs by convention through adding a new JSON setting file in the Startup class, so I followed examples and created a hosting.JSON file with this (note: I've tried "server.urls" and "urls" as the key).
{
urls: "http://*:5001"
}
In Startup.cs below the default lines for adding the appsettings.json files I added .AddJsonFile("hosting.json", optional: false); (optional: false to make sure it was picking up the file)
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)
.AddJsonFile("hosting.json", optional: false);
if (env.IsDevelopment())
{
// For more details on using the user secret store see https://go.microsoft.com/fwlink/?LinkID=532709
builder.AddUserSecrets();
}
builder.AddEnvironmentVariables();
Configuration = builder.Build();
}
I have verified that the setting is there after the configuration builds, but it is not picked up or used when the host is built in the Program.cs
public class Program
{
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
host.Run();
}
}
and the application still starts listening on localhost:5000. It is my (likely incorrect) understanding that by having a setting properly named with the correct key the WebHostBuidler should pick it up and use it.
I've seen other examples that basically get rid of the startup class and create the configuration in Program.cs where it can then be passed into a call to UseConfiguration but again my understanding is that using a Startup class should do this by convention.
Essentially I'd like to keep Startup and Program separate, add a hosting.JSON file to the configuration with URLs, and have it picked up and used without needing to call UseUrls, UseConfiguration, etc.
Am I missing something obvious, or trying to do something that is not actually correct?
To explain why this is not a duplicate as per my comment:
That post specifically says "launcherSettings is for Visual Studio F5". I am using the command line on a Linux box. Nothing to do with VS.
The solution provided in that post moved the configuration build into the main method. I specifically state that I want to build my configuration in the Startup class just like the default "dotnet new -t web" project.
I do not feel this is a duplicate, and am still reviewing the ASP.NET Core source to see if this is possible.
In regards to the correct key:
https://github.com/aspnet/Hosting/blob/b6da89f54cff11474f17486cdc55c2f21f2bbd6b/src/Microsoft.AspNetCore.Hosting.Abstractions/WebHostDefaults.cs
namespace Microsoft.AspNetCore.Hosting
{
public static class WebHostDefaults
{
public static readonly string ApplicationKey = "applicationName";
public static readonly string StartupAssemblyKey = "startupAssembly";
public static readonly string DetailedErrorsKey = "detailedErrors";
public static readonly string EnvironmentKey = "environment";
public static readonly string WebRootKey = "webroot";
public static readonly string CaptureStartupErrorsKey = "captureStartupErrors";
public static readonly string ServerUrlsKey = "urls";
public static readonly string ContentRootKey = "contentRoot";
}
}
https://github.com/aspnet/Hosting/blob/80ae7f056c08b740820ee42a7df9eae34541e49e/src/Microsoft.AspNetCore.Hosting/Internal/WebHost.cs
public class WebHost : IWebHost
{
private static readonly string DeprecatedServerUrlsKey = "server.urls";
In your hosting.json file you should use server.urls instead of urls. And hosting.json file need to added in the program.cs (in the main method) not in startup.
Here is my hosting.json file.
{
"server.urls": "http://localhost:5010;http://localhost:5012"
}
Here is the Main method.
public static void Main(string[] args)
{
var config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddCommandLine(args)
.AddEnvironmentVariables(prefix: "ASPNETCORE_")
.AddJsonFile("hosting.json", optional: true)
.Build();
var host = new WebHostBuilder()
.UseConfiguration(config)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
host.Run();
}
And here is the screenshot.

Accessing the host configuration inside the ASP.NET Core Startup methods

On a typical ASP.NET Core application, the Main method creates a configuration and passes it to the WebHostBuilder. Is there a way to access this configuration inside any of the Startup methods (e.g. Configure or ConfigureServices)?
I need this configuration because it has access to the command line arguments.
Thanks
Pedro
You can just create a property in the class, where Main method exists, to persist the configuration, or use any other class for storage.
public IConfigurationRoot Configuration { get; set; }
From other side, you can read configuration few times. You are allowed to create another instance of ConfigurationBuilder and specify configuration sources (maybe even different than you use in main method):
var builder = new ConfigurationBuilder()
.AddEnvironmentVariables();
var anotherConfiguration = builder.Build();

ASP.NET Core ignores ASPNET_ENV and Hosting:Environment

No matter when or where I set either ASPNET_ENV or Hosting:Environment the startup code will always enter
//This method is invoked when ASPNET_ENV is 'Production'
//The allowed values are Development,Staging and Production
public void ConfigureProduction(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(minLevel: LogLevel.Warning);
Configure(app);
}
What I've tried so far
Set Hosting:Environment to Development in the project properties
Set ASPNET_ENV to Development in the project properties
Set Hosting:Environment to Development in launchSettings.json
Set ASPNET_ENV to Development in launchSettings.json
Set ASPNET_ENV to Development in code via Environment.SetEnvironmentVariable("ASPNET_ENV", "Development"); in the Startup method before the call to ConfigurationBuilder.GetEnvironmentVariables()
This is version 1.0.0-rc2-20143 by the way. Am I missing something here or is it just a bug?
The environment variable name has been changed to ASPNETCORE_ENVIRONMENT as part of the name change.
It was announced in this issue and change in this PR.
If you have not done it already, try adding the environment variables to the configuration instance.
public static void Main(string[] args)
{
var builder = new ConfigurationBuilder();
builder.AddJsonFile("appsettings.json");
builder.AddEnvironmentVariables();
var config = builder.Build();
}
Whilst the answer from Henk Mollema is correct for the environment variable name, I still encountered issues where dotnetcore would seem to ignore the environment variable and use appsettings.json when run from command line.
To ensure it uses the correct environment and app settings, try the following:
Change the environment variable via Project Properties -> Debug -> Environment Variables -> ASPNETCORE_ENVIRONMENT -> Value : Change the value to match your intended environment eg Staging
Build Solution
From commandline, type SETX ASPNETCORE_ENVIRONMENT "YOUR-ENVIRONMENT-NAME" where replace "YOUR-ENVIRONMENT-NAME" to match what you set in step 1 eg Staging and ensure you include the "quotes"
IMPORTANT - make sure you all your various appsettings.json eg appsettings.staging.json is present in the project directory where you will run it. (In the case of a published solution, the appsettings.staging.json may not have been copied, so ensure it's there)
From Commandline, go to your project directory (or published directory) and run your project by typing dotnet run (or "dotnet YOURPROJECTNAME.DLL" for published projects)
Observe in the next line that appears in the commandline which states Hosting environment: YOURENVIRONMENTNAME eg Hosting environment:staging
Running a DotNetCore project from Visual Studio always picked the
correct appsettings based on the environment set in your project
properties, however these are the steps I followed to run dotnet core correctly from commandline
working correctly.
This worked for me using .net core 2.0 and building a web api. Notice the 2 different methods of accessing the environment variable.
Program.cs
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args){
if(Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT ") == "Production"){
return WebHost.CreateDefaultBuilder(args)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
} else {
return WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
}
}
But in the startup file there is another environment option
Startup.cs
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)
{
services.AddCors(options => options.AddPolicy("AllowAll", p => p.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()));
var connectionString = "Data Source=tcp:<some-ip>,<some-port>;Initial Catalog=<some database>;Integrated Security=False;User Id=SA;Password=<some password>;MultipleActiveResultSets=True";
services.AddDbContext<myContext>(opt => opt.UseSqlServer(connectionString));
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseCors("AllowAll");
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc();
}
}