The problem to solve:
There is a list of settings lets say:
{
"Kind1":
{"attr1":"val11"},
{"attr2":"val12"},
"Kind2":
{"attr1":"val21"},
{"attr2":"val22"},
}
and a consumer class (controller) in .NET Core 2.1, needs to access the above configuration to use Kind1 or Kind2.
Supposing corresponding class is already defined in C#:
public class KindSetting
{
public string attr1{get;set;}
public string attr2{get;set;}
}
Now what is the best way of inject the configuration into the consumer object.
Is there a way to inject an IConfiguration instance into the consumer object and use it like this?:
KindSetting kindSetting =_configuration.GetValue<KindSetting>(kindSettingKey);
Is there any better approach to fulfill the above requirement?
In the startup.cs file, in the ConfigureServices method you can do a configuration. Sample code below:
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
//Need to add following lines
services.Configure<KindSetting>(Configuration.GetSection("Kind1"));
}
After adding into services, you can inject this configuration in your class, like follow:
public class HomeController : Controller
{
private readonly IOptions<KindSetting> _KindSetting;
public HomeController(IOptions<KindSetting> KindSetting)
{
_KindSetting = KindSetting
}
public void myFunction()
{
var mysetting = _KindSetting.Value.attr1
}
}
I used the following approach, however I am not sure be the best
in startup class configure method:
services.Configure<List<KindSetting>>(Configuration.GetSection("KindSettingList"));
and in consumer object side:
public ConsumerController(IOptions<List<KindSetting>> kindSettingsListAccessor,...)
After adding services.Configure<KindSettings>, you can inject configuration via DI by adding to your constructor.
IOptionsSnapshot<KindSettings> kindSettingsConfiguration
or
IOptions<KindSettings> kindSettingsConfiguration
The difference is, IOptionsSnapshot will reflect live changes in your config file, while IOptions is for singleton use.
Edit after comment:
Lets say your configuration file looks like this:
{
"Kind1":
{"attr1":"val11"},
{"attr2":"val12"},
"Kind2":
{"attr1":"val21"},
{"attr2":"val22"},
}
To successfully bind this, you would need two configuration classes
public class Kind1Configuration
{
public string Attr1 { get; set; }
public string Attr2 { get; set; }
}
public class Kind2Configuration
{
public string Attr1 { get; set; }
public string Attr2 { get; set; }
}
And as previously stated, to connect the dots just add
public void ConfigureServices(IServiceCollection services)
{
services.Configure<Kind1Configuration>(Configuration.GetSection("Kind1"));
services.Configure<Kind2Configuration>(Configuration.GetSection("Kind2"));
}
To use this in a controller, add your IOptions to the constructor
public class TestController(IOptionsSnapshot<Kind1Configuration> kindSettingsConfiguration)
{
Kind1Configuration configuration = kindSettingsConfiguration.Value;
}
Hope it helps.
Related
I'm reviewing a code I wrote sometime before and I noticed I did in past
public class Linq2DbSettings : ILinqToDBSettings
{
public IEnumerable<IDataProviderSettings> DataProviders
{
get { yield break; }
}
public string DefaultConfiguration =>
"SqlServer"; // lets set your configuration as default, so you can call just new DataContext() or new DataConnection()
public string DefaultDataProvider => ProviderName.SqlServer; // and set default database type
public IEnumerable<IConnectionStringSettings> ConnectionStrings
{
get
{
yield return
new ConnectionStringSettings
{
Name = "SqlServer",
ProviderName = "SqlServer",
ConnectionString =ConfigurationManager.ConnectionStrings["default"].ConnectionString
};
}
}
}
public class ConnectionStringSettings : IConnectionStringSettings
{
public string ConnectionString { get; set; }
public string Name { get; set; }
public string ProviderName { get; set; }
public bool IsGlobal => false;
}
Even if it's related to Linq2Db it appies to all classes where I need to resolve the container.
As you can see I'm using here ConfigurationManager.ConnectionStrings["default"] while it would be best to use IConfiuration from Microsoft.Extensions.Configuration
To do so I should resolve the IConfiguration item, registered in SimpleInjector's Container.
In past I used a wrapper
public static class ContainerWrapper
{
public static Container Container { get; set; }
}
and I assigned it as
ContainerWrapper.Container = container;
container.Verify();
But I think it's a wrong approach, what's the best solution?
My advise is the following:
Keep your configuration objects narrow; don't create wide configuration objects that contain a large set of propertiesand are used by many consumers.
Prevent configuration objects from reading from the configuration system. Make them immutable behaviorless data objects instead, and supply them with configuration values in their constructor. This prevents the configuration object from becoming a Volatile Dependency.
Remove the interfaces on your configuration objects. Interfaces are meant to hide behavior, but the configuration objects should only contain data.
Load configuration values during application startup, and register those configuration objects as Singleton in the container.
I'm creating a service that requires some config parameters and a logger. Here is the constructor for my service:
public StorageProvider(string directory, ILogger<StorageProvider> logger)
I just added the logger. I used to initalize it like this in my startup.cs:
services.AddSingleton<IStorageProvider>(
new StorageProvider(Configuration["TempStorage.Path"]));
The directory parameter comes from the config file, and the logger gets DI'ed. How do I setup my IStorageProvider?
You should do the following:
Wrap the configuration value TempStorage:Path into its own configuration class, e.g. StorageProviderSettings.
Let StorageProvider depend upon that new configuration class.
Register that configuration class as singleton into the ASP.NET configuration system.
Example:
public sealed class StorageProviderSettings
{
public readonly string TempStoragePath;
public StorageProviderSettings(string tempStoragePath)
{
if (string.IsNullOrWhiteSpace(tempStoragePath))
throw new ArgumentException(nameof(tempStoragePath));
this.TempStoragePath = tempStoragePath;
}
}
public sealed class StorageProvider : IStorageProvider
{
public StorageProvider(
StorageProviderSettings settings, ILogger<StorageProvider> logger)
{
// ...
}
}
// Registration
services.AddSingleton(new StorageProviderSettings(Configuration["TempStorage.Path"]));
services.AddSingleton<IStorageProvider, StorageProvider>();
Use the Options pattern as Tratcher suggests in a comment. Read more in the official docs on Configuration.
Basically you define a class to be hold the value you need:
public class StorageProviderOptions
{
public string TempStoragePath { get; set; }
}
Then in ConfigureServices you register the type:
services.Configure<StorageProviderOptions>();
In your code, you request IOptions<StorageProviderOptions> and set this to an instance of StorageProviderOptions:
public class SomeController
{
private readonly StorageProviderOptions _options;
public SomeController(IOptions<StorageProviderOptions> options)
{
_options = options.Value;
}
}
Finally, make sure you have an element in your configuration source that matches the TempStoragePath name. Alternately, you can register the option in ConfigureServices using code:
services.Configure<ServiceProviderOptions>(o => o.TempStoragePath = "temp");
My controller which returns user details by calling an API intern
public class HomeController : Controller
{
public ActionResult AccountDetails(int userId)
{
return this.Content(new WebHelperService().GetAccountDetails(userId)), "application/json");
}
}
Here is my WebHelperService which is in Business Layer, where i need to get value from appsettings.json
public class WebHelperService
{
private string url = null;
public WebHelperService()
{
//url = ConfigurationManager.ConnectionString["ExternalApiUrl"].ToString();
// ConfigurationManager is not available in .net core.
//So How do i read ExternalApiUrl from appsettings.josn,Which is the best way
}
public string GetAccountDetails(int userId)
{
return WebCall("{'userId':" + userId + "}");
}
private string WebCall(string data)
{
WebRequest request = WebRequest.Create(url);
// get the data from url and returns it
}
}
Do I need to carry settings all the way from controller in mvc6?
Reference : learn.microsoft.com/en-us/aspnet/core/mvc/con..)
Let's forget for a moment your particular use case and just talk about settings in .net core in general. Importantly, I think you are trying to access the raw AppSettings from your class, but what you actually want to do is DI them into your class. So let's do that.
Consider you have a appSettings.json that resembles something like below :
{
"myConfiguration": {
"myProperty": true
}
}
Now you need to create a POCO to hold these settings. Something like so :
public class MyConfiguration
{
public bool MyProperty { get; set; }
}
In your startup.cs you should have a method called "ConfigureServices". In there you are going to place a call to "configure" your settings like so.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<MyConfiguration>(Configuration.GetSection("myConfiguration"));
}
And so now you want to inject that settings object into a class. Let's call it MyClass for now. It would look like the following :
public class MyClass : IMyClass
{
private readonly MyConfiguration _myConfiguration;
public MyClass(IOptions<MyConfiguration> myConfiguration)
{
_myConfiguration = myConfiguration.Value;
}
}
Now you have access to your configuration!
Bonus!
Instead you can make your ConfigureServices method look like the following :
public void ConfigureServices(IServiceCollection services)
{
//services.Configure<MyConfiguration>(Configuration.GetSection("myConfiguration"));
services.AddSingleton(Configuration.GetSection("myConfiguration").Get<MyConfiguration>());
}
What this now does is bind your services onto an actual class, not the IOptions object.
Now when you inject it into your class, you instead inject the POCO settings class, not IOptions. Like so :
public class MyClass : IMyClass
{
private readonly MyConfiguration _myConfiguration;
public MyClass(MyConfiguration myConfiguration)
{
_myConfiguration = myConfiguration;
}
}
For further reading :
http://dotnetcoretutorials.com/2016/12/26/custom-configuration-sections-asp-net-core/
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration
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.
I have a base Document DB repository in the infrastructure layer of my solution. I based this repository on this GitHub project, which is a static class that is utilized by my other domain model repositories.
In my API layer I have config.json files that are environment specific. I would like to use dependency injection to be able to use my configuration class that reads the DocumentDB settings defined in the API layer in the deeper Infrastructure layer. This StackOverflow answer details how to use DI with an API controller, however I can't figure out how to use DI in this case, as a static class, I don't have a constructor. Is it possible to use DI with my static repository? If not, how should I read config settings into the infrastructure layer?
My ConfigurationOptions class (in Infrastructure layer):
public class ConfigurationOptions
{
public string EndpointUri { get; set; }
}
My DocumentDbRepository class (in Infrastructure layer):
public static class DocumentDbRepository<T>
{
// I want to read these strings from my config.json files
private static string EndpointUri { get; } = ConfigurationOptions.EndpointUri;
//...
private static Document GetDocument(string id)
{
return Client.CreateDocumentQuery(Collection.DocumentsLink)
.Where(d => d.Id == id)
.AsEnumerable()
.FirstOrDefault();
}
}
Part of my Startup class (in my API layer)
public class Startup
{
public IConfiguration Configuration { get; set; }
public void ConfigureServices(IServiceCollection services)
{
services.Configure<ConfigurationOptions>(options =>
options.EndpointUri = Configuration.Get("EndpointUri"));
// ...
}
// ...
}
I believe you are almost there.
The first step you have to take is almost done.
in your startup.cs, you have
services.Configure<ConfigurationOptions>(options =>
options.EndpointUri = Configuration.Get("EndpointUri"));
you can just call
services.Configure<ConfigurationOptions>(Configuration);
the services will map the EndpointUri attribute of your class. With this step 1 is done.
Now, following the post you linked, you can send your configurationOptions to the controller like:
public class SomeController
{
private string _endpointUrl;
public SomeController(IOptions<ConfigurationOptions> options)
{
_endpointUrl = options.Options.EndpointUri;
}
}
but, from what i assume, you want to have the EndpointUri in the DocumentDbRepository. You can do that in 2 ways:
1 --------------------------------------------------
You can create a constructor in your DocumentDbRepository to receive the EndpointUri and call it in your controller like such:
public class SomeController
{
private DocumentDbRepository _documentDbRepositoy
public SomeController(IOptions<ConfigurationOptions> options)
{
_documentDbRepositoy = new DocumentDbRepository (options.Options.EndpointUri);
}
}
2 ---------------------------------------------------
You can inject the DocumentDbRepository to all your controllers. For that i suggest that you create an interface IDocumentDbRepository and then configure it at startup making it a singleton or scoped or trasiend (for more info, see this link)
To do so, lets start with your DocumentDbRepository
public static class DocumentDbRepository<T> : IDocumentDbRepository<T>
{
private string EndpointUri { get; private set;}
public DocumentDbRepository(IOptions<ConfigurationOptions> options){
EndpointUri = options.Options.EndpointUri;
}
//...
}
then, at startup.cs you set it as singleton (in this example)
public void ConfigureServices(IServiceCollection services){
services.Configure<ConfigurationOptions(Configuration);
services.AddSingleton<IDocumentDbRepository, DocumentDbRepository>();
// ...
}
by doing this, if your controllers have a dependency on a IDocumentDbRepository, the singleton will be provided:
public class SomeController
{
private DocumentDbRepository _documentDbRepositoy
public SomeController(IDocumentDbRepository documentDbRepository)
{
_documentDbRepositoy = documentDbRepository
}
}