Using IOptions and load options from database with multi tenant application - c#

I move an application to .net core and trying to use IOptions pattern.
My application is multi tenant with single database. System has global default options and I keep them in database (same as my old application) and also each tenant has own options. If tenant has no option with a key in global, so I need to use global option.
In configuration, I handle to getting global options from database. It is easy with example in documentation.
However, each tenant options not going well. Although I actually know what I want, I don't know how to do it in .Net Core.
I test in a console application.
class Program {
static void Main(string[] args) {
var services = ConfigureServices();
var serviceProvider = services.BuildServiceProvider();
serviceProvider.GetService<App>().Run();
}
public class App {
private readonly IOptionsSnapshot<DemoOptions> _options;
public App(IOptionsSnapshot<DemoOptions> options) {
_options = options;
}
public void Run() {
Console.WriteLine("Hello from App.cs");
Console.WriteLine($"DemoOptions:Global:Enabled={_options.Value.Enabled}");
Console.WriteLine($"DemoOptions:Global:AutoRetryDelay={_options.Value.AutoRetryDelay}");
Console.WriteLine($"DemoOptions:Global:IdentityOptions:MaxUserNameLength={_options.Value.IdentityOptions.MaxUserNameLength}");
}
}
private static IServiceCollection ConfigureServices() {
IServiceCollection services = new ServiceCollection();
//Load global configuration from database and use them.
var config = LoadConfiguration();
services.AddSingleton(config);
services.AddDbContext<EntityConfigurationContext>(options => options.UseInMemoryDatabase("InMemoryDb"));
services.AddScoped<ITenantService, TenantService>();
//I take this part from example link in below. But I am not successed.
services.AddSingleton<IOptionsMonitorCache<DemoOptions>,TenantOptionsCache<DemoOptions>>();
services.AddTransient<IOptionsFactory<DemoOptions>,TenantOptionsFactory<DemoOptions>>();
services.AddScoped<IOptionsSnapshot<DemoOptions>,TenantOptions<DemoOptions>>();
services.AddSingleton<IOptions<DemoOptions>,TenantOptions<DemoOptions>>();
// required to run the application
services.AddTransient<App>();
return services;
}
public static IConfiguration LoadConfiguration() {
var builder = new ConfigurationBuilder();
builder.Sources.Clear();
builder.AddEntityConfiguration(options => options.UseInMemoryDatabase("InMemoryDb"));
IConfigurationRoot configurationRoot = builder.Build();
DemoOptions options = new();
configurationRoot.GetSection($"{nameof(DemoOptions)}:{DemoOptions.Global}").Bind(options);
Console.WriteLine($"DemoOptions:Global:Enabled={options.Enabled}");
Console.WriteLine($"DemoOptions:Global:AutoRetryDelay={options.AutoRetryDelay}");
Console.WriteLine($"DemoOptions:Global:IdentityOptions:MaxUserNameLength={options.IdentityOptions.MaxUserNameLength}");
return builder.Build();
}
}
public record DemoOptions {
public const string Global = nameof(Global);
public const string Tenant = nameof(Tenant);
public bool Enabled { get; set; }
public TimeSpan AutoRetryDelay { get; set; }
public IdentityOptions IdentityOptions { get; set; }
}
public record IdentityOptions {
public int MaxUserNameLength { get; set; }
}
public record DemoSettings(string Key, string Value) {
public int Id { get; set; }
}
public record TenantSettings(string Key, string Value, int TenantId) {
public int Id { get; set; }
}
I add some important class here. However if you want to look at all project, I add github link.
I use this example

I see.
You ought to do these things
install a nuget package
Microsoft.Extensions.Options.ConfigurationExtensions
In your Programs.cs in ConfigureServices replace this code
var config = LoadConfiguration();
services.Configure<DemoOptions>(config.GetSection($"{nameof(DemoOptions)}:{DemoOptions.Global}"));
services.AddSingleton(config);

You can try ready-to-use JsonRepositoryConfiguration Nuget package. It also has a auto-refresh feature and works pretty much like configuration form JSON config files, only it expects the JSON config from your repository which can in turn bring the data from any internal/external storage including making a database call, API call etc.
You can mix different configuration provides (like this one and JSON config files) and use them together should you like to. Confession: I am the author.
The next step would be using Named Options.

