Getting a Configuration Value in ASP.NET 5 (vNext) - c#

I am struggling with some concepts in ASP.NET 5 (vNext).
One of those is the Dependency Injection approach used for configuration. It seems like I have to pass a parameter all the way through the stack. I'm probably misunderstanding something or doing it wrong.
Imagine I have a config property named "contactEmailAddress". I'll use that config property to send an email when a new order is placed. With that scenario in mind, my ASP.NET 5 stack will look like this:
Startup.cs
public class Startup
{
public IConfiguration Configuration { get; set; }
public Startup(IHostingEnvironment environment)
{
var configuration = new Configuration().AddJsonFile("config.json");
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
services.Configure<AppSettings>(Configuration.GetSubKey("AppSettings"));
services.AddMvc();
}
public void Configure(IApplicationBuilder app)
{
app.UseErrorPage();
app.UseMvc(routes =>
{
routes.MapRoute("default",
"{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index" });
}
);
app.UseWelcomePage();
}
AppSettings.cs
public class AppSettings
{
public string ContactEmailAddress { get; set; }
}
config.json
{
"AppSettings": {
"ContactEmailAddress":"support#mycompany.com"
}
}
OrderController.cs
[Route("orders")]
public class OrdersController : Controller
{
private IOptions<AppSettings> AppSettings { get; set; }
public OrdersController(IOptions<AppSettings> appSettings)
{
AppSettings = appSettings;
}
[HttpGet("new-order")]
public IActionResult OrderCreate()
{
var viewModel = new OrderViewModel();
return View(viewModel);
}
[HttpPost("new-order")]
public IActionResult OrderCreate(OrderViewModel viewModel)
{
return new HttpStatusCodeResult(200);
}
}
Order.cs
public class Order()
{
public void Save(IOptions<AppSettings> appSettings)
{
// Send email to address in appSettings
}
public static List<Order> FindAll(IOptions<AppSettings> appSettings)
{
// Send report email to address in appSettings
return new List<Order>();
}
}
As the example above shows, I'm passing AppSettings through the entire stack. This does not feel correct. To further my worries, this approach will not work if I'm attempt to use a third-party library that needs to access configuration settings. How can a third-party library access configuration settings? Am I misunderstanding something? Is there a better way to do this?

