Providing configurations to static classes in .Net Core - c#

I'm curious regarding what approaches can be taken to solve the following problem.
I was using Properties.Settings.Default in a WPF app to store some configurations. It occurred to me that it should be possible to decouple the code from this variable. So I've come up with something like this to enable the DI of a configuration class:
public partial class App : Application
{
private ServiceProvider _serviceProvider;
protected override void OnStartup(StartupEventArgs e)
{
//configureSservices and buildServiceProvider
}
private void ConfigureServices(IServiceCollection services)
{
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.Build();
services.AddSingleton<IConfiguration>(configuration);
services.AddTransient<MainWindow>();
services.AddTransient<IMainViewModel, MainViewModel>();
services.AddTransient<IDataService, DataService>();
}
}
Considering this code, if I want to use the Configuration in other classes I can do it like this:
public class MyClass
{
private readonly IConfiguration _config;
public MyClass(IConfiguration config)
{
_config = config;
var mySetting = _config["MySetting"];
}
//or even like this...
public void MyMethod()
{
var config = ServiceProvider.GetService<IConfiguration>();
var mySetting = config["MySetting"];
// ...
}
}
My problem now is how to use this configurations in static classes, that are already using Properties.Settings.Default on too many places, since none of the previous is possible.
I guess that one possible approach would be to refactor all the static methods like this:
public static class MyStaticClass
{
public static void MyMethod(IConfiguration config)
{
var mySetting = config["MySetting"];
}
}
What would be shortcomings to this solutions? Would it be better to make them none static (despite the fact that they only provide utility methods)?
Thank you for reading!

You cannot inject dependencies in to static classes because the scope/lifetime of static classes is independent of the main app thread.
By the time the processor hits the lines of code where DI is being registered in App those static classes have already fired up in memory.
Set some breakpoints on the register statements and the static class code and you'll see that static classes are initialized before your App.cs has even started.
The solution is to only use static classes for things that don't require configuration - utility type functions and things of that nature rather than functions that deal with the business logic of your application.
Ask yourself: Why are these static? Likely you won't have a great reason for that.

Related

Singleton for service that takes a parameter

In the DI container to create a singleton would the following be an ok way to do?
public void ConfigureServices(IServiceCollection services)
{
var botClient = new TelegramBotClient(_config["Tokens:Telegram"]);
services.AddSingleton(botClient);
}
TelegramBotClient is from the library that I'm using, so I can't start changing that.
A more orthadox way of handling a DI service with a configuration is to use the IOptions pattern. This way you aren't tightly coupling your startup object with the service. As it stands now, if your configuration changes, you have to modify your startup object.
A way to tackle this and keep your concerns separated, take a look at this:
TelegramBotClientService.cs
public interface ITelegramBotClientService
{
Task DoSomethingAsync();
}
public sealed class TelegramBotClientService : ITelegramBotClientService
{
private readonly TelegramConfigModel _config;
public TelegramBotClientService(IOptions<TelegramConfigModel> options)
{
_config = options.Value;
}
public Task DoSomethingAsync()
{
var token = _config.Token;
// ...
}
}
Startup.cs
// ...
public void ConfigureServices(IServiceCollection services)
{
services.Configure<TelegramConfigModel>(
Configuration.GetSection("TelegramConfig"));
services.AddSingleton<ITelegramBotClientService, TelegramBotClientService>();
}
// ...
appsettings.json
{
"TelegramConfig": {
"Token": "12345"
}
}
TelegramConfigModel.cs
public sealed class TelegramConfigModel
{
public string Token { get; set; }
}
This hasn't been tested, so there may be a typo somewhere, but, now your concerns are separated. The DI pipeline is now doing the instantiation and also injecting your configurations.
A side note
I noticed you may be injecting a singleton to maintain a bot. I would highly suggest you use IHostedService or BackgroundService and inject using AddHostedService to maintain something like a bot.

Getting logger and configuration from Dependency Injection?

