Application Settings in custom class ASP.Net 5 MVC 6 - c#

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.

Related

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>();
//
...
...
}

How to load appsettings.json inside a Static class based on deploy environment so that I can use in other class libraries

W'm working on a migration project. I need to use my appsettings in other class libraries. so after googling and stackoverflowing, I load my appsettings.json inside static class as follows:
public static class ReadAppConfig
{
private static readonly IConfiguration Root;
private static readonly ConfigurationBuilder ConfigurationBuilder;
static ReadAppConfig()
{
if (ConfigurationBuilder == null)
{
ConfigurationBuilder = new ConfigurationBuilder();
ConfigurationBuilder.SetBasePath(Directory.GetCurrentDirectory());
ConfigurationBuilder.AddJsonFile("appsettings.json", optional: true);
ConfigurationBuilder.AddJsonFile("appsettings.QA.json", optional: true);
ConfigurationBuilder.AddJsonFile("appsettings.Dev.json", optional: true);
ConfigurationBuilder.AddJsonFile("appsettings.Staging.json", optional: true);
if (Root == null)
Root = ConfigurationBuilder.Build();
}
}
public static string UserManualFile => Root.GetSection("AppSettings:SomeKey").Value;
}
So now I can get UserManualFile like ReadAppConfig.UserManualFile in other libraries.
This works fine. But it always reads from appsettings.Staging.json only. How to make this read based on deploy environment.
I cannot get IHostingEnvironment here as this is static class.
Please assist / suggest me with proper way to do this.
Thanks
There's two problems here. First, don't use a static class. Configuration is designed to be dependency injected and dependency injection is fundamentally incompatible with statics. In truth, statics are almost always the wrong approach, dependency injection or not. Second, libraries should depend only on abstractions, not concrete data/implementations.
Honestly, there's three problems and the last one is the killer here: you need IHostingEnvironment for your use case, and there's absolutely know way to get that in a static class. Game over.
There's multiple ways you could go here, but I'm going to be opinionated with what I feel is the best option. Ultimately, your libraries just need UserManualFile, it seems. As such, that is all they should depend on: a string that corresponds to the location of a user manual, presumably. So, you'll do something like:
public class SomeLibraryClass
{
private readonly string _userManualFie;
public SomeLibraryClass(string userManualFile)
{
_userManualFile = userManualFile;
}
}
This requires the least amount of knowledge and provides the greatest amount of abstraction for your library. It no longer cares where or how it gets the file location, just that it gets it.
Then, in your actual app, you'll use strongly-typed config to provide this value:
services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
...
services.AddScoped(p =>
{
var appSettings = p.GetRequiredService<IOptions<AppSettings>>();
return new SomeLibraryClass(appSettings.Value.UserManualFile);
});
Done. Now, if there's actually other stuff the library needs, you might choose to pass a custom "settings" class to the library. This class should come from the library, so that it documents what it needs. For example, in your library, you'd create a class like:
public class SomeLibrarySettings
{
public string Foo { get; set; }
public string Bar { get; set; }
// etc.
}
Then, your library class(es) would inject this:
public SomeLibraryClass(SomeLibrarySettings settings)
Finally, in your app, you can either manually compose this settings class instance or inject it. Injecting it will still require you to manually compose it, so it only makes sense to do it that way if you're going to share it between multiple classes.
Manually compose
services.AddScoped(p =>
{
var appSettings = p.GetRequiredService<IOptions<AppSettings>>();
var someLibrarySettings = new SomeLibrarySettings
{
Foo = appSettings.Value.Foo,
Bar = appSettings.Value.Bar,
// etc.
};
return SomeLibraryClass(someLibrarySettings);
});
Inject
services.AddSingleton(p =>
{
var appSettings = p.GetRequiredService<IOptions<AppSettings>>();
return new SomeLibrarySettings
{
Foo = appSettings.Value.Foo,
Bar = appSettings.Value.Bar,
// etc.
};
});
services.AddScoped<SomeLibraryClass1>();
services.AddScoped<SomeLibraryClass2>();
// etc.
Because SomeLibrarySettings is registered in the service collection, it will be automatically injected into the library classes that depend on it.
Finally, it's worth noting that because you're moving the configuration logic to where it actually belongs, you no longer need to even worry about the environment. ASP.NET Core is already set up to load the appropriate environment settings, so it just works.

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)

C# ASP.Net 5 configuration and backwards compatibility with Class Libraries

