How to read appsettings.json values ASP.NET Core - asp.net

After I've read this article about dependency injection Here I still do not have a clear understanding on how to read the appsetting in other than a controller classes.
Lets say for instance I have a helper class with a bunch of static methods that I'm planning to use, I do not create an instance of this class, how do I read setting values to use inside the methods of this class?

I used to create helper class to read data from appsettings.config in one of my applications:
public static class ConfigValueProvider
{
private static readonly IConfigurationRoot Configuration;
static ConfigValueProvider()
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
Configuration = builder.Build();
}
public static string Get(string name)
{
return Configuration[name];
}
}
However later I reviewed my application to get away from static methods which depends on application config in order to make my application testable.

You should use services.Configure as below:
public class Startup
{
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.Configure<JSonAsClass>(Configuration.GetSection("MySectionName"));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
then you can inject JSonAsClass inside any class you want to use it:
private JSonAsClass jSonAsClass;
public MailService(IOptions<JSonAsClass> jSonAsClass)
{
this.jSonAsClass = jSonAsClass.Value;
}

Related

Modify a FeatureManager value in a SpecFlow test

I have a Controller where a FeatureManager is injected and the code branches depending on the values read from _featureManager.IsEnabledAsync("NameOfFeature"). I am using SpecFlow and want to create a Given attribute that modifies a particular FeatureManager feature so I can test with the feature enabled and disabled.
At the same time, I would like to control some of the features from the environment (e.g. set them in appsettings.json) because they will change depending on how the tests are executed (locally or in docker in a CI-pipeleine). This is an ASP.NET Core application set up with dependency injection.
The appsettings.json file contains features in section "FeatureManagement". The features that are used by the application projects can be mocked, they don't have to be read from this file, but some features are to be used by the SpecFlow project and have to be taken from this file.
Question
How can I control what the IFeatureManager returns in the tests while also reading some other features from appsettings.json? It is acceptable to mock the FeatureManager, as well as defining a custom one.
Code
Hooks.cs (SpecFlow project)
[Binding]
public sealed class Hooks
{
private static IHost _host;
[BeforeTestRun]
public static void BeforeTestRun()
{
// Program is the app being tested. It contains a Startup class with
// services.AddFeatureManagement(configuration.GetSection("FeatureManagement"));
var hostBuilder = Program.CreateHostBuilder(Array.Empty<string>());
hostBuilder.ConfigureServices((hostBuilderContext, services) => {
// Test services that we wish to inject in the app are added here
});
_host = hostBuilder.Build();
_host.Start();
}
[ScenarioDependencies]
public static IServiceCollection CreateServices()
{
var services = new ServiceCollection();
// Services that are injected in the SpecFlow step definitions are added here
services.AddFeatureManagement(configuration.GetSection("FeatureManagement"));
return services;
}
}
StepDefinitions.cs (SpecFlow project)
[Given(#"the feature '([^']*)' is enabled")]
public void GivenTheFeatureIsEnabled(string feature)
{
// Set up the FeatureManager to return true for the feature
}
// And similar for when the feature is disabled
Startup.cs (Application) Simplified for brevity
public class Startup
{
private readonly IConfiguration _configuration;
public Startup(IConfiguration configuration)
{
_configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
services.AddFeatureManagement(_configuration.GetSection("FeatureManagement"));
// Other services added...
}
}

Can't figure out how to inject LinkGenerator

So I know how to setup my controller so that I can accept a LinkGenerator injected into the controller. What I can't figure out is how do I inject my controller at startup with a LinkGenerator.
Controller
protected readonly LinkGenerator _linkGenerator;
public SomeController(config config, LinkGenerator linkGenerator)
{
config = Config;
_linkGenerator = linkGenerator;
}
StartUp - ConfigureServices
Controllers.SomeController someController = new
Controllers.SomeController(config, linkGenerator); //how do I get an
instance of link generator here.
services.AddSingleton(someController);
I tried this in the Configure method of startup, but ConfigureServices runs before Configure
app.Use(async (context, next) =>
{
linkGenerator = context.RequestServices.GetService<LinkGenerator>();
});
What am I missing?
Try the following approach in ConfigureServices of Startup.cs
public Startup(IConfiguration configuration , IHttpContextAccessor accessor)
{
Configuration = configuration;
_accessor = accessor;
}
public readonly IHttpContextAccessor _accessor;
public IConfiguration Configuration { get; }
var linkGenerator = _accessor.HttpContext.RequestServices.GetService<LinkGenerator>();
services.AddScoped<LinkGenerator>();
services.AddTransient(ctx =>
new ValuesController(linkGenerator));
Controller
private readonly LinkGenerator _linkGenerator;
public ValuesController(LinkGenerator linkGenerator)
{
_linkGenerator = linkGenerator;
}
Reference :https://andrewlock.net/controller-activation-and-dependency-injection-in-asp-net-core-mvc/
I found this article: https://kontext.tech/article/974/generate-absolute-path-using-linkgenerator-in-aspnet-core.
It states:
To use this class in your code, we need to inject it. LinkGenerator is
a singleton service available from DI. It is registered automatically
when you call services.AddRouting();. All the methods also need to
access HttpContext instance for each request, which can be accessed by
injecting HttpContextAccessor.
services.AddHttpContextAccessor();

.NET CORE 2.1 Constructor asks for IConfiguration

I've followed several methods on StackOverflow to fix my issue, none with a result:
My DefaultConnection-string is in my AppSettings.json. To retrieve info I am reading to use the IConfiguration from my startup.cs. The constructor of my MsSQL-context is still asking for this IConfiguration. Note: I'm using a repository pattern.
startup.cs:
public Startup(IConfiguration configuration)
{
this.Configuration = configuration;
}
public IConfiguration Configuration { get; private set; }
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IConfiguration>(Configuration);
I've added the Singleton in my startup after a suggestion. With or without this the constructor of MsSQLContext is still requesting this as a variable to be passed. Leaving the constructor without this gives me the error: Connectionstring not initialized.
AdminMsSQLContext:
private readonly string _connectionString;
public MSSQLAdminContext(IConfiguration configuration)
{
_connectionString = configuration.GetConnectionString("DefaultConnection");
}
Injecting IConfiguration is actually an anti-pattern, anyways. What you should be doing is supplying an action to your scope registration and change your MSSQLAdminContext class to accept just the connection string in its constructor:
public MSSQLAdminContext(string connectionString)
{
_connectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
}
Then:
services.AddScoped(_ =>
new MSSQLAdminContext(Configuration.GetConnectionString("DefaultConnection)));
Your repo should not have knowledge of something like your configuration. If it needs a connection string, then it should take the connection string, and that is all.
I believe the issue you are having is that you have not registered the MSSQLAdminContext with the DI container. Because of this the DI engine does not know to inject the IConfiguration into the class. In your start up you will register this class however you need, I tend to use scoped for these types of classes you may use in multiple places. So something like this.
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<MSSQLAdminContext>();
services.AddSingleton<IConfiguration>(Configuration);
}

ASP.NET 5 DI app setting outside controller

I can DI app setting in the controller like this
private IOptions<AppSettings> appSettings;
public CompanyInfoController(IOptions<AppSettings> appSettings)
{
this.appSettings = appSettings;
}
But how to DI that in my custom class like this
private IOptions<AppSettings> appSettings;
public PermissionFactory(IOptions<AppSettings> appSetting)
{
this.appSettings = appSettings;
}
my register in Startup.cs is
services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
The "proper" way
Register your custom class in the DI, the same way you register other dependencies in ConfigureServices method, for example:
services.AddTransient<PermissionFactory>();
(Instead of AddTransient, you can use AddScoped, or any other lifetime that you need)
Then add this dependency to the constructor of your controller:
public CompanyInfoController(IOptions<AppSettings> appSettings, PermissionFactory permFact)
Now, DI knows about PermissionFactory, can instantiate it and will inject it into your controller.
If you want to use PermissionFactory in Configure method, just add it to it's parameter list:
Configure(IApplicationBuilder app, PermissionFactory prov)
Aspnet will do it's magic and inject the class there.
The "nasty" way
If you want to instantiate PermissionFactory somewhere deep in your code, you can also do it in a little nasty way - store reference to IServiceProvider in Startup class:
internal static IServiceProvider ServiceProvider { get;set; }
Configure(IApplicationBuilder app, IServiceProvider prov) {
ServiceProvider = prov;
...
}
Now you can access it like this:
var factory = Startup.ServiceProvider.GetService<PermissionFactory>();
Again, DI will take care of injecting IOptions<AppSettings> into PermissionFactory.
Asp.Net 5 Docs in Dependency Injection
I recommend not passing AppSettings. A class shouldn't depend on something vague - it should depend on exactly what it needs, or close to it. ASP.NET Core makes it easier to move away from the old pattern of depending on AppSettings. If your class depends on AppSettings then you can't really see from the constructor what it depends on. It could depend on any key. If it depends on a more specific interface then its dependency is clearer, more explicit, and you can mock that interface when unit testing.
You can create an interface with the specific settings that your class needs (or something less specific but not too broad) and a class that implements it - for example,
public interface IFooSettings
{
string Name { get; }
IEnumerable Foos { get; }
}
public interface IFoo
{
string Color { get; }
double BarUnits { get; }
}
public class FooSettings : IFooSettings
{
public string Name { get; set; }
public List<Foo> FooList { get; set; }
public IEnumerable Foos
{
get
{
if (FooList == null) FooList = new List<Foo>();
return FooList.Cast<IFoo>();
}
}
}
public class Foo : IFoo
{
public string Color { get; set; }
public double BarUnits { get; set; }
}
Then add a .json file, fooSettings.json:
{
"FooSettings": {
"Name": "MyFooSettings",
"FooList": [
{
"Color": "Red",
"BarUnits": "1.5"
}, {
"Color": "Blue",
"BarUnits": "3.14159'"
}, {
"Color": "Green",
"BarUnits": "-0.99999"
}
]
}
}
Then, in Startup() (in Startup.cs) where we specify what goes into our Configuration, add fooSettings.json:
var builder = new ConfigurationBuilder(appEnv.ApplicationBasePath)
.AddJsonFile("config.json")
.AddJsonFile($"config.{env.EnvironmentName}.json", optional: true)
.AddJsonFile("fooSettings.json");
Finally, in ConfigureServices() (also in Startup.cs) tell it to load an instance of FooSettings, cast it as IFooSettings (so the properties appear read-only) and supply that single instance for all dependencies on IFooSettings:
var fooSettings = (IFooSettings)ConfigurationBinder.Bind<FooSettings>(
Configuration.GetConfigurationSection("FooSettings"));
services.AddInstance(typeof (IFooSettings), fooSettings);
Now your class - controller, filter, or anything else created by the DI container - can have a dependency on IFooSettings and it will be supplied from the .json file. But you can mock IFooSettings for unit testing.
Original blog post - it's mine so I'm not plagiarizing.
You can do dependency injection in your non-controller classes as well.
In your startup class,
public class Startup
{
public IConfigurationRoot Configuration { get; set; }
public Startup(IHostingEnvironment env)
{
// Set up configuration sources.
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public void ConfigureServices(IServiceCollection services)
{
// register other dependencies also here
services.AddInstance<IConfiguration>(Configuration);
}
}
Now in your custom class, Have the constructor accept an implementation of IConfiguration
private IConfiguration configuration;
public PermissionFactory(IConfiguration configuration)
{
this.configuration = configuration;
}
public void SomeMethod()
{
var someSection = this.configuration.GetSection("SomeSection");
var someValue= this.configuration.Get<string>("YourItem:SubItem");
}
If you want to DI to action filter reference to Action filters, service filters and type filters in ASP.NET 5 and MVC 6 service filter part.

ASP.NET 5 - Using a Configuration Setting

I am playing with ASP.NET 5. I am trying to understand the new configuration model. I have read several articles. However, I am still unsuccessful in loading a configuration setting. My config.json file looks like this:
{
"App" : {
"Info" : {
"Version":"1.0.0",
"ReleaseDate":"03-15-2015"
}
}
}
My Startup.cs file looks like this:
public class Startup
{
public IConfiguration Configuration { get; private set; }
public Startup()
{
Configuration = new Configuration()
.AddJsonFile("config.json");
}
public void ConfigureServices(IServiceCollection services)
{
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.UseMvc();
app.UseWelcomePage();
}
}
In one of my controllers, I have the following
MyController.cs
using System;
using Microsoft.AspNet.Mvc;
namespace MyOrg.MyApp
{
public class MyController : Controller
{
[HttpGet()]
public ActionResult Index()
{
var version = Configuration.Get("App:Info:Version");
return new HttpStatusCodeResult(200);
}
}
}
When I start the app, I get an error that says:
error CS0103: The name 'Configuration' does not exist in the current context
at Microsoft.Framework.Runtime.Roslyn.RoslynProjectReference.Load(IAssemblyLo
adContext loadContext)
at Microsoft.Framework.Runtime.Loader.ProjectAssemblyLoader.Load(String name,
IAssemblyLoadContext loadContext)
at Microsoft.Framework.Runtime.Loader.ProjectAssemblyLoader.Load(String name)
at kre.host.LoaderContainer.Load(String name)
at kre.hosting.RuntimeBootstrapper.<>c__DisplayClass6_0.<ExecuteAsync>b__4(As
semblyName assemblyName)
at kre.hosting.RuntimeBootstrapper.<>c__DisplayClass6_0.<ExecuteAsync>b__7(Ob
ject sender, ResolveEventArgs a)
at System.AppDomain.OnAssemblyResolveEvent(RuntimeAssembly assembly, String assemblyFullName)
What am I doing wrong? I feel like I've followed the examples I've seen. Yet, I can figure out what I'm doing wrong.
Clearly you want to access Configuration property in your Startup class. And the error method says it doesn't know what Configuration is. So you need a using statement or a fully qualified name. Also, you should avoid naming things the same thing as stuff found in the framework. Your Startup class has a Configuration property, but it also tries to use the Configuration class from Microsoft.Framework.ConfigurationModel. How confusing is that?
Your Configure() method in Startup needs a using statement or fully qualified name so it knows what the Configuration class is.
using Microsoft.Framework.ConfigurationModel; //at the top of your class
Configuration = new Configuration(); //later in the code, we can access without fully qualifying name
or
Configuration = new Microsoft.Framework.ConfigurationModel.Configuration();
In your controller, you may have a similar issue. Replace MyOrg.MyApp.Startup in the example below with whatever the namespace is for your Startup class.
using MyOrg.MyApp.Startup //at the top of your class
Startup.Configuration.Get("App:Info:Version"); //later in the code, we can access without fully qualifying name
or
MyOrg.MyApp.Startup.Startup.Configuration.Get("App:Info:Version");
Better way of doing things
That should be enough to get you started. However, accessing the Startup class to retrieve your configuration isn't ideal, because now your controller's action methods depend on having the Startup class there. That's not very unit testable. Ideally your controllers should be isolated from each other. You should define some sort of interface to hold the configuration info you want, then have the controller depend on that interface. When you're in your site, you'll respond with a class specific to the site's configuration. When unit testing, you can have tight control over the test values by using a different class.
interface ISiteConfig
{
string Version {get; set;}
DateTime ReleaseDate {get; set;}
}
public class SiteConfig : ISiteConfig
{
public string Version {get; set;}
public DateTime ReleaseDate {get; set;}
public SiteConfig()
{
var c = new Configuration()
.AddJsonFile("config.json");
Version = c.Get("App:Info:Version");
ReleaseDate = c.Get("App:Info:ReleaseDate"); //may need to parse here
}
}
public class TestConfig : ISiteConfig
{
public string Version {get; set;}
public DateTime ReleaseDate {get; set;}
public TestConfig(string version, DateTime releaseDate)
{
Version = version;
ReleaseDate = releaseDate;
}
}
Then you'd use Dependency Injection to inject instances of your configuration into the Controller.
public class MyController : Controller
{
private readonly ISiteConfig Config;
public MyController(ISiteConfig config)
{
Config = config;
}
[HttpGet()]
public HttpStatusCodeResult Index()
{
var version = Config.Version;
return new HttpStatusCodeResult(200);
}
}
public class Startup
{
public void Configure(IBuilder app)
{
...
app.UseServices(services =>
{
...
// Set up the dependencies
services.AddTransient<ISiteConfig, SiteConfig>();
...
});
...
}
}
Now you can more easily unit test your action methods, because your unit tests can use the TestConfig class while the site can use the SiteConfig class. And also if you want to change how your configuration is done, you don't have to replace strings in a bunch of different places. You'll have one class where you do so, the rest is strongly typed and easy to change without blowing up your application.
Your unit test might look like this:
//Arrange
var testConfig = new TestConfig("1.0", DateTime.Now );
var controller = new MyController(testConfig );
//Act
var response = controller.Index();
//Assert
Assert.AreEqual(200, response.StatusCode);
As of Beta 5 the accepted answer is no longer correct. There is no longer a Get method on IConfiguration. Also the way of constructing the configuration object is also changed.
The following code works on Beta 7:
// showing using statements here since this is new from Beta 5
using Microsoft.Dnx.Runtime; // renamed was Microsoft.Framework.Runtime
using Microsoft.Framework.Configuration; // renamed was Microsoft.Framework.ConfigurationModel
// other using statements here
// Startup constructor. Note: now takes IApplicationEnvironment
// this is required in order to get base path
public Startup(IHostingEnvironment env, IApplicationEnvironment appEnv)
{
// Setup configuration sources.
var builder = new ConfigurationBuilder(appEnv.ApplicationBasePath)
.AddJsonFile("config.json")
.AddJsonFile("dbconfig.json")
.AddEnvironmentVariables();
Configuration = builder.Build();
}
// property to hold configuration object created in constructor
public IConfiguration Configuration { get; set; }
public void ConfigureServices(IServiceCollection services)
{
// this will bind to an IOptions<AppSettings> instance
// where AppSettings is a class you define that has a set of
// properties that match your configuration section loaded from the
// json file
services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
// here I am loading a connection string from a json file and passing into an
// new EF 6.x DB Context class
services.AddInstance<TalentAgencyContainer>(new TalentAgencyContainer(Configuration["ConnectionStrings:TalentAgencyContainer"]));
// Add MVC services to the services container.
services.AddMvc();
}

Resources