How to use configuration data when configuring services in .Net 5.0? - c#

I'm trying to use already configured custom config-class to configure another service. Configuration gets data from local settings and Azure AppConfiguration store. This is my Startup code:
public void ConfigureServices(IServiceCollection services)
{
services.AddAzureAppConfiguration();
services.Configure<CustomConfig>(Configuration);
services.AddTransient<ISomeService, SomeService>((serviceProvider) =>
{
CustomConfig config = serviceProvider.GetRequiredService<IOptions<CustomConfig>>().Value;
return new SomeService(config);
});
//--------------------------------------------------------------------
services.AddRazorPages();
services.AddControllers();
}
But when SomeService is instantiated, my custom config-object doesn't contain data that should come from the Azure AppConfig. It has only data from the appsettings.json.
What is wrong and what can I do here?

So the short answer is: it's actually working.
I was suspecting some stupid error, and it was exactly the case (a couple of code lines was commented so data was not retrieved from Azure - shame on me).
Thank you #pinkfloydx33 for the reassurance that the pattern should work.
And in case if someone wonders about binding root config values - it works as well.
In my case, appsettings.json contains root values that I need to connect to Azure AppConfig store (primary and secondary endpoints, refresh interval and environment name which is used in key labels) and a few sections corresponding with external services: database, AAD B2C etc which are retrieved from Azure AppConfig.
So my custom class has root values and a few nested classes like this:
public class CustomConfig : ConfigRoot
{
// These root values come from appsettings.json or environment variables in Azure
[IsRequired]
public string Env { get; set; }
[IsRequired, Regex("RegexAppConfigEndpoint")]
public Uri AppConfigEndpointPrimary { get; set; }
[IsRequired, Regex("RegexAppConfigEndpoint")]
public Uri AppConfigEndpointSecondary { get; set; }
public int AppConfigRefreshTimeoutMinutes { get; set; } = 30;
// And these sections come from the Azure AppConfig(s) from the above
public ConfigSectionDb Db { get; set; } = new ConfigSectionDb();
public ConfigSectionB2c B2c { get; set; } = new ConfigSectionB2c();
// Some other sections here...
}
Here ConfigSection<...> classes contain in their turn other subclasses. So I have quite a hierarchy here. ConfigRoot here is an abstract class providing Validate method.
And it works: this services.Configure<CustomConfig>(Configuration); part gets all the data - the root and the sections from all configured providers. In my case, it's two Azure AppConfigs, appsettings.json, environment variables.

Related

Accessing top-level fields in AppSettings.json from injected configuration class