I've got multiple legacy libraries which configure themselves via the ConfigurationManager. Example:
var port = ConfigurationManager.AppSettings["service.port"];
The new ASP.Net system prefers strongly typed models based on an additional "config.json" file. Example (taken from Rick Strahl's Web Log):
//from AppSettings.cs
public class AppSettings
{
public string SiteTitle { get; set; }
}
//from config.json
{
"AppSettings": {
"SiteTitle": "WebApplication2",
},
"Data": {
"DefaultConnection": {
"ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=blahfoo;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
}
// from Startup.cs
public class Startup
{
public IConfiguration Configuration { get; set; }
public Startup(IHostingEnvironment env)
{
// Setup configuration sources.
var configuration = new Configuration()
.AddJsonFile("config.json")
.AddJsonFile($"config.{env.EnvironmentName}.json", optional: true);
configuration.AddEnvironmentVariables();
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
// Add Application settings to the services container.
services.Configure<AppSettings>(Configuration.GetSubKey("AppSettings"));
…
}
}
My question: Is there a way to embrace the new ASP.Net 5 and it's strongly typed configuration methodology while maintaining backwards compatibility with my other application libraries?
Or rather, can I utilize common libraries from our portfolio without having to rewrite them?
Your problem is that you relied on a concrete implementation of configuration and used the ConfigurationManager's static members from your classes instead of writing a SOLID implementation with proper dependency injection.
You could find some hacky tricks where you don't have to change your code to make use of the new configuration model, but I reckon you should do yourself a favour and use this as an opportunity to actually re-factor your code and abstract your current configurations behind one simple interface like e.g.:
public interface IMyAppConfiguration
{
string Setting1 { get; }
string Setting2 { get; }
SomeOtherMoreComplexSetting Setting3 { get; }
}
Then inject this dependency in every class where you require one of the settings and provide one implementation which wraps the current ConfigurationManager class and another implementation which wraps the new configuration model.
This is a perfect example why SOLID design is important and makes code maintenance and innovation easier when done right.

Getting a Configuration Value in ASP.NET 5 (vNext)

I am struggling with some concepts in ASP.NET 5 (vNext).
One of those is the Dependency Injection approach used for configuration. It seems like I have to pass a parameter all the way through the stack. I'm probably misunderstanding something or doing it wrong.
Imagine I have a config property named "contactEmailAddress". I'll use that config property to send an email when a new order is placed. With that scenario in mind, my ASP.NET 5 stack will look like this:
Startup.cs
public class Startup
{
public IConfiguration Configuration { get; set; }
public Startup(IHostingEnvironment environment)
{
var configuration = new Configuration().AddJsonFile("config.json");
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
services.Configure<AppSettings>(Configuration.GetSubKey("AppSettings"));
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.UseWelcomePage();
}
AppSettings.cs
public class AppSettings
{
public string ContactEmailAddress { get; set; }
}
config.json
{
"AppSettings": {
"ContactEmailAddress":"support#mycompany.com"
}
}
OrderController.cs
[Route("orders")]
public class OrdersController : Controller
{
private IOptions<AppSettings> AppSettings { get; set; }
public OrdersController(IOptions<AppSettings> appSettings)
{
AppSettings = appSettings;
}
[HttpGet("new-order")]
public IActionResult OrderCreate()
{
var viewModel = new OrderViewModel();
return View(viewModel);
}
[HttpPost("new-order")]
public IActionResult OrderCreate(OrderViewModel viewModel)
{
return new HttpStatusCodeResult(200);
}
}
Order.cs
public class Order()
{
public void Save(IOptions<AppSettings> appSettings)
{
// Send email to address in appSettings
}
public static List<Order> FindAll(IOptions<AppSettings> appSettings)
{
// Send report email to address in appSettings
return new List<Order>();
}
}
As the example above shows, I'm passing AppSettings through the entire stack. This does not feel correct. To further my worries, this approach will not work if I'm attempt to use a third-party library that needs to access configuration settings. How can a third-party library access configuration settings? Am I misunderstanding something? Is there a better way to do this?
You are entangling 2 different run time resource provider, AppSettings and Dependency Injection.
AppSettings, provides run-time access to Application specific values like UICulture strings, Contact Email, etc.
DI Containers are factories that Manage access to Services and their lifetime scopes. For example, If a MVC Controller needed access to your EmailService, you would configure
public void ConfigureServices(IServiceCollection services)
{
// Add all dependencies needed by Mvc.
services.AddMvc();
// Add EmailService to the collection. When an instance is needed,
// the framework injects this instance to the objects that needs it
services.AddSingleton<IEmailService, EmailService>();
}
Then, if our Home Controller needs access to your EmailService, we add a dependency on it's Interface by adding it as a parameter to the Controller constructor
public class HomeController : Controller
{
private readonly IEmailService _emailService;
private readonly string _emailContact;
/// The framework will inject an instance of an IEmailService implementation.
public HomeController(IEmailService emailService)
{
_emailService = emailService;
_emailContact = System.Configuration.ConfigurationManager.
AppSettings.Get("ContactEmail");
}
[HttpPost]
public void EmailSupport([FromBody] string message)
{
if (!ModelState.IsValid)
{
Context.Response.StatusCode = 400;
}
else
{
_emailService.Send(_emailContact, message);
The purpose of Dependancy Injection is to manage access and lifetimes of services.
In the previous example, in our Application Startup, we configured the DI Factory to associate application requests for IEmailService with EmailService. So when our Controllers are instantiate by the MVC Framework, the framework notices that our Home Controller expects IEmailService, the framework checks our Application Services Collection. It finds mapping instructions and Inject a Singleton EmailService (a descendant of the occupying Interface) into our Home Controller.
Super Polymorphic Factorific - alodocious!
Why is this important?
If your contact email changes, you change the AppSetting value and are done. All requests for "ContactEmail" from ConfigurationManager are Globally changed. Strings are easy. No need for Injection when we can just hash.
If your Repository, Email Service, Logging Service, etc changes, you want a Global way to change all references to this service. Service reference aren't as easily transferred as immutable string literals. Service instantiation should be handled by a factory to configure the Service's settings and dependencies.
So, in a year you develop a RobustMailService:
Class RobustMailService : IEmailService
{
....
}
As long as your new RobustMailService inherits and implements the IEmailService Interface, you can substitute all references to your mail service Globally by changing :
public void ConfigureServices(IServiceCollection services)
{
// Add all dependencies needed by Mvc.
services.AddMvc();
// Add RobustMailService to the collection. When an instance is needed,
// the framework injects this instance to the objects that needs it
services.AddSingleton<IEmailService, RobustMailService>();
}
This can be achieved using IOptions assessor service as it seems you were trying.
We can begin by creating a class with all of the variables that your controller needs from configuration.
public class VariablesNeeded
{
public string Foo1{ get; set; }
public int Foo2{ get; set; }
}
public class OtherVariablesNeeded
{
public string Foo1{ get; set; }
public int Foo2{ get; set; }
}
We now need to tell the middleware that the controller needs this class in the constructor of the controller using dependency injection, we do this using IOptions accessor service.
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
public class MyController: Controller{
private readonly VariablesNeeded _variablesNeeded;
public MyController(IOptions<VariablesNeeded> variablesNeeded) {
_variablesNeeded= variablesNeeded.Value;
}
public ActionResult TestVariables() {
return Content(_variablesNeeded.Foo1 + _variablesNeeded.Foo2);
}
}
To get the variables from your configuration files, we create a constructor for the startup class, and a configuration property.
public IConfigurationRoot Configuration { get; }
public Startup(IHostingEnvironment env)
{
/* This is the fairly standard procedure now for configuration builders which will pull from appsettings (potentially with an environmental suffix), and environment variables. */
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
Now we need to make sure the pipeline actually supplies the controller with this service.
In your ConfigureServices method in your Startup class, you want to use the Options middleware, and inject an object of type VariablesNeeded in to the pipeline.
public void ConfigureServices(IServiceCollection services)
{
// Tells the pipeline we want to use IOption Assessor Services
services.AddOptions();
// Injects the object VariablesNeeded in to the pipeline with our desired variables
services.Configure<VariablesNeeded>(x =>
{
x.Foo1 = Configuration["KeyInAppSettings"]
x.Foo2 = Convert.ToInt32(Configuration["KeyParentName:KeyInAppSettings"])
});
//You may want another set of options for another controller, or perhaps to pass both to our "MyController" if so, you just add it to the pipeline
services.Configure<OtherVariablesNeeded>(x =>
{
x.Foo1 = "Other Test String",
x.Foo2 = 2
});
//The rest of your configure services...
}
For more information see the chapter on Using Options and configuration objects in the ASPCore Docs

Categories

Resources