As you can tell from this question I’m still a newbie with .Net Core and understanding about Dependency Injection.
I’m in the process of writing a .Net Core Console app and I was finally able to get to a point where I’m doing a little bit of DI for logging and configuration settings. What I’m not understanding is using DI when calling another class from within a class.
I created a class called AppHost which has a function called Run() in it. In my Program.cs I’ve setup DI and then will call the AppHost.Run() to execute my main code.
Inside of my AppHost I need to call some database functions in another file I’ve called Data/DataManager. My understanding was that I would setup the class in the DI container and would be able to get my logging and configuration from there. As far as I know, I’ve done that in my “host” declaration. However, when I call my DataManager.GetActiveEmployees() it’s wanting me to create an object since my DataManager is not set a static. When I create a DataManager object it is wanting me to pass in my logger and configuration since that is what is in the constructor of the class. I can do that but is sure seems like that is not the correct way to do it. I thought with DI I would be able to get the logger and configuration out of DI and not need to pass it into the object? Am I supposed to create the DataManager object and pass the logger and configuration from my AppHost into it?
Program.cs
var host = Host.CreateDefaultBuilder().ConfigureServices((context, services) =>
{
services.AddTransient<IAppHost, AppHost>();
services.AddTransient<IFileManager, FileManager>();
services.AddTransient<IDataManager, DataManager>();
services.AddLogging(builder =>
{
builder.AddNLog("nlog.config");
});
}).Build();
var svc = ActivatorUtilities.CreateInstance<AppHost>(host.Services);
svc.Run();
AppHost.cs
private void CheckEmailAddresses()
{
DataManager oDataManager = new DataManager();
var listEmployees = new List<Employee>();
listEmployees = oDataManager.GetActiveEmployees();
}
DataManager.cs
public class DataManager : IDataManager
{
private readonly ILogger<DataManager> _log;
private readonly IConfiguration _configuration;
public DataManager(ILogger<DataManager> log, IConfiguration config)
{
_log = log;
_configuration = config;
}
}
You've already registered your IDataManager in DI dependency. So, instead of doing new which killing the purpose of DI anyway you need to change your CheckEmailAddresses like this.
private void YouClassName()
{
private readonly IDataManager _dataManager;
public YouClassName(IDataManager dataManager)
{
_dataManager = dataManger.
}
private void CheckEmailAddresses()
{
var listEmployees = new List<Employee>();
listEmployees = _dataManager.GetActiveEmployees();
}
}
Now we are inject IDataManager into your class and your other dependencies like Logger will be build automatically.

Creating a static settings class in .net core class library

I am inviting criticism and feedback. A roast if you will.
What I have done here feels wrong and I want to know why.
To create a static settings class in .net core that can returns settings from the appsettings.json file. It does work but it uses ConfigurationBuilder on every accessing of settings.
public static class GeneralSettings
{
private static IConfigurationRoot Configuration = StartConfig();
private static IConfigurationRoot StartConfig()
{
var configBuilder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{environment}.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables();
return configBuilder.Build();
}
public static string ContextUserID
{
get
{
string result =
Configuration.GetSection("AppSettings").GetSection("ContextUserID").Value;
return result;
}
}
}
Given the above code, you're rebuilding the configuration every time its called. You may as well make it a singleton. And since singletons are bad, well something else is probably wrong. Your instinct this feels wrong is RIGHT!
Avoid static classes until you know they are static.
Usually, common 'helper' classes like this violate one or more SOLID principles. (This is probably why you have that feel of the code being 'wrong')
Read more on how 'helper' classes like this do not adhere to SOLID principles, and what those principles are in this blog article
If instead of a static class, were you to leverage .NET Core's built in dependency injection, we could easily turn this abstraction into a bit of code that adheres to SOLID principles. That of course doesn't solve your problem of being able to use your new IContextSettings in another static class, but does enable you to use this interface as a first class citizen directly where you need the ContextUserID, and you would simply use dependency injection in your Controller, or PageModel
public interface IContextSettings
{
string ContextUserID { get; }
}
public class ContextSettings : IContextSettings
{
private IConfiguration configuration;
public ContextSettings(IConfiguration configuration)
{
this.configuration = configuration;
}
public string ContextUserID => configuration.GetSection("AppSettings").GetSection("ContextUserID").Value;
}
Usage (RazorPages Example)
public class IndexModel : PageModel
{
private readonly IContextSettings settings;
public IndexModel(IContextSettings settings)
{
this.settings = settings;
}
public IActionResult OnGet()
{
var userId = settings.ContextUserID;
return Page();
}
}
Feels right... right?

Use IOptionsMonitor in Singleton service to reload values from appsettings on runtime