Say we have such AppSettings.json
{
"Region": Europe,
"WeirdService": {
"JustField": "value"
}
}
Registering WeirdService settings in separate, singleton class (or using options pattern) is fine, just:
service.AddSingleton(configuration.GetSection("WeirdService").Get<WeirdService>();
And at this point it's fine. I don't know however how to deal cleanly with this top-level properties like Region in my example.
I know I can just inject IConfiguration and use config.GetValue<string>("Region") or just access configuration directly, but I wonder if there is some clean, better way without hardcoding this stuff in services.
Edit
I forgot to mention. Team I'm currently working with uses .NET Core 3.1 as it's current LTS release.
I think the easiest way would be to just create a class for the toplevel keys. In your case you could create something like AppConfig with the single property Region. Then you just register it without getting a config section using the Configuration object, the Configure methods asks for a Configuration interface anyway and not a ConfigurationSection.
AppConfig:
public class AppConfig
{
public string? Region { get; set; }
}
Registration:
public static IServiceCollection AddOptions(this IServiceCollection services, IConfiguration configuration)
{
return services.Configure<AppConfig>(configuration);
}
Usage:
public class ExampleConsumer
{
public ExampleConsumer(IOptions<AppConfig> appConfig) {}
}
You got two options
Don't have any top level fields
All top level fields would go one level in. Your configuration would look something like:
{
"App": {
"Region": "east-us-2",
"ShowMaintenancePrompt": false
},
// other options follow
}
The advantage of this approach is you can keep adding to "App" as your application grows, and continue to use the options pattern.
Gather top-level fields into a class, and register that with DI
For a configuration like:
{
"Region": "east-us-2"
}
Create a AppConfig class like:
internal class AppConfig
{
public string? Region { get; set; }
}
And register this class with the DI:
var toplevelConfig = new AppConfig {
Region = configuration.GetValue<string>("Region")
};
services.AddSingleton<AppConfig>(toplevelConfig);
You can now inject AppConfig anywhere you'd like.
The only minor downside to this is that you cannot use the options pattern anymore.
Avoid injecting IConfiguration directly.
Updated
Method 1 Preferred way
The preferred way to read related configuration values is using the options pattern.
For more detail about the options pattern check the link below.
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-5.0
"WeirdService": {
"JustField": "value"
}
Create the WeiredServiceOptions class.
public class WeiredServiceOptions
{
public const string WeiredService = "WeiredService";
public string JustField { get; set; }
}
An options class:
Must be non-abstract with a public parameterless constructor.
All public read-write properties of the type are bound.
Fields are not bound. In the preceding code, WeiredService is not bound. The Position property is used so the string WeiredService doesn't need to be hardcoded in the app when binding the class to a configuration provider.
Calls ConfigurationBinder.Bind to bind the WeiredServiceOptions class to the WeiredService section.
var weiredServiceOptions = new PositionOptions();
configuration.GetSection(PositionOptions.Position).Bind(positionOptions);
An alternative approach when using the options pattern is to bind the WeiredService section and add it to the dependency injection service container. In the following code, WeiredServiceOptions is added to the service container with Configure and bound to the configuration
services.Configure<WeiredServiceOptions>(Configuration.GetSection(
WeiredServiceOptions.Position));
and then read the WeiredService Options.
private readonly WeiredServiceOptions _options;
public YourClassContructor(IOptions<WeiredServiceOptions> options)
{
_options = options
}
Console.WriteLine($"JustField: {_options.JustField}");
Method 2
service.AddSingleton(configuration.GetSection("WeirdService:JustField").value);
Method 3
service.AddSingleton(configuration["WeirdService:JustField"]);
Doing: services.AddSingleton(Configuration.GetSection("WeirdService").Get<WeirdService>());
will register WeirdService to the Ioc container without supporting Options pattern. Assuming this is what you are looking for. Here is what you could do:
Create a class with properties mathcing the top level configuration similar to the AppConfig class a couple of people have suggested in the answers
Register the AppConfig class with the Ioc as below:
services.AddSingleton(Configuration.Get<AppConfig>());
Note:
Doing Configuration.Get<AppConfig>() will bind matching properties on the AppConfig class with the corresponding values from appsettings.json
Feel free to skip properties for keys that you do not want to bind
The IConfiguration.Get<T> is an extension method defined in Microsoft.Extensions.Configuration.ConfigurationBinder just in case
You can bind section to a class as well, it allows clean usage without using much of the magic strings.
public class WeirdService{
public string JustField{ get; set;}
public string AnotherField{ get; set;}
}
In controller you can then define a field
private readonly WeirdService _weirdService = new WeirdService();
public UserController(IConfiguration configuration)
{
configuration.GetSection("WeirdService").Bind(_weirdService);
//_weirdService.JustField
//_weirdService.AnotherField
}
You can access Region field using
configuration.GetValue<string>("Region")
or other way is
public Startup(IConfiguration configuration, IWebHostEnvironment
webHostEnvironment)
{
Configuration = configuration;
environment = webHostEnvironment;
}
then you can just use
Configuration["Region"]

ASP Net Core 3, set global variable from database and use in view, controllers

First of all excuse me for my bad english ...
I'm looking for a way to use a class in all my project. I explain my need to you. Because I don't know where to go.
I'have a web project asp net core connected to sql server. Inside i have a public pages with different section (ex: payment, create, message, ads, register...), and i have a area admin (back office for me).
I have a class :
public class Section
{
public bool RegistrationEnabled { get; set; }
public bool PaymentEnabled { get; set; }
public bool PublicationEnabled { get; set; }
public bool MessageEnabled { get; set; }
}
I want this class to allow me to make certain part of the site accessible or not in my views or in my controllers.
Ex in view :
#if (Section.RegistrationEnabled)
{
// Display form register..
}
The data is saved in a table in sql server (with entity framework), and I must be able to modify it from my admin area. I would like it to be persistent without having to fetch it each time a page is called by a user.
I have explored different solution:
appSetting: but I can't edit from my admin interface I think?
Resource file: But I don't think I can edit it directly?
Singleton?
Azure ?
I'm out of ideas and I don't know where to go ...
It's a bit of a broad/general question, but not a difficult problem to solve.
You can create a table in your database, e.g. AppSettings, with a Name and a Value string column. Probably you'd want to do this with your normal EF Core code first approach, creating the models, then doing a migration.
a) Then, you'll want to create a component that will automatically read these values from the database and insert them into the app configuration. For example this seems to be a good implementation candidate.
But, this begs the question when/how do you want this to be updated, since you mention that you don't want it to be read from the database in each request. Still, if you want to make it dynamically configurable, you will need some sort of re-reading. Unless you're comfortable restarting your service each time you modify the configuration.
b) Another option, instead of the configuration injector above, is to create a custom service, e.g. AppSettingsService (or preferably a less general name), that you can register as a singleton scoped service in the container, and inject into your consuming classes. This service would read the values from the DB, cache them in memory, and expose them in an easy to consume way; plus it could expose a method for refreshing the values from the DB.
If you need help with the implementation details of either of the above, let me know and I can add more details and code samples by editing this answer. Without knowing a bit more of your preferences it would be too demanding to cover everything in detail.
You can use dynamic configuration in asp.net core to accomplish this, I think it is the best way and I recommend you to use this approach. you can find a step by step guide here.
Else you can get your set of values from the database and store them in the session when the application is started. [Source]
First install nuget package - Microsoft.AspNetCore.Session
In Startup.cs file
public void ConfigureServices(IServiceCollection services)
{
services.AddDistributedMemoryCache();
// Add these 6 lines
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromSeconds(10);
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
});
.
.
.
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseSession(); //< --- And add this line
..............
}
And in C# you can set and retrive values from Session Storage
using Microsoft.AspNetCore.Http;
public class SomeController : Controller
{
public ActionResult Index()
{
HttpContext.Session.SetString("RegistrationEnabled", true ); // Set Session
var registrationEnabled = HttpContext.Session.GetString("RegistrationEnabled"); // Get Value of Session
}
}
Thanks, from your answers I tried to produce something that works, but I don't know if it's very relevant.
This is my setting class :
[Table("SETTING")]
public class Setting : BaseObject
{
[Key]
[StringLength(50)]
public string Key { get; set; }
[StringLength(50)]
public string Value { get; set; }
public e_SettingType Type { get; set; }
}
public enum e_SettingType
{
UNDEFINED,
BOOL,
BYTE,
CHAR,
DECIMAL,
DOUBLE,
FLOAT,
INT,
STRING,
}
And my service :
public class SettingService
{
private const string CACHE_KEY = "CurrentSetting";
private readonly IMemoryCache _cache;
private readonly IBaseRepository<Setting> _setting;
public SettingService(IMemoryCache cache, IBaseRepository<Setting> setting)
{
_cache = cache;
_setting = setting;
}
public async Task<bool> GetBool(string key)
{
var settings = await GetFromCache();
if (settings != null && settings.Any(x => x.Key.Equals(key)))
return bool.Parse(settings.First(x => x.Key.Equals(key)).Value);
else
return default;
}
public async Task<string> GetString(string key)
{
var settings = await GetFromCache();
if (settings != null && settings.Any(x => x.Key.Equals(key)))
return settings.First(x => x.Key.Equals(key)).Value;
else
return default;
}
private async Task<List<Setting>> GetFromCache()
{
if (!_cache.TryGetValue(CACHE_KEY, out List<Setting> data))
{
var models = await _setting.FindAll();
data = models.ToList();
var cacheOptions = new MemoryCacheEntryOptions()
.SetAbsoluteExpiration(TimeSpan.FromMinutes(30));
_cache.Set(CACHE_KEY, data, cacheOptions);
return data;
}
else
return data;
}
}
I've add this in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<SettingService>();
}
And for consume my setting in my view, i use this :
#inject MyProject.Service.SettingService setting //top of view
Get in my view :
#setting.GetString("MY_KEY").Result
But is this relevant? Or good practice?

