I need to redesign one of the system's module using Asp.Net Core and host it inside IIS server. So, I just need to replace already existing ASP.NET Web Forms app with the .Net Core based API. But the tricky part here that I still have to use upper level web.config file(s) and fetch configuration parameters from it.
I did brief research and found out that it may be a problem cause the configuration mechanism was changed and it's not recommended to use XML based we.config or app.config any more and I should use app.settings instead: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-2.2
I found this very nice article https://wildermuth.com/2018/04/15/Building-a-NET-Core-Configuration-Source about how to build custom configuration source and provider and now I can even extract app setting from the local web.config file but still experiencing the problem with getting access to the upper level web.config file.
When I do this
.ConfigureAppConfiguration((hostingContext, config) =>
{
config
.AddWebConfig("web.config", optional: true)
.AddJsonFile("appsettings.json", optional: true);
})
I can successfully find and parse web.config file, but when I try to find web.config in an upper level like this
.ConfigureAppConfiguration((hostingContext, config) =>
{
config
.AddWebConfig("..\\web.config", optional: false)
.AddJsonFile("appsettings.json", optional: true);
})
I get en error message
System.IO.FileNotFoundException: 'The configuration file
'..\web.config' was not found and is not optional.'
Any recommendations about how to get access to upper level web.config file would be very much appreciated!
Unfortunately I can't skip this part and this is one of the client's business requirements.
Related
I have an Azure function, and I'm using the DI system in order to register some types; for example:
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services
.AddTransient<IMyInterface, MyClass>()
. . . etc
However, I also was to register some data from my environment settings. Inside the function itself, I can get the ExecutionContext, and so I can do this:
IConfiguration config = new ConfigurationBuilder()
.SetBasePath(context.FunctionAppDirectory)
.AddJsonFile("local.settings.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables()
.Build();
However, in the FunctionsStartup, I don't have access to the ExecutionContext. Is there a way that I can either get the ExecutionContext from the FunctionsStartup class or, alternatively, another way to determine the current running directory, so that I can set the base path?
While the checked answer to this question is correct, I thought it lacked some depth as to why. The first thing you should know is that under the covers an Azure Function uses the same ConfigurationBuilder as found in an ASP.NET Core application but with a different set of providers. Unlike ASP.NET Core which is extremely well documented (https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/) this is not the case for Azure Functions.
To understand this set of providers you can place the following line of code in the Configure(IFunctionsHostBuilder builder) method of your FunctionStartup class,
var configuration = builder.Services.BuildServiceProvider().GetService<IConfiguration>();
place a debug break point after the command, execute your function in debug mode and do a Quick Watch… on the configuration variable (right click the variable name to select Quick Watch…).
The result of this dive into the code execution is the following list of providers.
Microsoft.Extensions.Configuration.ChainedConfigurationProvider
MemoryConfigurationProvider
HostJsonFileConfigurationProvider
JsonConfigurationProvider for 'appsettings.json' (Optional)
EnvironmentVariablesConfigurationProvider
MemoryConfigurationProvider
The ChainedConfigurationProvider adds an existing IConfiguration as a source. In the default configuration case, adds the host configuration and setting it as the first source for the app configuration.
The first MemoryConfigurationProvider adds the key/value {[AzureWebJobsConfigurationSection, AzureFunctionsJobHost]}. At least it does this in the Development environment. At the time I am writing this I can find no documentation on the purpose of this MemoryConfigurationProvider or AzureWebJobsConfigurationSection.
The HostJsonFileConfigurationProvider is another one of those under the covers undocumented providers, however in looking at documentation on host.json (https://learn.microsoft.com/en-us/azure/azure-functions/functions-host-json) it appears to be responsible for pulling this metadata.
The JsonConfigurationProvider for appsettings.json appears to be an obvious correlation to ASP.NET Core’s use of appsettings.json except for one thing which is it does not work. After some investigation I found that the Source FileProvider Root was not set to the applications root folder where the file is located but instead some obscure AppData folder (C:\Users%USERNANE%\AppData\Local\AzureFunctionsTools\Releases\3.15.0\cli_x64). Go fish.
The EnvironmentVariablesConfigurationProvider loads the environment variable key-value pairs.
The second MemoryConfigurationProvider adds the key/value {[AzureFunctionsJobHost:logging:console:isEnabled, false]}. At least it does this in the Development environment. Again, at the time I am writing this I can find no documentation on the purpose of this MemoryConfigurationProvider or AzureFunctionsJobHost.
Now the interesting thing that needs to be pointed out is that no where in the configuration is any mention of local.settings.json. That’s because local.settings.json is NOT part of the ConfigurationBuilder process. Instead local.settings.json is part of Azure Functions Core Tools which lets you develop and test your functions on your local computer (https://learn.microsoft.com/en-us/azure/azure-functions/functions-run-local). The Azure Function Core Tools only focus on specific sections and key/values like IsEncrypted, the Values and ConnectionString sections, etc. as defined in the documentation (https://learn.microsoft.com/en-us/azure/azure-functions/functions-run-local#local-settings-file). What happens to these key/values is also unique. For example, key/values in the Values section are inserted into the environment as variables. Most developers don’t even notice that local.settings.json is by default set to be ignored by Git which also makes it problematic should you remove the repository from you development environment only to restore it in the future. Something that ASP.NET Core has fixed with app secrets (https://learn.microsoft.com/en-us/aspnet/core/security/app-secrets).
So, what happens if we create our own configuration with ConfigurationBuilder as suggested in the original question
IConfiguration config = new ConfigurationBuilder()
.SetBasePath(context.FunctionAppDirectory)
.AddJsonFile("local.settings.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables()
.Build();
or using the example shown in one of the other answers?
ExecutionContextOptions executionContextOptions = builder.Services.BuildServiceProvider().GetService<IOptions<ExecutionContextOptions>>().Value;
IConfigurationBuilder configurationBuilder = new ConfigurationBuilder()
.SetBasePath(executionContextOptions.AppDirectory)
.AddEnvironmentVariables()
.AddJsonFile("appsettings.json", false)
.AddJsonFile("local.settings.json", true)
.AddUserSecrets(Assembly.GetExecutingAssembly(), true);
The following are just a few of the issues with both examples.
The second example is incorrectly ordered as AddEnvironmentVariables should come last.
Neither of the examples mentions the need for the following line of code.
List item
builder.Services.AddSingleton<IConfiguration>(configurationBuilder.Build());
Without this line the configuration only exist in the Configure(IFunctionsHostBuilder builder) method of your FunctionStartup class. However, with the line you replace the configuration your Azure Function build under the covers. This is not necessarily a good thing as you have no way of replacing providers like HostJsonFileConfigurationProvider.
Reading the local.settings.json file (.AddJsonFile("appsettings.json")) will NOT place the key/value pairs in the Values section into the configuration as individual key/value pairs as expected, but instead group them under the Values section. In other word, if for example you want to access {["AzureWebJobsStorage": ""]} in Values you might use the command configuration.GetValue("Values:AzureWebJobsStorage"). The problem is that Azure is expecting to access it by the key name "AzureWebJobsStorage". Even more interesting is the fact that since local.settings.json was never part of the ConfigurationBuilder process this is redundant as Azure Functions Core Tools has already placed these values into the environment. The only thing this will do is allow you to access sections and key/values not defined as part of local.settings.json (https://learn.microsoft.com/en-us/azure/azure-functions/functions-run-local#local-settings-file). But why would you want to pull configuration values out of a file that will not be copied into your production code?
All of this brings us to a better way to affect changes to the configuration without destroying the default configuration build by Azure Function which is to override the ConfigureAppConfiguration method in your FunctionStartup class (https://learn.microsoft.com/en-us/azure/azure-functions/functions-dotnet-dependency-injection#customizing-configuration-sources).
The following example takes the one provided in the documentation a step further by adding user secrets.
public override void ConfigureAppConfiguration(IFunctionsConfigurationBuilder builder)
{
FunctionsHostBuilderContext context = builder.GetContext();
builder.ConfigurationBuilder
.SetBasePath(context.ApplicationRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false)
.AddJsonFile($"appsettings.{context.EnvironmentName}.json", optional: true, reloadOnChange: false)
.AddUserSecrets(Assembly.GetExecutingAssembly(), true, true)
.AddEnvironmentVariables();
}
By default, configuration files such as appsettings.json are not automatically copied to the Azure Function output folder. Be sure to review the documentation (https://learn.microsoft.com/en-us/azure/azure-functions/functions-dotnet-dependency-injection#customizing-configuration-sources) for modifications to your .csproj file. Also note that due to the way the method appends the existing providers it is necessary to always end with .AddEnvironmentVariables().
You don't need any Configuration object in Azure Functions (v2). All the app settings get injected as Environment variables. So you can do just a simple Environment.GetEnvironmentVariable()
When running locally the local.settings.json gets read in the same way.
see here: https://learn.microsoft.com/en-us/sandbox/functions-recipes/environment-variables?tabs=csharp
There's a solid way to do this, answered here:
Get root directory of Azure Function App v2
The fact that Function Apps use environment variables as the typical way to get configuration is, while true, not optimal IMO. The ability to acquire an appsettings.json file in addition to items that deserve to be environment variables has its place.
The number of env vars being set via the DevOps task: "Azure App Service Deploy" option "Application and Configuration Settings" > "App settings" gets completely out of hand.
This is my implementation of it at the time of this writing:
ExecutionContextOptions executionContextOptions = builder.Services.BuildServiceProvider().GetService<IOptions<ExecutionContextOptions>>().Value;
IConfigurationBuilder configurationBuilder = new ConfigurationBuilder()
.SetBasePath(executionContextOptions.AppDirectory)
.AddEnvironmentVariables()
.AddJsonFile("appsettings.json", false)
.AddJsonFile("local.settings.json", true)
.AddUserSecrets(Assembly.GetExecutingAssembly(), true);
This allows me to leverage my release process variables to do environment specific "JSON variable substitution" for the bulk of my configuration, which lives in a nicely structured appsettings.json that is set to Copy Always. Notice that the loading of appsettings.json is set to not optional (the false setting), while I have local settings and secrets set to optional to accommodate local development.
appsettings.json can then be formatted nice and structured like this. Release variables named properly, e.g. "MyConfig.Setting" will replace the value properly if you set your release to do JSON variable substitution.
{
"Environment": "dev",
"APPINSIGHTS_INSTRUMENTATIONKEY": "<guid>",
"MyConfig": {
"Setting": "foo-bar-baz"
}
}
While local.settings.json remains in the flat style:
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "DefaultEndpointsProtocol=https;AccountName=<accountname>;AccountKey=<accountkey>;EndpointSuffix=core.windows.net",
"Strickland:Propane:Config": "Dammit, Bobby!"
}
}
In addition, I set some App settings (env vars) to Azure KeyVault References in the release process, as well as the minimum settings that are required for Azure Function runtime to start properly and communicate properly with app insights live metrics.
Hope this helps someone who, like me, hates the ever-growing mass of -Variable.Name "$(ReleaseVariableName)" items in App settings. :)
Unfortunately, at present there is no standard way to get the local running directory. It would be best if ExecutionContext or something similar exposed this.
In the absence of a standard way, I am using AzureWebJobsScriptRoot environment variable to get the current working directory, but it only works locally. In azure environment, I am using Environment.GetEnvironmentVariable("HOME")}\\site\\wwwroot.
I posted a code for this in response to a similar question here:
Azure Functions, how to have multiple .json config files
There is also a similar solution at this github issue.
You might want to use GetContext().ApplicationRootPath, for example:
[assembly:FunctionsStartup(typeof(SampleFunction.FunctionsAppStartup))]
namespace SampleFunction
{
public class FunctionsAppStartup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
string appRootPath = builder.GetContext().ApplicationRootPath;
// ...
}
}
}
I recently deployed an ASP.Net Web API project to our Azure App Service test slot but started receiving an error when making requests to the API endpoints. Through remote debugging, it became clear that the app was extracting my dev connection strings from the deployed web.config file.
The connection strings are supposed to come from the Application Settings we set up via the Azure Portal - and, in previous deployments, they were - but that's not the case.
Why would this happen and what can be done to ensure the correct behaviour occurs? We absolutely don't want our production database secrets being put into GIT via the web.config...
I recently experienced the same problem and fixed it:
In Azure App Services, the machine-wide web.config file is located at D:\Windows\Microsoft.NET\Framework64\v4.0.30319\Config\web.config.
This file differs to a normal machine-wide web.config file because it has this extra element:
<system.web>
...
<compilation>
<assemblies>
<add assembly="EnvSettings, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
...
</assemblies>
</compilation>
</system.web>
The EnvSettings.dll assembly is located inside D:\Program Files\IIS\Microsoft Web Hosting Framework (unfortunately this directory is access-controlled and I can't get into it).
But EnvSettings.dll is mirrored in the GAC, so I was able to copy it from there.
Inside EnvSettings.dll is an [assembly: PreApplicationStartMethod] attribute that runs a method in EnvSettings.dll which copiesthe APPSETTING_ and database connection-string settings from Environment Variables into the .NET Framework ConfigurationManager.AppSettings and ConfigurationManager.ConnectionStrings collections.
Note this copying only happens once (during application startup), and only in the first AppDomain - so if you have other AppDomain instances in your application they won't see the updated ConfigurationManager.
Therefore, if you see that your Azure Portal configuration settings for your App Service are not being used when you dump your ConfigurationManager, then the following is likely happening:
You used <clear /> in your <compilation><assemblies> element, which stops EnvSettings.dll from being loaded at all.
In which case you need to either add back the <add assembly="EnvSettings... element from above to your own web.config, or find some other way to load it.
I don't recommend saving EnvSettings.dll locally and adding an assembly reference to your project, as EnvSettings.dll is part of the Microsoft Web Hosting Framework.
Or you have code that is clearing or resetting ConfigurationManager after EnvSettings populates it for you.
Or something else is going on that I have no idea about!
As an alternative to having EnvSettings.dll copy your settings over, another option is to copy the environment-variables over yourself - and as you control the code that does this it means you can call it whenever you need to (e.g. if you ever reset them).
Here's the code I used:
public static class AzureAppSettingsConfigurationLoader
{
public static void Apply()
{
foreach( DictionaryEntry environmentVariable in Environment.GetEnvironmentVariables() )
{
String name = (String)environmentVariable.Key;
String value = (String)environmentVariable.Value;
if( name.StartsWith( "APPSETTING_", StringComparison.OrdinalIgnoreCase ) )
{
String appSettingName = name.Substring( "APPSETTING_".Length );
ConfigurationManager.AppSettings[ appSettingName ] = value;
}
else if( name.StartsWith( "SQLAZURECONNSTR_", StringComparison.OrdinalIgnoreCase ) )
{
String csName = name.Substring( "SQLAZURECONNSTR_".Length );
ConfigurationManager.ConnectionStrings.Add( new ConnectionStringSettings( csName, value, providerName: ""System.Data.SqlClient" ) );
}
}
}
}
See my sample here: http://mvc5appsettings.azurewebsites.net/
// My web.config also has a "HERO_TEXT" key in
// that reads "Value from web.config"
string hero = ConfigurationManager.AppSettings["HERO_TEXT"];
Wiki page on App Settings for .NET:
https://github.com/projectkudu/kudu/wiki/Managing-settings-and-secrets
As already mentioned here, make sure you have that App Setting in the right slot.
As I know, the settings in Azure portal will override existing setting in Web.config. So If you want to ignore the Azure Application settings in portal and use Web.config instead. I am afraid you need to configure the settings in web.config, and remove the same key/pair in Azure portal.
I saw that in asp.net older version, editing configuration cause recycle.
(so we implemented our own library for dynamic config to avoid recycling).
Now I'm writing new asp.net core app and I have not seen anywhere that there is recycle, if using built-in configuration (like:
.AddJsonFile("appsettings.json", optional: true, **reloadOnChange: true**)
So, can I use it without expecting any down time?
AFAIK, editing configuration does not cause IIS recycle. One of the reason maybe is that IIS is acting now merely as a reverse proxy and the application itself runs as a separate process using the Kestrel HTTP server (if you use Kestrel).
If you need to catch moment, when configuration has been changed, you can use Configuration Reload Token (Microsoft.Extensions.Configuration.ConfigurationReloadToken):
var config = builder.Build();
var token = config.GetReloadToken();
token.RegisterChangeCallback(_ =>
{
Console.WriteLine("Changed");
}, null);
But note, the token only fires once, so need have code in the callback to a change token, if needed.
I've got an azure webjob, that has some appsettings for api keys etc.
I've also got a bunch of PRODUCTION azure app settings (specified in the portal), that should override my webjob config appsettings.. But they don't.
For my website, they work as expected, and all is fine. For the webjobs however, they just get completely ignored, and my app settings from the config are used instead.
Is this a bug in azure? All the docs suggest that this should work.
EDIT
I found this blog all about using CloudConfigurationManager.GetSetting so I've implemented that and it still won't work - still using the settings that are defined in web job's app settings file :(
Thanks
So it looks like I've found a bug!
It's because my AppSettings were referenced from another file, like this (in app.config):
<AppSettings file="appsettings.config"/>
This basically breaks azure's config management.
Use
"D:\home\site\wwwroot\" to create an absolute path to files inside your website.
inside your WebJob to point to any file inside your wwwwroot directory. Using the Absolute path with D:\home\site\wwwroot\appsettings.config should fix you website.
Did you set your settings only in the Web.config file or did you set them in the portal or using the VS "Website Settings" tooling? You need to set the WebJobs-related settings at that level, not just in the Web.config. Once you do that, restart your site, and the settings should be there.
I had a similar problem but for me it was that we set the "is_in_place" settings.job value to true. I guess when it's in place, it doesn't update the config file with the settings. We didn't have a strict requirement that it run in place, so removing that setting fixed the problem for us.
I have a similar problem with several web jobs sharing a common settings file. It would be inconvenient to duplicate the settings in each job.
Someone mentioned using configSource= instead of file= in the app.config to reference the external config settings file. I tried this, and it appears that it now works as expected. The settings are being taken from the Portal App Settings instead of the file now.
Fortunately, the settings for the web jobs all come from the external file. The use of file= allowed me to use additional per-app settings other than just those in the file, but fortunately I don't need to do that.
A workaround to this problem is to place the settings inline in the AppSettings tag of the Web.config like this.
<AppSettings>
<add key="host" value="someHost" />
</AppSettings>
Azure App Service Application Settings are persisted in the Environment Variables. Go to the Azure Portal add your application settings or connection string values. Then, you can check out the environment variables of your app service at https://sitename.scm.azurewebsites.net
If you want your webjobs to share those settings, in your webjob project, create a appsettings.json:
{
"APPSETTING_AzureWebJobsDashboard": "",
"APPSETTING_AzureWebJobsStorage": "",
"SQLAZURECONNSTR_xxx": ""
}
In your Main() method
private static void Main(string[] args)
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables();
var configurations = builder.Build();
var services = new ServiceCollection()
.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(configurations["SQLAZURECONNSTR_xxx"]),
ServiceLifetime.Transient)
.BuildServiceProvider();
var host = new JobHost(new JobHostConfiguration
{
DashboardConnectionString = configurations["APPSETTING_AzureWebJobsDashboard"],
StorageConnectionString = configurations["APPSETTING_AzureWebJobsStorage"]
});
host.RunAndBlock();
}
This is simpler than trying to figure out the path to the website config on the app service
I'm implementing the combined web/worker role scenario as described here where you simply add the following to your worker role:
public override void Run()
{
// This is a sample worker implementation. Replace with your logic.
Trace.WriteLine("WorkerRole1 entry point called", "Information");
while (true)
{
Thread.Sleep(10000);
Trace.WriteLine("Working", "Information");
}
}
The problem, as noted in the post's comments, is that this worker process cant read web.config so you have to add an app.config. It is also noted that app.config does not get deployed automatically.
So my question is how do I configure my project so app.config will get deployed?
I've added app.config to my project, set the Build Action to "Content", and "Copy always"
THIS WORKS FINE IN THE EMULATOR, but not when deployed to Azure.
Note: I noticed in the emulator a projectname.dll.config is created, but not when deployed to Azure.
I'm using VS2010, Windows Azure Tools 2011
I know some will suggest using the .cscfg file instead, but many of my components get their settings from web.config/app.config:
Elmah, Transient Fault Handling Client, Diagnostics, Email, etc...
Please read thoroughly this blog post. It explains in great details what is happening in Windows Azure Web Role with Full IIS.
What you need to do, is to add a WaIISHost.exe.config file (with copy to output = copy always). And put all the configurations you need in that file. This is because, your code (RoleEntryPoint) lives in WaIISHost.exe process, and not your pdojectName.dll process.
For me using Azure SDK 1.8 and deploying my Web Worker Role from the Visual Studio using publish, I had to include a config file, named. ProjectName.Dll.config with my settings. The configuration from app.config is not picked up by the web role when running in windows azure. And the app.config file is not converted into a ProjectName.Dll.config and added automatically to the bin folder of the deployment package, so you have to create it by hand and set it to copy always.
I'm using Azure SDK 2.0 and OS Family 3, and has been very confused about this. So I created a MVC 4.0 website with all 4 config files suggested in various answers. That is:
Web.config
App.config
WaIISHost.exe.config
[AssemblyName].dll.config
All but Web.config was set to "Copy if newer".
In the configs file I wrote:
<appSettings>
<add key="AppSettingFile" value="[NameOfConfigFile]"/>
</appSettings>
In the WebRole.cs I have the following code:
public class WebRole : RoleEntryPoint
{
public override void Run()
{
string appSetting = ConfigurationManager.AppSettings["AppSettingFile"] ?? "No config file found";
Trace.TraceInformation("Config file: " + appSetting);
while (true)
{
...
}
}
}
Result when deployed with 4 .config files:
"Config file: App.config". So App.config must be the answer, right?
Wrong! Result when deployed with only Web.config and App.config:
"Config file: No config file found". Hmm wierd.
Result when deployed with Web.config, App.config and [AssemblyName].dll.config:
"Config file: [AssemblyName].dll.config". So [AssemblyName].dll.config must be the answer, right?
Wrong! Result when deployed with only Web.config and [AssemblyName].dll.config:
"Config file: No config file found". WTF!
Result when deployed with only Web.config and WaIISHost.exe.config:
"Config file: No config file found".
Result when deployed with Web.config, App.config and WaIISHost.exe.config:
"Config file: No config file found". WTF!
So my conclusion is that you need to have 3 or 4 config files to be able to configure the Worker role of a Web project.
This is clearly a bug. Personally I think the intention from MS was to change from WaIISHost.exe.config to App.config (to align with Worker Roles and .NET in general). But App.config is only used when all 4 .config files exists.
So for now I'm having Web.config and both App.config and [AssemblyName].dll.config, and they contain exactly the same.
Hopefully going forward with Azure SDK 2.x we can use only App.config and Web.config.