I'm currently working on an ASP.NET Core 2.2/.NET Framework 4.7.2 application (I had to install .NET Core 2.2 as a Framework within my .NET Framework 4.7.2 application). I need to retrieve the values from my appsettings.json file (the values can change on runtime through manually changing the appsettings.json file) in a singleton service. This works fine. However, the desired reloadOnChange does not work.
When I change a value in my appsettings.json file on runtime and then trigger a new request to my service logic, the new values are not being retrieved. I already tried to inject my singleton service as scoped, unfortunately in vain - the same result, no updated configuration.
I really don't know why the updated values from appsettings.json never appear in my service class on runtime.
My CreateWebHostBuilder method looks like this:
public IWebHostBuilder CreateWebHostBuilder()
{
var config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile(configFilePath, optional: false, reloadOnChange: true)
.Build();
return WebHost.CreateDefaultBuilder()
.UseStartup<Startup>()
.UseConfiguration(config);
}
My Startup class looks like this:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public static IConfiguration Configuration { get; private set; }
public void ConfigureServices(IServiceCollection services)
{
services.Configure<AppSettings>(Configuration);
services.AddSingleton<IFruitService, FruitService>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{ }
}
My FruitService looks like this:
public class FruitService : IFruitService
{
private readonly IOptionsSnapshot<AppSettings> appSettings;
public PomService(IOptionsSnapshot<AppSettings> appSettings)
{
this.appSettings = appSettings;
}
public async Task<FruitMix> StartFruitMix(List<Fruit> fruits)
{
// here I would need the changed values from the appsettings.json,
// but always the same, no change in any request...
var a = appSettings.Value.Test;
var b = appSettings.Value;
var c = appSettings.Get("");
}
}
My AppSettings class is very simple:
public class AppSettings
{
public string Test { get; set; }
}
My appsettings.json looks like this:
{
"test": "1234" // during runtime when I manually change 1234 to sadf or 567 nothing happens in my service class on a new request (nor in singleton neither in scoped mode... I cannot retrieve the new value in my service class.)
}
Do you know how to retrieve the changed values in the FruitService class?
Thank you very much
Options, even with reload enabled, do not change during the life of a request. However, it sounds like you have in fact made a new request and found that the options have still not changed.
I personally haven't encountered a scenario where I've needed to use IOptionsMonitor<TOptions> specifically. However, I do know that it internally uses a cache and has a manual ability to invalidate options in said cache. It's possible that it does not in fact automatically reload on change - not sure about that.
Regardless, it's more typical to use IOptionsSnapshot<TOptions> instead. This exists solely to reload options per request, so it would seem to meet your needs here. The only benefit to IOptionsMonitor<TOptions> seems to be its ability to actually watch for changes and notify a callback. Again, I haven't used this enough to tell if you're simply doing something wrong, but I don't think you actually need this anyways.

Azure Stateful Service - with remoting and custom singleton service

I would like to use remoting in a .net core stateful service. I have a custom class which needs to be added as a singleton. Is this possible?
First I tried to register the custom class in Startup.cs's ConfigureServices() method but then I realized this method will be never called, since I used return this.CreateServiceRemotingReplicaListeners(); to generate my replica listeners in the CreateServiceReplicaListeners() method, and removed the Kestrel configuration (which would make this method to be called).
Is there a way to make the Startup.cs's ConfigureServices() method to be called, or add the singleton service at another place while keeping the remoting configuration?
The CreateServiceReplicaListeners() method in the MyStefulService.cs class looks like the following:
protected override IEnumerable<ServiceReplicaListener>
CreateServiceReplicaListeners()
{
return this.CreateServiceRemotingReplicaListeners();
}
The ConfigureServices method in the Startup.cs looks like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IMyHandler>(x => new MyHandler());
}
Finally i found a solution to my problem: I used Autofac to make sure the class I register is the same instance wherever it is used.
I extended the Program.cs with the Autofac container, so this way I didn't need the Startup.cs class at all:
I defined static variable of my custom class and an Autofac container, then in Main() method I added the implementation:
public static IContainer AutofacContainer;
private static IMyHandler _handler;
private static void Main()
{
try
{
if (_autofacContainer == null)
{
var builder = new ContainerBuilder();
builder.RegisterType<MyHandler>()
.As<IMyHandler>()
.SingleInstance();
_autofacContainer = builder.Build();
_handler = autofacContainer.Resolve<IMyHandler>();
}
//[...] normal service registration continues here
}
catch (Exception e)
{
ServiceEventSource.Current.ServiceHostInitializationFailed(e.ToString());
throw;
}
}
Since the container is static and public, all the other classes in the project can access it and get the singleton instance.
Configuration, environment variables, etc. can also be configured from here, similarly to the Startup.cs.

Categories