I have a website that takes input from emails that go out to users (an email is sent to everyone, they click a link which calls a controller action). I want to use a webjob to send the emails out, but I need to cycle through all the users in the database to grab the email addresses.
I set up the website and everything is working great. I have the DbContext inheriting from IdentityDbContext here:
public class MooodDbContext : IdentityDbContext<ApplicationUser>
{
public MooodDbContext(DbContextOptions options) : base(options)
{
}
public DbSet<Response> Response { get; set; }
public DbSet<Cow> Cow { get; set; }
public DbSet<Herd> Herd { get; set; }
}
The ConfigureServices is run when I launch the webapp
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddDbContext<MooodDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("MooodConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<MooodDbContext>()
.AddDefaultTokenProviders();
services.AddMvc(options => options.Filters.Add(typeof(UnauthorizedExceptionFilterAttribute)));
services.AddScoped<IDbInitializer, DbInitializer>();
// Add application services.
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
}
but obviously not when I run the webjob. So, when I try to access anything in the context, I get this exception (after setting context = new context()):
System.InvalidOperationException occurred
HResult=0x80131509
Message=No database provider has been configured for this DbContext. A provider can be configured by overriding the DbContext.OnConfiguring method or by using AddDbContext on the application service provider. If AddDbContext is used, then also ensure that your DbContext type accepts a DbContextOptions<TContext> object in its constructor and passes it to the base constructor for DbContext.
Source=Microsoft.EntityFrameworkCore
It seems to me, it's because I'm not doing any configuration on the DbContext, but I'm not sure. Is there a better way to accomplish this? Or am I missing something small?
Edit: This is the program.cs of my webjob (minus the emailing functionality, which is cluttery):
private static MooodDbContext _context;
// Please set the following connection strings in app.config for this WebJob to run:
// AzureWebJobsDashboard and AzureWebJobsStorage
private static void Main()
{
_context = new MooodDbContext(new DbContextOptions<MooodDbContext>());
var host = new JobHost();
EmailAllHerds();
host.RunAndBlock();
EmailAllHerds();
}
Couple of assumptions that i made here
web job is a console App built on .net core
So you will have Program.cs file with a main method. So in your
Program.cs
class Program {
// declare a field to store config values
private readonly IConfigurationRoot _configuration;
// Declare a constructor
public Program()
{
var builder = new ConfigurationBuilder()
.SetBasePath(environment.BasePath)
.AddJsonFile($"appsettings.json", true, true)
.AddJsonFile($"appsettings.{environment.EnvironmentName}.json", true, true)
.AddEnvironmentVariables();
_configuration = builder.Build();
}
}
public static void Main(string[] args)
{
var program = new Program();
var serviceCollection = new ServiceCollection();
program.ConfigureServices(serviceCollection);
}
private void ConfigureServices(IServiceCollection services)
{
// now here you can resolve your DbContext similar to web
services.AddDbContext<MooodDbContext>(options =>options.UseSqlServer(_configuration.GetConnectionString("MooodConnection")));
}
}
Related
This question is specific to NET Maui in my case.
My Settings file:
{
"MyConfig": {
"FactoryConfig1": {
"SomeType1": "SomeConfig1"
},
"FactoryConfig2": {
"SomeType2": "SomeConfig2"
}
}
}
Let's say I'm creating my services like this:
var builder = MauiApp.CreateBuilder();
builder.Services.AddSingleton<IService1, Service1>();
builder.Services.AddSingleton<IService2, Service2>();
builder.Services.AddTransient<Service3>();
// and config file-- how to modify this to use IOptions to use with IOptions Monitor?
var a = Assembly.GetExecutingAssembly();
using var stream = a.GetManifestResourceStream("MauiBlazorTestApp.appsettings.json");
var config = new ConfigurationBuilder()
.AddJsonStream(stream)
.Build();
builder.Configuration.AddConfiguration(config);
return builder.Build();
then in another class I get the config:
public class Service1 : IService1
{
IConfiguration _configuration;
public Service1(IConfiguration configuration)
{
_configuration = configuration;
}
public void SomeMethod()
{
var myConfig = _configuration.GetRequiredSection("MyConfig").Get<MyConfig>();
}
}
In each of the services, I'm injecting an IConfiguration with the configuration parameters used in each service from an appsettings.json file.
The code above works the way it is but what if sometime after starting the app, and after the services have been already created with the existing settings, the user changes some settings in appsettings.json from a UI dashboard page, how can I reload all those services with the new config settings for each...
I have to clear the services, and re-add them, is this possible, in a class outside program.cs?
It would work if I restart the application, but can it be done without, just a refresh of the DI?
As #Nkosi mentioned in the comments you should always use option pattern and not directly access the IConfiguration object.
In your case just use a class to store the desired values from configuration:
public class MyConfig
{
// The class to bind IConfiguration to
public string YourSetting { get; set; }
}
add options to the service collection and configure them. Also note the reloadOnChange parameter used during setup which indicates whether the configuration is reloaded if the file changes.
//...
builder.Configuration
.AddJsonFile("MauiBlazorTestApp.appsettings.json", optional: false, reloadOnChange: true);
// Add and configure your options
builder.Services.AddOptions();
builder.Services.Configure<MyConfig>(builder.Configuration.GetRequiredSection("MyConfig"));
//...
and finally inject the options into the target services and access the settings as needed.
public class Service1 : IService1
{
private readonly IOptionsMonitor<MyConfig> _options;
public Service1(IOptionsMonitor<MyConfig> options)
{
_options = options;
}
public void SomeMethod()
{
// Read the current value provided via IConfiguration
MyConfig appSettings = _options.CurrentValue;
var value = appSettings.YourSetting;
//...use setting as needed
}
}
When ever the current setting is requested, it will pull the latest version from settings, even if the service is a singeton.
For more information you can find this pattern very well documented here and especially for the IOptionsMonitor here
I have a .Net 5 Web Api project and want to configure options with some logic. That's why I would like to move the configuration out from the Startup.ConfigureServices method and put them into their own service configurator (running during startup). So I came up with this
class OptionsConfigurator : IHostedService
{
private readonly IServiceCollection _serviceCollection;
public StartupOptionsConfigurator(IServiceCollection serviceCollection)
{
_serviceCollection = serviceCollection;
}
public Task StartAsync(CancellationToken cancellationToken)
{
/*
Build the ServiceProvider from the _serviceCollection
Get the IConfiguration service from the ServiceProvider
Read the configurationSection from the IConfiguration
call _serviceCollection.Configure<MyOptions>(configurationSection);
*/
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}
This won't work because I can't inject IServiceCollection to the constructor. Is there a way to configure those options without the need of an instance of IServiceCollection?
I can provide another example showing the problem:
Your Api provides a single endpoint to update a specific configuration to change it during runtime (maybe change the database connection string for some reasons).
Create a new Web Api project
Create an options class
.
public class MyOptions
{
public string Text { get; set; }
}
Create a controller
.
[ApiController]
[Route("[controller]")]
public class MyController : ControllerBase
{
private readonly IOptionsMonitor<MyOptions> _myOptionsMonitor;
private readonly IServiceCollection _serviceCollection;
public MyController(IOptionsMonitor<MyOptions> myOptionsMonitor, IServiceCollection serviceCollection)
{
_myOptionsMonitor = myOptionsMonitor;
_serviceCollection = serviceCollection;
}
[HttpPatch]
public IActionResult UpdateOptions([FromBody] string text)
{
Console.WriteLine("Before: " + JsonSerializer.Serialize(_myOptionsMonitor.CurrentValue));
_serviceCollection.Configure<MyOptions>(x => { x.Text = text; });
string newConfig = JsonSerializer.Serialize(_myOptionsMonitor.CurrentValue);
Console.WriteLine("After: " + newConfig);
return Ok(newConfig);
}
}
Call the patch endpoint with a new configuration string in the request body. As expected this doesn't work neither, the injection of IServiceCollection is not possible and throws an exception.
Are there any solutions on how to configure options later on?
Reconfiguring options using IServiceCollection.Configure is not possible after initialization because BuildServiceProvider has already been called. You cannot configure or add any service outside program initialiazation.
To change configuration values you just need to update appsettings.json file in UpdateOptions action and to use IOptionsMonitor everywhere.
When configuring configuration you can specify to reload the configuration file when it changes. In Program.CreateHostBuilder:
public static IHostBuilder CreateHostBuilder(string[] args)
{
return Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.ConfigureAppConfiguration((hostingConext, configurationBuilder) =>
{
configurationBuilder.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
configurationBuilder.AddJsonFile($"appsettings.{hostingConext.HostingEnvironment.EnvironmentName}.json", optional: false, reloadOnChange: true);
configurationBuilder.AddUserSecrets(Assembly.GetExecutingAssembly(), true, true);
});
}
In this way IOptionsMonitor<TOptions>.CurrentValue will always return the value stored in the configuration file even if it changes.
It sounds like you should implement your own ConfigurationProvider. This would allow you to react to changes in your configuration and then you use IOptionsMonitor (for singleton services) or IOptionsSnapshot (for transient or scoped services) to read the values.
There is a sample implementation for a database based configuration provider over at learn.microsoft.com. That would be fairly easy to change to something where you store the values in memory (or wherever you want if you want to persist changes) and then you could expose this to your controller.
I'm following this tutorial
Integration Testing with Entity Framework Core and SQL Server
My code looks like this
Integration Test Class
public class ControllerRequestsShould : IDisposable
{
private readonly TestServer _server;
private readonly HttpClient _client;
private readonly YourContext _context;
public ControllerRequestsShould()
{
// Arrange
var serviceProvider = new ServiceCollection()
.AddEntityFrameworkSqlServer()
.BuildServiceProvider();
var builder = new DbContextOptionsBuilder<YourContext>();
builder.UseSqlServer($"Server=(localdb)\\mssqllocaldb;Database=your_db_{Guid.NewGuid()};Trusted_Connection=True;MultipleActiveResultSets=true")
.UseInternalServiceProvider(serviceProvider);
_context = new YourContext(builder.Options);
_context.Database.Migrate();
_server = new TestServer(new WebHostBuilder()
.UseStartup<Startup>()
.UseEnvironment(Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")));
_client = _server.CreateClient();
}
[Fact]
public async Task ReturnListOfObjectDtos()
{
// Arrange database data
_context.ObjectDbSet.Add(new ObjectEntity{ Id = 1, Code = "PTF0001", Name = "Portfolio One" });
_context.ObjectDbSet.Add(new ObjectEntity{ Id = 2, Code = "PTF0002", Name = "Portfolio Two" });
// Act
var response = await _client.GetAsync("/api/route");
response.EnsureSuccessStatusCode();
// Assert
var result = Assert.IsType<OkResult>(response);
}
public void Dispose()
{
_context.Dispose();
}
As I understand it, the .UseStartUp method ensures the TestServer uses my startup class
The issue I'm having is that when my Act statement is hit
var response = await _client.GetAsync("/api/route");
I get an error in my startup class that the connection string is null. I think My understanding of the problem is that when my controller is hit from the client it injects my data repository, which in turn injects the db context.
I think I need to configure the service as part of the new WebHostBuilder section so that it used the context created in the test. But I'm not sure how to do this.
ConfigureServices method in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
// Add framework services
services.AddMvc(setupAction =>
{
setupAction.ReturnHttpNotAcceptable = true;
setupAction.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter());
setupAction.InputFormatters.Add(new XmlDataContractSerializerInputFormatter());
});
// Db context configuration
var connectionString = Configuration["ConnectionStrings:YourConnectionString"];
services.AddDbContext<YourContext>(options => options.UseSqlServer(connectionString));
// Register services for dependency injection
services.AddScoped<IYourRepository, YourRepository>();
}
#ilya-chumakov's answer is awesome. I just would like to add one more option
3. Use ConfigureTestServices method from WebHostBuilderExtensions.
The method ConfigureTestServices is available in the Microsoft.AspNetCore.TestHost version 2.1(on 20.05.2018 it is RC1-final). And it lets us override existing registrations with mocks.
The code:
_server = new TestServer(new WebHostBuilder()
.UseStartup<Startup>()
.ConfigureTestServices(services =>
{
services.AddTransient<IFooService, MockService>();
})
);
Here are two options:
1. Use WebHostBuilder.ConfigureServices
Use WebHostBuilder.ConfigureServices together with WebHostBuilder.UseStartup<T> to override and mock a web application`s DI registrations:
_server = new TestServer(new WebHostBuilder()
.ConfigureServices(services =>
{
services.AddScoped<IFooService, MockService>();
})
.UseStartup<Startup>()
);
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
//use TryAdd to support mocking IFooService
services.TryAddTransient<IFooService, FooService>();
}
}
The key point here is to use TryAdd methods inside the original Startup class. Custom WebHostBuilder.ConfigureServices is called before the original Startup, so the mocks are registered before the original services. TryAdd doesn't do anything if the same interface has already been registered, thus the real services will not be even touched.
More info: Running Integration Tests For ASP.NET Core Apps.
2. Inheritance / new Startup class
Create TestStartup class to re-configure ASP.NET Core DI. You can inherit it from Startup and override only needed methods:
public class TestStartup : Startup
{
public TestStartup(IHostingEnvironment env) : base(env) { }
public override void ConfigureServices(IServiceCollection services)
{
//mock DbContext and any other dependencies here
}
}
Alternatively TestStartup can be created from scratch to keep testing cleaner.
And specify it in UseStartup to run the test server:
_server = new TestServer(new WebHostBuilder().UseStartup<TestStartup>());
This is a complete large example: Integration testing your asp .net core app with an in memory database.
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)
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