ASP.Net Core 2 configuration taking up a lot of memory. How do I get config information differently?

Ok, so I have an app that gets a pretty good amount of traffic. I have been working with the Microsoft Azure and Coding teams to resolve a problem with memory. They have seen the GB's of logs and how found that the Microsoft.Extensions.Configuration code is taking up a lion's share of the RAM when we are under heavy load.
In my API code I have a "base controller" that all of the other controllers inherit from. This allows me to share common methods and the like. In this base controller I have created a global variable:
public IConfigurationRoot _configuration { get; }
This is, I believe, the culprit... but I am not sure how to get rid of it. This _configuration variable allows me to get access to my appsettings.json environment variables. I am not sure how to get access to these in a different way.
For instance... in a GET call I need to know if we have caching on or not.
bool isCaching = bool.Parse(_configuration["Data:Cache"]);
One thought I had is to make the _configuration private to the BaseController and make methods inside of there to get the properties I need (i.e. caching) so that the other controllers don't have to pass around this _configuration object.
Not sure if make it private will do anything though....
I am not sure why you need to be parsing the same values over and over again, when you could just read the configuration file during Startup and reuse it:
public class MyConfiguration
{
public bool CachingEnabled { get; set; }
// more configuration data
}
public void ConfigureServices(IServiceCollection services)
{
// your existing configuration
var myConfiguration = new MyConfiguration
{
CachingEnabled = bool.Parse(Configuration["Data:Cache"]),
// other properties
}
// register the data as a singleton since it won't change
services.AddSingleton(myConfiguration);
}
public class MyController : Controller
{
private readonly MyConfiguration configuration;
public MyController(MyConfiguration config)
{
configuration = config;
}
}