Related

Dependency injection between two ASP.NET Core projects

I'm currently developing a web application with ASP.NET Core and handling the database with Entity Framework Core. I have two projects in my VS Solution; WebApp (the main application) and DatabaseHandler (the EF Core handler). I have installed Entity Framework Core with the Pomelo package, since I'm using a MySQL database.
I've been following the Microsoft documentation to setup EF Core, connection strings and all that, and it works fine. I'm able to make migrations, make updates and do stuff with the database. I'm however not sure if I'm doing it correctly, since the latest EF Core tutorials use dependency injection and I'm not familiar with it.
Right now I'm passing the DbContext object as an argument from WebApp to DatabaseHandler, since I want all database-related stuff to only exist in DatabaseHandler. This works, but is it possible to call functions from another project and also share the DbContext object without passing it as an argument? I'm probably not explaining it well, I hope my code explains it better.
WebApp/Startup.cs:
This is where I load the connection string from appsettings.json.
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContextPool<DataContext>(
options => options.UseMySql(Configuration.GetConnectionString("DefaultConnection")
));
services.AddRouting(options => options.LowercaseUrls = true);
services.AddControllersWithViews();
}
WebApp/HomeController.cs:
This is where I call the GetAllChallenges() function from the DatabaseHandler project, and I also pass the DataContext object as an argument. This is what I'm trying to avoid!
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly DataContext db;
public HomeController(ILogger<HomeController> logger, DataContext _db)
{
_logger = logger;
db = _db;
}
public IActionResult Challenges()
{
List<Challenge> ChallengesList = DatabaseHandler.HandleChallenges.GetAllChallenges(db);
return View(ChallengesList);
}
}
DatabaseHandler/DataContext.cs:
This is where I initialize the entity classes and so on.
public class DataContext : DbContext
{
public DataContext(DbContextOptions<DataContext> options) : base(options) { }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { }
// Tables
public DbSet<User> Users { get; set; }
public DbSet<Challenge> Challenges { get; set; }
// Data seeding
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Seed();
}
}
DatabaseHandler/HandleChallenges.cs:
This is where I have all my database functions. The results are returned back to the controller within the WebApp project.
public class HandleChallenges
{
public static List<Challenge> GetAllChallenges(DataContext db)
{
var Data = db.Challenges;
List<Challenge> ChallengesList = Data.ToList();
return ChallengesList;
}
}
I have looked into dependency injection, but I'm not sure how I can use this between two projects. Is there a less complicated way of achieving this, perhaps without using DI at all? I'm satisfied as long as I don't need to pass the DataContext object as an argument every time I need to call a function from DatabaseHandler.
Can someone help me understand? Thanks a lot in advance!
You could use Options pattern, which I have already used many times. Its working very well despite of database you use. Thanks to dependency injection you are able to access if from multiple projects. Reading documentation about Option pattern (https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-3.1) is useful but I will also provide you with my own example :
First you create model to store you connection string, dbName etc. Remember to add it in a library outside your main project(eg. Web Api) :
public class NameOfYourProject_ApiDbSettings : IIMTTApiDbSettings
{
public NameOfYourProject_ApiDbSettings()
{
}
public string CollectionName { get; set; }
public string ConnectionString { get; set; }
public string DatabaseName { get; set; }
}
public interface I_NameOfYourProject_ApiDbSettings
{
string CollectionName { get; set; }
string ConnectionString { get; set; }
string DatabaseName { get; set; }
}
Secondly you make it available for all you projects :
services.Configure<NameOfYourProjectApiDbSettings>(options =>
{
options.ConnectionString
= Configuration.GetSection("NameOfYourProjectDbSettings:ConnectionString").Value;
options.DatabaseName
= Configuration.GetSection("NameOfYourProjectDbSettings:DatabaseName").Value;
});
Then you can use it in multiple projects. (Rememebr to add referance to you model -> point 1. I keep the model always with repository) I will give you my example where I use MongoDb :
private readonly IMongoDatabase _database = null;
public SomeObjectContext(IOptions<IMyProjectDbSettings> settings)
{
var client = new MongoClient(settings.Value.ConnectionString);
if (client != null)
_database = client.GetDatabase(settings.Value.DatabaseName);
}
public IMongoCollection<MyModel> MyModels
{
get
{
return _database.GetCollection<MyModel>("MyModels");
}
}
You need to extract an interface from the class (note the method is no longer static) and add a constructor for the context:
public interface IHandleChallenges
{
List<Challenge> GetAllChallenges();
}
public class HandleChallenges : IHandleChallenges
{
public HandleChallenges(DataContext context)
{
db = context;
}
private DataContext db;
public List<Challenge> GetAllChallenges()
{
var Data = db.Challenges;
List<Challenge> ChallengesList = Data.ToList();
return ChallengesList;
}
}
Then register it as a service:
services.AddScoped<IHandleChallenges, HandleChallenges>();
Your controller now receives this class in it's constructor instead of the context:
private IHandleChallenges _challengeHandler;
public HomeController(ILogger<HomeController> logger, IHandleChallenges challengeHandler)
{
_logger = logger;
_challengeHandler = challengeHandler;
}
And calls it from the action:
public IActionResult Challenges()
{
List<Challenge> ChallengesList = _challengeHandler.GetAllChallenges();
return View(ChallengesList);
}