You are entangling 2 different run time resource provider, AppSettings and Dependency Injection.
AppSettings, provides run-time access to Application specific values like UICulture strings, Contact Email, etc.
DI Containers are factories that Manage access to Services and their lifetime scopes. For example, If a MVC Controller needed access to your EmailService, you would configure
public void ConfigureServices(IServiceCollection services)
{
// Add all dependencies needed by Mvc.
services.AddMvc();
// Add EmailService to the collection. When an instance is needed,
// the framework injects this instance to the objects that needs it
services.AddSingleton<IEmailService, EmailService>();
}
Then, if our Home Controller needs access to your EmailService, we add a dependency on it's Interface by adding it as a parameter to the Controller constructor
public class HomeController : Controller
{
private readonly IEmailService _emailService;
private readonly string _emailContact;
/// The framework will inject an instance of an IEmailService implementation.
public HomeController(IEmailService emailService)
{
_emailService = emailService;
_emailContact = System.Configuration.ConfigurationManager.
AppSettings.Get("ContactEmail");
}
[HttpPost]
public void EmailSupport([FromBody] string message)
{
if (!ModelState.IsValid)
{
Context.Response.StatusCode = 400;
}
else
{
_emailService.Send(_emailContact, message);
The purpose of Dependancy Injection is to manage access and lifetimes of services.
In the previous example, in our Application Startup, we configured the DI Factory to associate application requests for IEmailService with EmailService. So when our Controllers are instantiate by the MVC Framework, the framework notices that our Home Controller expects IEmailService, the framework checks our Application Services Collection. It finds mapping instructions and Inject a Singleton EmailService (a descendant of the occupying Interface) into our Home Controller.
Super Polymorphic Factorific - alodocious!
Why is this important?
If your contact email changes, you change the AppSetting value and are done. All requests for "ContactEmail" from ConfigurationManager are Globally changed. Strings are easy. No need for Injection when we can just hash.
If your Repository, Email Service, Logging Service, etc changes, you want a Global way to change all references to this service. Service reference aren't as easily transferred as immutable string literals. Service instantiation should be handled by a factory to configure the Service's settings and dependencies.
So, in a year you develop a RobustMailService:
Class RobustMailService : IEmailService
{
....
}
As long as your new RobustMailService inherits and implements the IEmailService Interface, you can substitute all references to your mail service Globally by changing :
public void ConfigureServices(IServiceCollection services)
{
// Add all dependencies needed by Mvc.
services.AddMvc();
// Add RobustMailService to the collection. When an instance is needed,
// the framework injects this instance to the objects that needs it
services.AddSingleton<IEmailService, RobustMailService>();
}

This can be achieved using IOptions assessor service as it seems you were trying.
We can begin by creating a class with all of the variables that your controller needs from configuration.
public class VariablesNeeded
{
public string Foo1{ get; set; }
public int Foo2{ get; set; }
}
public class OtherVariablesNeeded
{
public string Foo1{ get; set; }
public int Foo2{ get; set; }
}
We now need to tell the middleware that the controller needs this class in the constructor of the controller using dependency injection, we do this using IOptions accessor service.
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
public class MyController: Controller{
private readonly VariablesNeeded _variablesNeeded;
public MyController(IOptions<VariablesNeeded> variablesNeeded) {
_variablesNeeded= variablesNeeded.Value;
}
public ActionResult TestVariables() {
return Content(_variablesNeeded.Foo1 + _variablesNeeded.Foo2);
}
}
To get the variables from your configuration files, we create a constructor for the startup class, and a configuration property.
public IConfigurationRoot Configuration { get; }
public Startup(IHostingEnvironment env)
{
/* This is the fairly standard procedure now for configuration builders which will pull from appsettings (potentially with an environmental suffix), and environment variables. */
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();
}
Now we need to make sure the pipeline actually supplies the controller with this service.
In your ConfigureServices method in your Startup class, you want to use the Options middleware, and inject an object of type VariablesNeeded in to the pipeline.
public void ConfigureServices(IServiceCollection services)
{
// Tells the pipeline we want to use IOption Assessor Services
services.AddOptions();
// Injects the object VariablesNeeded in to the pipeline with our desired variables
services.Configure<VariablesNeeded>(x =>
{
x.Foo1 = Configuration["KeyInAppSettings"]
x.Foo2 = Convert.ToInt32(Configuration["KeyParentName:KeyInAppSettings"])
});
//You may want another set of options for another controller, or perhaps to pass both to our "MyController" if so, you just add it to the pipeline
services.Configure<OtherVariablesNeeded>(x =>
{
x.Foo1 = "Other Test String",
x.Foo2 = 2
});
//The rest of your configure services...
}
For more information see the chapter on Using Options and configuration objects in the ASPCore Docs

Related

Dependency Injection how to re-add all services to builder.Services after config change

This question is specific to NET Maui in my case.
My Settings file:
{
"MyConfig": {
"FactoryConfig1": {
"SomeType1": "SomeConfig1"
},
"FactoryConfig2": {
"SomeType2": "SomeConfig2"
}
}
}
Let's say I'm creating my services like this:
var builder = MauiApp.CreateBuilder();
builder.Services.AddSingleton<IService1, Service1>();
builder.Services.AddSingleton<IService2, Service2>();
builder.Services.AddTransient<Service3>();
// and config file-- how to modify this to use IOptions to use with IOptions Monitor?
var a = Assembly.GetExecutingAssembly();
using var stream = a.GetManifestResourceStream("MauiBlazorTestApp.appsettings.json");
var config = new ConfigurationBuilder()
.AddJsonStream(stream)
.Build();
builder.Configuration.AddConfiguration(config);
return builder.Build();
then in another class I get the config:
public class Service1 : IService1
{
IConfiguration _configuration;
public Service1(IConfiguration configuration)
{
_configuration = configuration;
}
public void SomeMethod()
{
var myConfig = _configuration.GetRequiredSection("MyConfig").Get<MyConfig>();
}
}
In each of the services, I'm injecting an IConfiguration with the configuration parameters used in each service from an appsettings.json file.
The code above works the way it is but what if sometime after starting the app, and after the services have been already created with the existing settings, the user changes some settings in appsettings.json from a UI dashboard page, how can I reload all those services with the new config settings for each...
I have to clear the services, and re-add them, is this possible, in a class outside program.cs?
It would work if I restart the application, but can it be done without, just a refresh of the DI?
As #Nkosi mentioned in the comments you should always use option pattern and not directly access the IConfiguration object.
In your case just use a class to store the desired values from configuration:
public class MyConfig
{
// The class to bind IConfiguration to
public string YourSetting { get; set; }
}
add options to the service collection and configure them. Also note the reloadOnChange parameter used during setup which indicates whether the configuration is reloaded if the file changes.
//...
builder.Configuration
.AddJsonFile("MauiBlazorTestApp.appsettings.json", optional: false, reloadOnChange: true);
// Add and configure your options
builder.Services.AddOptions();
builder.Services.Configure<MyConfig>(builder.Configuration.GetRequiredSection("MyConfig"));
//...
and finally inject the options into the target services and access the settings as needed.
public class Service1 : IService1
{
private readonly IOptionsMonitor<MyConfig> _options;
public Service1(IOptionsMonitor<MyConfig> options)
{
_options = options;
}
public void SomeMethod()
{
// Read the current value provided via IConfiguration
MyConfig appSettings = _options.CurrentValue;
var value = appSettings.YourSetting;
//...use setting as needed
}
}
When ever the current setting is requested, it will pull the latest version from settings, even if the service is a singeton.
For more information you can find this pattern very well documented here and especially for the IOptionsMonitor here

Is there a way to modify an IOptionsSnapshot<T> programmatically instead of a change in the source file?

I have a transient class registered in ASP.NET Core's DI.
An IOptions<T> is injected into its constructor. For every request and when needed, during runtime and based on a condition, I want to have another instance of IOptions injected.
Since IOptionsSnapshot<T> gets updated for every request when the source file gets updated, is there a way to mimic this behavior but instead of a change in the file, I want to programmatically make a change in IOptions, and before constructor injection, during runtime when a request comes in?
And use IOptionsSnapshot<T> instead of IOptions<T> for that.
Update:
Condition example to be run before injection happens somewhere in the app like maybe a controller or action custom attribute? In the attribute check a value and so:
if (some condition)
Options.cnnectionstring = "string1";
else
Options.cnnectionstring = "string2";
Injected into a class like this:
public class Books
{
private readonly string connectionString;
public Books(IOptions<DBOptions> options)
{
this.connectionString = options.Value.connectionString;
}
public void SomeMethod()
{
.... //uses connectionString
}
}
Registered like this:
services.Configure<DBOptions>(options =>
{
options.connectionString = "some connection string";
});
IOption (like IConfiguration) is registered as singleton, but the request is scoped. Then it isn't possible to use request's information to modify the configuration.
You can use a intermediate scoped service, that retrieve the request's information and generate the desired connection string, like :
public class BooksConnectionString
{
public IConfiguration _configuration;
public IHttpContextAccessor _httpContextAccessor;
public BooksConnectionString(IConfiguration configuration, IHttpContextAccessor httpContextAccessor)
{
_configuration = configuration;
_httpContextAccessor = httpContextAccessor;
}
public string ConnectionString
{
get
{
var library = _httpContextAccessor.HttpContext.Request.Query["library"].First();
return _configuration.GetConnectionString(library);
}
}
}
Register the service as scoped :
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
...
services.AddHttpContextAccessor();
services.AddScoped<BooksConnectionString>();
}
...
}
Then you can inject like :
public class Books
{
private readonly string connectionString;
public Books(BooksConnectionString options)
{
this.connectionString = BooksConnectionString.ConnectionString;
}
}
It's suppose you can modify the class where the connection string is injected. If you can't modify this class, then you can register IOption as scoped :
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
...
services.AddHttpContextAccessor();
services.AddScoped<IOptions<DBOptions>>(p =>
{
var configuration = p.GetService<IConfiguration>();
var httpContextAccessor = p.GetService<IHttpContextAccessor>();
var library = httpContextAccessor.HttpContext.Request.Query["library"].First();
var dbOptions = configuration.GetSection("Databases").GetSection(library).Get<DBOptions>();
return Options.Create(dbOptions);
});
}
...
}
Warning, IOption are expected as singleton. Register IOption as scoped would break this expectation. To be used as a last resort.