Access App key data from class libraries in .NET Core / ASP.NET Core

To access App Keys in a class library, do we need to do the following code in every class library and class where we need to access a AppKey?
public static IConfigurationRoot Configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();
This is what I found in Microsoft docs, but this looks very redundant.
Startup class in a project as below
public class Startup
{
public IConfigurationRoot Configuration { get; set; }
public Startup()
{
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json");
Configuration = builder.Build();
}
public void ConfigureServices(IServiceCollection services)
{
services.AddEntityFramework().AddEntityFrameworkSqlServer()
.AddDbContext<DbContext>(options =>
options.UseSqlServer(Configuration["Data:MyDb:ConnectionString"]));
}
}
Then how should I inject this "IConfigurationRoot" in each class of a project. And do I have to repeat this Startup class in each class Library? Why is this not part of .NET Core Framework?
The recommended way is to use the options pattern, provided by Microsoft and used heavily in ASP.NET Core.
Basically you create a strong typed class and configure it in the Startup.cs class.
public class MySettings
{
public string Value1 { get; set; }
public string Value2 { get; set; }
}
and initialize it in the Startup class.
// load it directly from the appsettings.json "mysettings" section
services.Configure<MySettings>(Configuration.GetSection("mysettings"));
// do it manually
services.Configure<MySettings>(new MySettings
{
Value1 = "Some Value",
Value2 = Configuration["somevalue:from:appsettings"]
});
then inject these options everywhere you need it.
public class MyService : IMyService
{
private readonly MySettings settings;
public MyService(IOptions<MySettings> mysettings)
{
this.settings = mySettings.Value;
}
}
By the principle of Information Hiding in Object-Oriented Programming, most classes should not need to have access to your application configuration. Only your main application class should need to directly have access to this information. Your class libraries should expose properties and methods to alter their behavior based on whatever criteria their callers deem necessary, and your application should use its configuration to set the right properties.
For example, a DateBox shouldn't need to know how timezone information is stored in your application configuration file - all it needs to know is that it has a DateBox.TimeZone property that it can check at runtime to see what timezone it is in.

How can a component provide other components?

I want to have a component register other components in the registry as / after it's constructed. Let's say I have the following components:
interface IConfiguration
{
string SourceDirectory { get; }
string TargetDirectory { get; }
// other primitive-typed configuration parameters
}
class FileConfiguration : IConfiguration
{
// read parameters from some config file
}
class SourceDirectoryWrapper
{
public byte[] ReadFile(string filename)
{
// read a file from the source directory
}
public string Directory { get; set; }
}
class TargetDirectoryWrapper
{
public byte[] WriteFile(string filename)
{
// write a file into the source directory
}
public string Directory { get; set; }
}
class DirectoryWrapperFactory
{
public DirectoryWrapperFactory(IConfiguration config)
{
var source = new SourceDirectoryWrapper {
Directory = config.SourceDirectory
};
var target = new TargetDirectoryWrapper {
Directory = config.SourceDirectory
};
}
}
The components FileConfiguration and DirectoryWrapperFactory can be registered as is usual.
However, what I'd like to accomplish is to somehow "outject" the source and target objects created in DirectoryWrapperFactory. The basic idea is that different environments might require different configuration providers. (And even if not, I think it's a good idea to put reading configuration parameters into a separate component.)
I'd also like to have SourceDirectoryWrapper and TargetDirectoryWrapper managed in the IoC container. In my case, mainly for convenience – I have an EventSource implementation that I need everywhere, so I inject it using property autowiring. Every object not in the IoC container needs to have it passed explicitly, which kind of bugs me.
So: is this possible with AutoFac? If so, how? I poked at the lifecycle events but most don't allow access to the registry after an object is built.
I don't quite understand why DirectoryWrapperFactory needs to exist. You could just register SourceDirectoryWrapper and TargetDirectoryWrapper directly as part of normal wireup:
builder.Register(c => new SourceDirectoryWrapper {
Directory = c.Resolve<IConfiguration>().SourceDirectory
});
builder.Register(c => new TargetDirectoryWrapper {
Directory = c.Resolve<IConfiguration>().SourceDirectory
});

Categories