What's the correct flow of injecting Appsettings.json from a main application and how to do it

I have my main .net core application called AppOne. In its appsettings.json I define which api's it should be able to call. For example:
"ApiSettings": {
"UrlToCall": "http://test",
}
Then there is my intermediate and shared project library, called InfraApp that makes the call itself to the Api.
There might be a second app called AppTwo where the url is different.
Both AppOne and AppTwo reference the InfraApp since the logic is common and call the code in there to make the actual call. However the settings (that specifies which url to call) are specific to the api's themselves and therefore cannot be specified in the InfraApp.
Let's consider only AppOne so far.
Such settings are registered through the Options pattern (https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-3.0) into the startup.cs:
services.AddOptions();
var apiSettings = Configuration.GetSection("ApiSettings");
services.Configure<ApiSettings>(apiSettings);
and I have my ApiSettings class:
public class ApiSettings
{
public string UrlToCall { get; set; }
}
what is the correct way to pass such ApiSettings to the InfraApp ? InfraApp doesn't know anything about ApiSettings since this is defined in the AppOne. Should I defined the ApiSettings class into the InfraApp? IMHO sounds wrong because it is something specific about the AppOne api but maybe I am thinking in the wrong way. Thanks!
I think, if you have something like this:
public interface IApiSettings
{
string UrlToCall { get; set; }
}
public class ApiSettings:IApiSettings
{
public string UrlToCall { get; set; }
public ApiSettings()
{
...
Console.WriteLine($"ApiSettings");
...
}
}
public class IInfraApp{}
public class InfraApp : IInfraApp
{
private IApiSettings _ApiSettings;
//using Microsoft.Extensions.Options:
public InfraApp(IOptions<ApiSettings> settings)
{
_ApiSettings = (IApiSettings)settings.Value;
Console.WriteLine($"InfraApp {_ApiSettings.UrlToCall}");
}
}
then, you can add/register, something along these lines:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
...
...
//
services.Configure<ApiSettings>(Configuration);
services.AddTransient<IInfraApp, InfraApp>();
...
...
//get instance of infraapp:
var provider = services.BuildServiceProvider();
var infraapp = provider.GetService<IInfraApp>();
//
...
...
}

Passing configuration to webjobs via dependency injection