Use Dependency Injection outside Controller asp.net-core v1.1.0 for application settings

I am using asp.net-core v1.1.0 :)
I want to access the application settings values from a service class and not from a controller, my code is:
appsettings.json
// appsettings.json
{
"ImagesDBSettings": {
"Endpoint": "",
"Key": "",
}
}
Startup.cs
// Startup.cs
...
public void ConfigureServices(IServiceCollection services)
{
...
services.AddOptions();
services.Configure<ImagesDBSettings>(Configuration.GetSection("ImagesDBSettings"));
...
}
...
ImagesDBSettings.cs
// ImagesDBSettings.cs
public class ImagesDBSettings
{
public string Endpoint { get; set; }
public string Key { get; set; }
}
ImagesDBService.cs
// ImagesDBService.cs
public class ImagesDBService
{
private readonly ImagesDBSettings _settings;
public ImagesDBService(IOptions<ImagesDBSettings> settings)
{
_settings = settings.Value;
}
}
On compiling I get the error:
There is no argument given that corresponds to the required formal parameter 'settings' of 'ImagesDBService.ImagesDBService(IOptions)'
Any ideas on how to make the Dependency Injection Work?
IOptions dependancy will not be injected into the ImagesDBService with the code shown. You need to use AddTransient in startup.cs for that. For the DI outside controller see this question. Or you can pass IOptions to your service class from the controller (not the best option).
public IActionResult Index(IOptions<ImagesDBSettings> settings)
{
ImagesDBService ss = new ImagesDBService(settings);
return View();
}
Here is how I do it in my app without the DI. I have a static AppSettings class that I configure in ConfigureServices (startup.cs). I then simply have access to my AppSettings anywhere in the app.
public static class AppSettings
{
public static string ConnectionString { get; set; }
}
Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
AppSettings.ConnectionString = Configuration.GetConnectionString("DefaultConnection");
}
You said it's a compiling error so probably not something to do with DI, it's more likely you are missing the IOptions<> namespace.
If you haven't done it already, install package: Microsoft.Extensions.Options.ConfigurationExtensions from Nuget.
Then reference the namespace Microsoft​.Extensions​.Options to your class ImagesDBService

