While building a .net console app which uses the new ConfigurationBuilder implementation for what used to be appSettings, I've run into a problem.
I have the following code:
public static void Main(string[] args)
{
try
{
var builder = new ConfigurationBuilder().AddJsonFile("config.json");
var config = builder.Build();
if (config["Azure"] != null)
{
;
}
}
catch (System.IO.FileNotFoundException exception)
{
...
}
}
The config.json file is in the same directory and looks like this:
{
"Azure": {
"Storage": {
"ConnectionString": "...",
"ContainerName": "..."
}
},
"Data": {
"DefaultConnection": {
"ConnectionString": "..."
}
},
"Logging": {
"RecordProgress": "..."
}
}
But the config object contains no keys.
I read somewhere that if the file path passed to AddJsonFile can't be found then it throws a FileNotFoundException but in my code that exception is never thrown.
So presuming the config.json file can be found, why are the settings not being loaded?
My original answer was off the mark. Here's an updated version. This is based on the recently released RC2.
The current runtime will throw FileNotFoundException if the config file isn't found. The AddJsonFile() extension method takes an optional parameter called optional that, if true, will cause the method not to throw.
I added config.json and it doesn't get copied to the bin directory, so I had to specify the location using the SetBasePath() extension method. This is what the Web project templates do in Startup, using IHostingEnvironment.ContentRootPath. In console apps, you can use Directory.GetCurrentDirectory().
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("config.json");
var config = builder.Build();
Finally, the config["Key"] indexer didn't work for me. Instead, I had to use the GetSection() extension method. So your example config file above might be accessed with:
// var result = config["Logging"];
var section = config.GetSection("Logging");
var result = section["RecordProgress"];
I left the old answer for the time being.
Old answer:
I found a possible solution here: https://github.com/aspnet/Mvc/issues/4481.
Quoting from the issue.
Thanks for the repro project. Looks like you need to update the
project.json file to have the "content" node and have Config.json
specified here.
Example:
https://github.com/aspnet/MusicStore/blob/dev/src/MusicStore/project.json#L22
It appears that a new content element may be required in your project.json.
...
"content": [
"Areas",
"Views",
"wwwroot",
"config.json",
"web.config"
],
...
I encountered the same problem.
config.json was in the correct directory and it was still not found.
The solution:
config.json contained a typo that broke the json syntax. After fixing the json syntax/format in config.json everything worked.
Related
We have a WPF app using Serilog for logging. When we try to open the file in a C++ dll we are getting an error. GetLastError returns "file in use by another process". The aim is to have the C+ code and C# code writing to the same log file. We have checked if we change the name of the file in C++ we get a a log file created in the same directory as where the WPF log file is. The C++ code is loaded into the same process so not sure why there is an issue with doing this.
ILoggerFactory loggerFactory = new LoggerFactory();
var path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
var config = new ConfigurationBuilder()
.SetBasePath(path)
.AddJsonFile("logging.json")
.Build();
string logpath = System.IO.Path.Combine($"{Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)}", "log.txt");
var log = new LoggerConfiguration()
.MinimumLevel.Information()
.ReadFrom.Configuration(config)
.Enrich.FromLogContext().WriteTo.File(logpath, shared: true).CreateLogger();
loggerFactory.AddSerilog(log);
Logging.json
{
"Serilog": {
"Using": [ "Serilog.Sinks.Debug", "Serilog.Sinks.RollingFile" ],
"MinimumLevel": {
"Default": "Information"
},
"Enrich": [ "FromLogContext" ],
"WriteTo": [
{ "Name": "Debug" },
{
"Name": "File",
"Args": {
"path": "%APPDATA%\\log.txt"
}
}
],
"Properties": {
"Application": "app"
}
}
}
trying to access it in a C++ dll
std::ofstream* ofs = new std::ofstream(logFileName.c_str(), std::ios_base::out | std::ios_base::app));
if (ofs->is_open())
{
// the is_open call fails so we don't get here
std::cout << "Operation successfully performed\n";
}
The aim is to have the C+ code and C# code writing to the same log file.
The operating system locks the file so only one process at a time can open it for writing.
Writing concurrently and reliably to a single file means that you need to synchronize the access to it from your processes, for example using a mutex. Each process should then acquire the mutex, open the file stream, write to the file and then close the stream.
A better idea would be probably to have a file per process and then merge them together when needed, for example at the end of each day or something.
When porting an application that uses a settings file to an Azure Function, is it necessary to remove reliance on the file?
I want to write a function app to import data from Xero into an Azure sql database.
The Xero SDK I am using is expecting an appsettings.json file.
Consequently when the function runs I get the error
System.Private.CoreLib: Exception while executing function:
FunctionXeroSync. Xero.Api: The type initializer for
'Xero.Api.Infrastructure.Applications.Private.Core' threw an exception.
Microsoft.Extensions.Configuration.FileExtensions: The configuration file
'appsettings.json' was not found and is not optional. The physical path is
'C:\Users\kirst\AppData\Local\AzureFunctionsTools\Releases\2.6.0\cli\appsettings.json'.
I tried putting the relevant settings in via the Manage Application Settings link on the VS2017 Project Publish Tab. Clearly this fails. Is there another way I can use?
Here is the relevant code in the api. I would prefer not to have to modify it, so that I can use the official nuget package.
namespace Xero.Api
{
public class XeroApiSettings : IXeroApiSettings
{
public IConfigurationSection ApiSettings { get; set; }
public XeroApiSettings(string settingspath)
{
var builder = new ConfigurationBuilder()
.AddJsonFile(settingspath)
.Build();
ApiSettings = builder.GetSection("XeroApi");
}
public XeroApiSettings() : this("appsettings.json")
{
}
public string BaseUrl => ApiSettings["BaseUrl"];
public string CallbackUrl => ApiSettings["CallbackUrl"];
public string ConsumerKey => ApiSettings["ConsumerKey"];
public string ConsumerSecret => ApiSettings["ConsumerSecret"];
public string SigningCertificatePath => ApiSettings["SigningCertPath"];
public string SigningCertificatePassword => ApiSettings["SigningCertPassword"];
public string AppType => ApiSettings["AppType"];
public bool IsPartnerApp => AppType?.Equals("partner", StringComparison.OrdinalIgnoreCase) ?? false;
}
}
When I add
log.LogInformation("base directory: "+AppDomain.CurrentDomain.BaseDirectory);
to the function I get
D:\Program Files (x86)\SiteExtensions\Functions\2.0.12095-alpha\32bit\
when running in the portal
When porting an application that uses a settings file to an Azure Function, is it necessary to remove reliance on the file?
Not necessary, we can still use the settings file required by the application. We only need to make sure the path of settings file is correct.
Put appsettings.json under function project and set it to be copied to output/publish directory.
Add ExecutionContext context in Azure Function method signature, it's used to find current function app directory(where appsettings.json locates).
Pass the valid path of appsettings.json in Azure Function to initialize XeroApiSettings.
var xeroApiSettings = new XeroApiSettings(context.FunctionAppDirectory+"/appsettings.json");
This Jon Gallant's blog suggests that you need to add the optional parameter to the AddJsonFile as it does not exist when you deploy:
var config = new ConfigurationBuilder()
.SetBasePath(context.FunctionAppDirectory)
.AddJsonFile("local.settings.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables()
.Build();
Note that in Azure, this will refer to the 'appsettings.json' file
I am porting an application from .NET Framework to .NET Core (Standard).
Within the application, we have the following code
public LogMessageListenerFromConfig()
: this(AppDomain.CurrentDomain.BaseDirectory.ConfigurationFile, LoggingSection.DefaultName)
{ }
public LogMessageListenerFromConfig(string section)
: this(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile, section)
{ }
public LogMessageListenerFromConfig(string filename, string section)
{
// todo: set up a file watcher and refresh the listeners (etc.) when it changes.
var fileMap = new ExeConfigurationFileMap { ExeConfigFilename = filename };
var configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
_section = LoggingSection.Section(configuration, section);
Refresh();
}
This appears to be compatible with .NET Core apart from the following statement
AppDomain.CurrentDomain.SetupInformation.ConfigurationFile
There is no SetupInformation on an AppDomain anymore. Fair enough I read that it would cause issues etc. However, how else can I get the application configuration file name in a utility class?
Please be aware that this class will be used in both console and asp.net (core) applications.
Any help appreciated
Stephen
Same problem if you are porting a library to .Net Standard. Solution is this;
install this nuget package system.configuration.configurationmanager then you can use:
var conf = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
var configFilePath = conf.FilePath;
I am following this guide. I have a Startup in the API project that uses an appsettings.json configuration file.
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
Log.Logger = new LoggerConfiguration()
.Enrich.FromLogContext()
.ReadFrom.Configuration(Configuration)
.CreateLogger();
}
The particular part I'm looking at is the env.ContentRootPath. I did some digging around and it looks like my appsettings.json isn't actually copied to the bin folder but that is fine as ContentRootPath is returning MySolution\src\MyProject.Api\, which is where the appsettings.json file is located.
So in my integration test project I have this test:
public class TestShould
{
private readonly TestServer _server;
private readonly HttpClient _client;
public TestShould()
{
_server = new TestServer(new WebHostBuilder().UseStartup<Startup>());
_client = _server.CreateClient();
}
[Fact]
public async Task ReturnSuccessful()
{
var response = await _client.GetAsync("/monitoring/test");
response.EnsureSuccessStatusCode();
var responseString = await response.Content.ReadAsStringAsync();
Assert.Equal("Successful", responseString);
}
This is basically copy and paste from the guide. When I debug this test, ContentRootPath is actually MySolution\src\MyProject.IntegrationTests\bin\Debug\net461\, which is obviously the build output folder for the test project and again the appsettings.json file is not there (yes I do have another appsettings.json file in the test project itself) so the test fails at creating the TestServer.
I tried getting around this by modifying the test project.json file.
"buildOptions": {
"emitEntryPoint": true,
"copyToOutput": {
"includeFiles": [
"appsettings.json"
]
}
}
I hoped this would copy the appsettings.json file to the build output directory but it complains about the project missing a Main method for an entry point, treating the test project like a console project.
What can I do to get around this? Am I doing something wrong?
Integration test on ASP.NET.Core 2.0 follow MS guide ,
You should right click appsettings.json set its property Copy to Output directory to Copy always
And now you could find the json file in output folder, and build TestServer with
var projectDir = GetProjectPath("", typeof(TStartup).GetTypeInfo().Assembly);
_server = new TestServer(new WebHostBuilder()
.UseEnvironment("Development")
.UseContentRoot(projectDir)
.UseConfiguration(new ConfigurationBuilder()
.SetBasePath(projectDir)
.AddJsonFile("appsettings.json")
.Build()
)
.UseStartup<TestStartup>());
/// Ref: https://stackoverflow.com/a/52136848/3634867
/// <summary>
/// Gets the full path to the target project that we wish to test
/// </summary>
/// <param name="projectRelativePath">
/// The parent directory of the target project.
/// e.g. src, samples, test, or test/Websites
/// </param>
/// <param name="startupAssembly">The target project's assembly.</param>
/// <returns>The full path to the target project.</returns>
private static string GetProjectPath(string projectRelativePath, Assembly startupAssembly)
{
// Get name of the target project which we want to test
var projectName = startupAssembly.GetName().Name;
// Get currently executing test project path
var applicationBasePath = System.AppContext.BaseDirectory;
// Find the path to the target project
var directoryInfo = new DirectoryInfo(applicationBasePath);
do
{
directoryInfo = directoryInfo.Parent;
var projectDirectoryInfo = new DirectoryInfo(Path.Combine(directoryInfo.FullName, projectRelativePath));
if (projectDirectoryInfo.Exists)
{
var projectFileInfo = new FileInfo(Path.Combine(projectDirectoryInfo.FullName, projectName, $"{projectName}.csproj"));
if (projectFileInfo.Exists)
{
return Path.Combine(projectDirectoryInfo.FullName, projectName);
}
}
}
while (directoryInfo.Parent != null);
throw new Exception($"Project root could not be located using the application root {applicationBasePath}.");
}
Ref: TestServer w/ WebHostBuilder doesn't read appsettings.json on ASP.NET Core 2.0, but it worked on 1.1
In the end, I followed this guide, specifically the Integration Testing section towards the bottom of the page. This removes the need to copy the appsettings.json file to the output directory. Instead it tells the test project the actual directory of the web application.
As for copying the appsettings.json to the output directory, I also managed to get that to work. Combined with the answer from dudu, I used include instead of includeFiles so the resulting section would look something like this:
"buildOptions": {
"copyToOutput": {
"include": "appsettings.json"
}
}
I'm not entirely sure why this works but it does. I had a quick look at the documentation but couldn't find any real differences and since the original problem was essentially solved I didn't look further.
Remove "emitEntryPoint": true in test project.json file.
I've created a static class to use my configurations, but when I try to add the JSON file to the configuration I got an exception:
MyConfigurations.json:
{ "ConnectionString": "my connection string", ...}
My static class constructor:
static MyConfigurations() {
var configuration = new Configuration()
.AddJsonFile("MyConfigurations.json")
.AddEnvironmentVariables();
...
...
My exception occurs when the .AddJsonFile is executed.
Exception: Object reference not set to an instance of an object."
StackTrace:
at
Microsoft.Framework.ConfigurationModel.PathResolver.get_ApplicationBaseDirectory()
at
Microsoft.Framework.ConfigurationModel.JsonConfigurationExtension.AddJsonFile(IConfigurationSourceRoot
configuration, String path, Boolean optional) at
Microsoft.Framework.ConfigurationModel.JsonConfigurationExtension.AddJsonFile(IConfigurationSourceRoot
configuration, String path) at
Project.SharedKernel.MyConfigurations..cctor() in
C:\Project\Project.SharedKernel\MyConfigurations.cs:line 86
You have not set the application base path which the configuration API needs to determine where the physical config files live. You can set it using the SetBasePath extension method. A typical implementation looks like this:
public Startup(IApplicationEnvironment appEnv)
{
var configuration = new ConfigurationBuilder()
.SetBasePath(appEnv.ApplicationBasePath)
.AddJsonFile("MyConfigurations.json")
.AddEnvironmentVariables()
.Build();
}
Note: this only counts for beta8, see this question. You don't have to specify the default base path anymore in RC1: https://github.com/aspnet/Announcements/issues/88.