This question is based on comments associated with this answer.
To summarize, the question is how to pass configuration settings to a web job without injecting the IConfiguration interface as a dependency when calling ConfigureServices to set up dependencies.
I had thought this would be a good way to do it:
IHostBuilder builder = new HostBuilder;
...
builder.ConfigureServices((context, services) =>
{
services.AddSingleton<IMyModelClass, MyModelClass>(sp => new MyModelClass(context.Configuration));
services.AddSingleton<IMyServiceClass, MyServiceClass>(sp => new MyServiceClass(new MyModelClass()));
})
Here, MyModelClass is a class that reads the configuration settings, like this:
public class MyModelClass : IMyModelClass
{
public string MySetting { get; set; }
public MyModelClass(IConfiguration config)
{
this.MySetting = config["MySetting"];
}
}
It thus encapsulates those settings and can be passed to other classes (like MyServiceClass) that need to access the configuration settings.
But it seems this isn't the best way. Any further suggestions?
So lets assume there is the following configuration
{
"MyModelSection": {
"MySetting": "SomeValue"
}
}
This is just a very simple example.
The associated model would look like
public class MyModelClass: IMyModelClass {
public string MySetting { get; set;}
}
The above can be extracted from configuration and registered with services
builder.ConfigureServices((context, services) => {
var configuration = context.Configuration.
var myModel = configuration.GetSection("MyModelSection").Get<MyModelClass>();
services.AddSingleton<IMyModelClass, MyModelClass>(myModel);
services.AddSingleton<IMyServiceClass, MyServiceClass>();
})

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)

Application Settings in custom class ASP.Net 5 MVC 6

Playing around with ASP.Net 5 MVC. Seen this question jumping around but not an full answer. What I want to do is have a helper class that is able to access the AppSettings. I can access it in the controller and the view but haven't figured out how to access it on my own custom class. Have startup configured like so.
public Startup(IHostingEnvironment env)
{
// Set up configuration sources.
var builder = new ConfigurationBuilder()
.AddJsonFile("config.json")
.AddJsonFile($"config.{env.EnvironmentName}.json", optional: true);
builder.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; set; }
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddOptions();
services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
}
.................
.................
So in your config.json file, suppose you have following settings
{
"smtp": {
"SenderEmail": "a#b.com",
"SenderFrom": "Test User"
}
}
Then in your ConfigureServices method you need to do something like that
services.Configure<SmtpEmailSetting>(Configuration.GetSection("smtp"));
This is your SmtpEmailSetting looks like
public class SmtpEmailSetting
{
public string SenderEmail { get; set; }
public string SenderFrom { get; set; }
}
and this is how you access your settings in any service or controller
public class SendEmailService
{
private readonly SmtpEmailSetting _smtpEmailSetting;
public SendEmailService(IOptions<SmtpEmailSetting> smtpOptions )
{
_smtpEmailSetting = smtpOptions.Value;
}
public void SendEmail()
{
var fromEmail = _smtpEmailSetting.SenderEmail;
var displayName = _smtpEmailSetting.SenderFrom;
}
}
So basically you use your settings or options (whatever you prefer to call) should be used in constructor as a generic type parameter of IOptions<> class. Hope it helps
In order to access your AppSettings properties in your custom class, make configuration as a static instance such as:
public static IConfigurationRoot Configuration { get; set; }
and make use of your AppSettings any where in your application (for connectionstring example) as:
var connectionString = Startup.Configuration["Data:DefaultConnection:ConnectionString"];
Just to add to adeel41's answer, this is correct and works great, but for myself, I didn't want to drag around an IOption object when using dependency injection.
So I prefer to do something like
services.AddSingleton<ISmtpEmailSettings>(Configuration.GetSection("SmtpSettings").Get<SmtpEmailSettings>());
Most importantly is the Get syntax added to GetSection to deserialize your JSON to an object.
At the time of RC1, I took inspiration from this post by Rick Strahl, which worked great. That is very similar to other approaches already proposed.
This answer is just to update with my findings as of RTM release. It seems like Configuration.GetSection(string topLevelKey) does not work anymore, at least for me it always returns null (even if configuration sources are set correctly).
After some search, this other SO thread pointed me in the right direction, by using:
// create empty config object
var smtpEmailSetting = new SmtpEmailSetting();
// fill it from configuration section
Configuration.GetSection("smtp").Bind(smtpEmailSetting);
// use it, e.g. by registering it into DI
services.Configure<SmtpEmailSetting>(smtpEmailSetting);
HTH
If you need it in your own class, it's probably right to pass it into the constructor of that class, or as a parameter. Eg;
public class Notifications
{
public Notifications(AppSettings settings) {
this.settings = settings;
}
public void SendEmail(string subject, string body) {
SmptClient.Send(subject, body, settings["email address"]);
}
}
So typically, you'd pass it through from your controller.
This avoids a global variable, which is always a good thing, I think.

Categories

Resources