How to access the ASP.NET Core DI Container from within a class

I am learning IoC & DI with Asp.net core.
I have setup my DbContext and other classes to be injected into my controllers.
Currently my startup.cs looks like this:
// Add framework services.
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.Password.RequireDigit = false;
options.Password.RequiredLength = 5;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireLowercase = false;
options.Password.RequireUppercase = false;
}).AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddMvc();
services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
As you can see amongst other things I am injecting AppSettings class. I have no problem accessing this class like this:
private readonly AppSettings _appSettings;
public HomeController(UserManager<ApplicationUser> userManager,
ApplicationDbContext dbContext,
ViewRender view,
IHostingEnvironment env,
IOptions<AppSettings> appSettings
)
{
}
Passing it into the constructor of a controller works fine.
But I need to access the AppSettings in a class, and was hoping there was a static method I could use to inject the class into any random class. Is this possible? Or do I need to inject it into the controller and pass it to each other class?
Prevent injecting IOptions<T> dependencies into application classes. Doing so is riddled with problems as described here.
Likewise is the injection of a AppSettings into classes a problem, because this means that all classes get all configuration values injected, while they only use one or two of those values. This makes those classes harder to test, and it becomes much harder to figure out which configuration value such class actually requires. It also pushes the verification of the configuration to inside your application, which makes your application much more fragile; you'll find out much later when a configuration value is missing, instead of finding out when the application is started.
A class should specify the things it requires in its constructor, and should not pass those dependencies around through other classes. This holds for both injected components and configuration values. This means that in case a class requires a specific configuration value, it should specify that -and only that- value in its constructor.
Update
The "email class" that contains this SendVerification() method that you mention, seems like an application component to me. Since that class sends the actual mail, it is the one that requires all those mail configuration settings; not the controller! So those settings should be injected directly into that component. But again, refrain from injecting anything general (such as IOptions<T>, AppSettings or IConfiguration) into that class. 1 Be as specific as possible as what that class needs and 2. make sure configuration values are read at application startup where you can let the application fail fast when the application starts up.
So I imagine your "mail class" to be defined by an abstraction as follows:
public interface IVerificationSender
{
void SendVerification(User user);
}
This allows your controller to take a dependency on this abstraction. Note that no component should create dependencies of application components itself. This is an anti-pattern known as Control Freak (see this book).
// Controller that depends on the IVerificationSender abstraction
public class HomeController : Controller
{
private readonly IVerificationSender verificationSender;
public HomeController(IVerificationSender verificationSender, ...) {
this.verificationSender = verificationSender;
}
public void SomeAction() {
this.verificationSender.SendVerification(user);
}
}
Now we have a IVerificationSender implementation that uses mail to send messages (that's your "mail class" thingy). That class is companioned by a Parameter Object that holds all the configuration values that this class requires (but absolutely nothing more than that).
// Settings class for the IVerificationSender implementation
public class SmtpVerificationSenderSettings
{
public string MailHost { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public bool EnableSsl { get; set; }
// etc
}
public class EmailVerificationSender : IVerificationSender
{
private readonly SmtpVerificationSenderSettings settings;
public EmailVerificationSender(SmtpVerificationSenderSettings settings) {
if (settings == null) throw new ArgumentNullException("settings");
this.settings = settings;
}
public void SendVerification(User user) {
using (var client = new SmtpClient(this.settings.MailHost, 25)) {
smtpClient.EnableSsl = this.settings.EnableSsl;
using (MailMessage mail = new MailMessage()) {
mail.From = new MailAddress("info#foo", "MyWeb Site");
mail.To.Add(new MailAddress(user.Email));
mail.Body = $"Hi {user.Name}, Welcome to our site.";
client.Send(mail);
}
}
}
}
Using this approach, registration of both the controller and the EmailVerificationSender should be trivial. You can even use this SmtpVerificationSenderSettings as serializable object that is loaded from the configuration file:
IConfiguration config = new ConfigurationBuilder()
.SetBasePath(appEnv.ApplicationBasePath)
.AddJsonFile("settubgs.json");
.Build();
var settings = config.GetSection("SmtpVerificationSenderSettings")
.Get<SmtpVerificationSenderSettings>();
// Verify the settings object
if (string.IsNullOrWhiteSpace(settings.MailHost)
throw new ConfigurationErrorsException("MailSettings MailHost missing.");
if (string.IsNullOrWhiteSpace(settings.MailHost)
throw new ConfigurationErrorsException("MailSettings UserName missing.");
// etc
// Register the EmailVerificationSender class
services.AddSingleton<IVerificationSender>(new EmailVerificationSender(settings));
Where the settings.json might look as follows:
{
"SmtpVerificationSenderSettings": {
"MailHost" : "localhost",
"UserName" : "foobar",
// etc
}
}

How do I access Configuration in any class in ASP.NET Core?

I have gone through configuration documentation on ASP.NET core. Documentation says you can access configuration from anywhere in the application.
Below is Startup.cs created by template
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.IsEnvironment("Development"))
{
// This will push telemetry data through Application Insights pipeline faster, allowing you to view results immediately.
builder.AddApplicationInsightsSettings(developerMode: true);
}
builder.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
// 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.AddApplicationInsightsTelemetry(Configuration);
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, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseApplicationInsightsRequestTelemetry();
app.UseApplicationInsightsExceptionTelemetry();
app.UseMvc();
}
}
So in Startup.cs we configure all the settings, Startup.cs also has a property named Configuration
What I'm not able to understand how do you access this configuration in controller or anywhere in the application? MS is recommending to use options pattern but I have only 4-5 key-value pairs so I would like not to use options pattern. I just wanted to have access to Configuration in application. How do I inject it in any class?
Update
Using ASP.NET Core 2.0 will automatically add the IConfiguration instance of your application in the dependency injection container. This also works in conjunction with ConfigureAppConfiguration on the WebHostBuilder.
For example:
public static void Main(string[] args)
{
var host = WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(builder =>
{
builder.AddIniFile("foo.ini");
})
.UseStartup<Startup>()
.Build();
host.Run();
}
It's just as easy as adding the IConfiguration instance to the service collection as a singleton object in ConfigureServices:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IConfiguration>(Configuration);
// ...
}
Where Configuration is the instance in your Startup class.
This allows you to inject IConfiguration in any controller or service:
public class HomeController
{
public HomeController(IConfiguration configuration)
{
// Use IConfiguration instance
}
}
The right way to do it:
In .NET Core you can inject the IConfiguration as a parameter into your Class constructor, and it will be available.
public class MyClass
{
private IConfiguration configuration;
public MyClass(IConfiguration configuration)
{
ConnectionString = new configuration.GetValue<string>("ConnectionString");
}
Now, when you want to create an instance of your class, since your class gets injected the IConfiguration, you won't be able to just do new MyClass(), because it needs a IConfiguration parameter injected into the constructor, so, you will need to inject your class as well to the injecting chain, which means two simple steps:
1) Add your Class/es - where you want to use the IConfiguration, to the IServiceCollection at the ConfigureServices() method in Startup.cs
services.AddTransient<MyClass>();
2) Define an instance - let's say in the Controller, and inject it using the constructor:
public class MyController : ControllerBase
{
private MyClass _myClass;
public MyController(MyClass myClass)
{
_myClass = myClass;
}
Now you should be able to enjoy your _myClass.configuration freely...
Another option:
If you are still looking for a way to have it available without having to inject the classes into the controller, then you can store it in a static class, which you will configure in the Startup.cs, something like:
public static class MyAppData
{
public static IConfiguration Configuration;
}
And your Startup constructor should look like this:
public Startup(IConfiguration configuration)
{
Configuration = configuration;
MyAppData.Configuration = configuration;
}
Then use MyAppData.Configuration anywhere in your program.
Don't confront me why the first option is the right way, I can just see experienced developers always avoid garbage data along their way, and it's well understood that it's not the best practice to have loads of data available in memory all the time, neither is it good for performance and nor for development, and perhaps it's also more secure to only have with you what you need.
I know this is old but given the IOptions patterns is relatively simple to implement:
Class with public get/set properties that match the settings in the configuration
public class ApplicationSettings
{
public string UrlBasePath { get; set; }
}
register your settings
public void ConfigureServices(IServiceCollection services)
{
...
services.Configure<ApplicationSettings>(Configuration.GetSection("ApplicationSettings"));
...
}
inject via IOptions
public class HomeController
{
public HomeController(IOptions<ApplicationSettings> appSettings)
{ ...
appSettings.Value.UrlBasePath
...
// or better practice create a readonly private reference
}
}
I'm not sure why you wouldn't just do this.
There is also an option to make configuration static in startup.cs so that what you can access it anywhere with ease, static variables are convenient huh!
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
internal static IConfiguration Configuration { get; private set; }
This makes configuration accessible anywhere using Startup.Configuration.GetSection... What can go wrong?
I'm doing it like this at the moment:
// Requires NuGet package Microsoft.Extensions.Configuration.Json
using Microsoft.Extensions.Configuration;
using System.IO;
namespace ImagesToMssql.AppsettingsJson
{
public static class AppSettingsJson
{
public static IConfigurationRoot GetAppSettings()
{
string applicationExeDirectory = ApplicationExeDirectory();
var builder = new ConfigurationBuilder()
.SetBasePath(applicationExeDirectory)
.AddJsonFile("appsettings.json");
return builder.Build();
}
private static string ApplicationExeDirectory()
{
var location = System.Reflection.Assembly.GetExecutingAssembly().Location;
var appRoot = Path.GetDirectoryName(location);
return appRoot;
}
}
}
And then I use this where I need to get the data from the appsettings.json file:
var appSettingsJson = AppSettingsJson.GetAppSettings();
// appSettingsJson["keyName"]
I know there may be several ways to do this, I'm using Core 3.1 and was looking for the optimal/cleaner option and I ended up doing this:
My startup class is as default
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.AddControllers();
}
My appsettings.json is like this
{
"CompanySettings": {
"name": "Fake Co"
}
}
My class is an API Controller, so first I added the using reference and then injected the IConfiguration interface
using Microsoft.Extensions.Configuration;
public class EmployeeController
{
private IConfiguration _configuration;
public EmployeeController(IConfiguration configuration)
{
_configuration = configuration;
}
}
Finally I used the GetValue method
public async Task<IActionResult> Post([FromBody] EmployeeModel form)
{
var companyName = configuration.GetValue<string>("CompanySettings:name");
// companyName = "Fake Co"
}
I looked into the options pattern sample and saw this:
public class Startup
{
public Startup(IConfiguration config)
{
// Configuration from appsettings.json has already been loaded by
// CreateDefaultBuilder on WebHost in Program.cs. Use DI to load
// the configuration into the Configuration property.
Configuration = config;
}
...
}
When adding Iconfiguration in the constructor of my class, I could access the configuration options through DI.
Example:
public class MyClass{
private Iconfiguration _config;
public MyClass(Iconfiguration config){
_config = config;
}
... // access _config["myAppSetting"] anywhere in this class
}
In 8-2017 Microsoft came out with System.Configuration for .NET CORE v4.4. Currently v4.5 and v4.6 preview.
For those of us, who works on transformation from .Net Framework to CORE, this is essential. It allows to keep and use current app.config files, which can be accessed from any assembly. It is probably even can be an alternative to appsettings.json, since Microsoft realized the need for it. It works same as before in FW. There is one difference:
In the web applications, [e.g. ASP.NET CORE WEB API] you need to use app.config and not web.config for your appSettings or configurationSection. You might need to use web.config but only if you deploying your site via IIS. You place IIS-specific settings into web.config
I've tested it with netstandard20 DLL and Asp.net Core Web Api and it is all working.
Using the Options pattern in ASP.NET Core is the way to go. I just want to add, if you need to access the options within your startup.cs, I recommend to do it this way:
CosmosDbOptions.cs:
public class CosmosDbOptions
{
public string ConnectionString { get; set; }
}
Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
// This is how you can access the Connection String:
var connectionString = Configuration.GetSection(nameof(CosmosDbOptions))[nameof(CosmosDbOptions.ConnectionString)];
}
I have to read own parameters by startup.
That has to be there before the WebHost is started (as I need the “to listen” url/IP and port from the parameter file and apply it to the WebHost). Further, I need the settings public in the whole application.
After searching for a while (no complete example found, only snippets) and after various try-and-error's, I have decided to do it the “old way" with an own .ini file.
So.. if you want to use your own .ini file and/or set the "to listen url/IP" your own and/or need the settings public, this is for you...
Complete example, valid for core 2.1 (mvc):
Create an .ini-file - example:
[Startup]
URL=http://172.16.1.201:22222
[Parameter]
*Dummy1=gew7623
Dummy1=true
Dummy2=1
whereby the Dummyx are only included as example for other date types than string (and also to test the case “wrong param” (see code below).
Added a code file in the root of the project, to store the global variables:
namespace MatrixGuide
{
public static class GV
{
// In this class all gobals are defined
static string _cURL;
public static string cURL // URL (IP + Port) on that the application has to listen
{
get { return _cURL; }
set { _cURL = value; }
}
static bool _bdummy1;
public static bool bdummy1 //
{
get { return _bdummy1; }
set { _bdummy1 = value; }
}
static int _idummy1;
public static int idummy1 //
{
get { return _idummy1; }
set { _idummy1 = value; }
}
static bool _bFehler_Ini;
public static bool bFehler_Ini //
{
get { return _bFehler_Ini; }
set { _bFehler_Ini = value; }
}
// add further GV variables here..
}
// Add further classes here...
}
Changed the code in program.cs (before CreateWebHostBuilder()):
namespace MatrixGuide
{
public class Program
{
public static void Main(string[] args)
{
// Read .ini file and overtake the contend in globale
// Do it in an try-catch to be able to react to errors
GV.bFehler_Ini = false;
try
{
var iniconfig = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddIniFile("matrixGuide.ini", optional: false, reloadOnChange: true)
.Build();
string cURL = iniconfig.GetValue<string>("Startup:URL");
bool bdummy1 = iniconfig.GetValue<bool>("Parameter:Dummy1");
int idummy2 = iniconfig.GetValue<int>("Parameter:Dummy2");
//
GV.cURL = cURL;
GV.bdummy1 = bdummy1;
GV.idummy1 = idummy2;
}
catch (Exception e)
{
GV.bFehler_Ini = true;
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("!! Fehler beim Lesen von MatrixGuide.ini !!");
Console.WriteLine("Message:" + e.Message);
if (!(e.InnerException != null))
{
Console.WriteLine("InnerException: " + e.InnerException.ToString());
}
Console.ForegroundColor = ConsoleColor.White;
}
// End .ini file processing
//
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>() //;
.UseUrls(GV.cURL, "http://localhost:5000"); // set the to use URL from .ini -> no impact to IISExpress
}
}
This way:
My Application config is separated from the appsettings.json and I
have no sideeffects to fear, if MS does changes in future versions ;-)
I have my settings in global variables
I am able to set the "to listen url" for each device, the applicaton run's on (my dev machine, the intranet server and the internet server)
I'm able to deactivate settings, the old way (just set a * before)
I'm able to react, if something is wrong in the .ini file (e.g. type mismatch)
If - e.g. - a wrong type is set (e.g. the *Dummy1=gew7623 is activated instead of
the Dummy1=true) the host shows red information's on the console
(including the exception) and I' able to react also in the
application (GV.bFehler_Ini ist set to true, if there are errors with
the .ini)

Categories

Resources