Get root directory of Azure Function App v2 - c#

I build an Azure Function App (v2). Configuration tasks necessary for all functions are done in a Setup class that is structured like the following:
[assembly: WebJobsStartup(typeof(Startup))]
internal class Startup : IWebJobsStartup
{
public void Configure(IWebJobsBuilder builder)
{
Configuration = new ConfigurationBuilder()
.SetBasePath(<functionAppDirectory>)
.AddJsonFile("local.settings.json")
.Build();
builder.AddDependencyInjection(ConfigureServices);
}
public IConfiguration Configuration { get; set; }
private void ConfigureServices(IServiceCollection services)
{
var connection = Configuration.GetConnectionString("<myconnection-string>");
...
}
}
In ConfigureServices I want to read a connection string from a configuration file. For that the function app base folder has be specified with SetBasePath. But I found no way to get access to this path. According to https://github.com/Azure/azure-functions-host/wiki/Retrieving-information-about-the-currently-running-function an ExecutionContext can be injected in a function, which contains the path need. But how do I access ExecutionContext in my Startup class?

You can use this piece of code in your startup file.
I have just tested it today for my project and it works on both cloud and local.
var executioncontextoptions = builder.Services.BuildServiceProvider()
.GetService<IOptions<ExecutionContextOptions>>().Value;
var currentDirectory = executioncontextoptions.AppDirectory;

TL;DR: just use Environment.GetEnvironmentVariable.
The ConfigurationBuilder approach shows up in a lot of blog posts, and worked up until we started doing DI. But there is no context parameter, so ConfigurationBuilder immediately starts to cause some strain.
I think people went this direction because in Azure Functions 2, we switched to ASP.NET Core configuration which caused ConfigurationManager to stop working. ConfigurationBuilder was a reasonable place to land. It felt congruent with MVC, and worked fine up until the introduction of DI.
But now that we are doing DI, it's becoming clear that Environment.GetEnvironmentVariable might have been the better choice all along for this platform... There's less code overhead, and it maps cleanly to the configuration model of Azure Functions: in dev, it picks up items in the local.settings.json > Values array, and in production it picks up your environment variables, and it just works.
It is different than what we do in MVC. Until these platforms come into closer parity, however, we should do what makes sense in Functions, rather than trying to force solutions from MVC.
So:
[assembly: WebJobsStartup(typeof(StartUp))]
namespace Keystone.AzureFunctions
{
public class StartUp : IWebJobsStartup
{
public void Configure(IWebJobsBuilder builder)
{
var connectionString = Environment.GetEnvironmentVariable("KeystoneDB");
// Configure EF
builder.Services.AddDbContext<KeystoneDB>(options => options.UseSqlServer(connectionString));
}
}
}
And your local.settings.json might look like this:
{
"IsEncrypted": false,
"Values": {
"KeystoneDB": "[CONNECTION STRING HERE]"
"FUNCTIONS_WORKER_RUNTIME": "dotnet"
}
}
You can also use Key Vault with Environment. It works great.

Greeting,
I found a solution that works in the Startup :
var fileInfo = new FileInfo(Assembly.GetExecutingAssembly().Location);
string path = fileInfo.Directory.Parent.FullName;
var configuration = new ConfigurationBuilder()
.SetBasePath(Environment.CurrentDirectory)
.SetBasePath(path)
.AddJsonFile("appsettings.json", false)
.Build();

You might want to use FunctionsStartupAttribute and IFunctionsHostBuilder from Microsoft.Azure.Functions.Extensions, for example:
[assembly:FunctionsStartup(typeof(SampleFunction.FunctionsAppStartup))]
namespace SampleFunction
{
public class FunctionsAppStartup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
string appRootPath = builder.GetContext().ApplicationRootPath;
// ...
}
}
}

The only workaround I found for configuration builder in Startup() method is to use hardcoded path "/home/site/wwwroot/"
var config = new ConfigurationBuilder()
.SetBasePath("/home/site/wwwroot/")
.AddJsonFile("config.json", optional: false)
.Build();
System.Environment.CurrentDirectory does not work in Azure. Though it works locally. But in Azure it gives an error: The configuration file 'config.json' was not found and is not optional. The physical path is '/config.json'. And function does not start.

Try using Environment.CurrentDirectory

Related

appsettings.json values not binding in Azure Functions App V3

I am a new a developer in terms of dealing with creating an Azure functions App. Since we have different environments for deployment, we use environment based appsettings.json files to load the correct values from the Azure Key Vault. I followed the tutorial here: https://learn.microsoft.com/en-us/azure/azure-functions/functions-dotnet-dependency-injection for adding appsettings.json files. Here is what my Startup class looks like.
[assembly: FunctionsStartup(typeof(Trialtimer.FunctionApp.Startup))]
namespace Trialtimer.FunctionApp
{
public class Startup: FunctionsStartup
{
public override void ConfigureAppConfiguration(IFunctionsConfigurationBuilder builder)
{
FunctionsHostBuilderContext context = builder.GetContext();
var config = builder.ConfigurationBuilder
.AddJsonFile(Path.Combine(context.ApplicationRootPath, "appsettings.json"), optional: true, reloadOnChange: false)
.AddJsonFile(Path.Combine(context.ApplicationRootPath, $"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")}.json"), optional: true, reloadOnChange: true)
.AddEnvironmentVariables()
.Build();
}
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddOptions<MyOptions>().Configure<IConfiguration>((options, configuration) =>
{
var section = configuration.GetSection("MyOptions");
section.Bind(options);
});
}
}
}
My appsettings.json file is empty while my appsettings.build.json file has the following, based on the tutorial above
"MyOptions": {
"MyCustomSetting": "Foobar"
}
My MyOptions class is the following
public class MyOptions
{
public string MyCustomSetting { get; set; }
}
My main class which houses the Azure Timer function is the following
public class TestTimerFunction
{
private readonly MyOptions _settings;
public TestTimerFunction(IOptions<MyOptions> options)
{
_settings = options.Value;
}
[FunctionName("AzureTimerFunction")]
public async Task Run([TimerTrigger("*/3 * * * * *")]TimerInfo myTimer, ILogger log)
{
var option = _settings;
log.LogInformation($"Renew function executed at: {DateTime.Now} successfully with {option.MyCustomSetting}");
}
}
The problem i'm having is, when i run the app, the "MyCustomSetting" variable is null. If i add the same json block that is in my appsettings.build.json file to the local.settings.json file, it looks like it gets read correctly and "MyCustomSetting" has the value "FooBar".
The ASPNETCORE_ENVIRONMENT variable is also correctly returning "build". I've checked to make sure that both the appsettings.json files are "Copy if Newer" and when hovering over the "configuration" variable in the "Configure" method, i see that my appsettings.build.json file there. How can I make the function app bind the block from appsettings.build.json rather than local.settings.json? Am i doing something incorrectly?
Got it to work by swapping out the
builder.Services.AddOptions<MyOptions>().Configure<IConfiguration>((options, configuration) =>
{
var section = configuration.GetSection("MyOptions");
section.Bind(options);
});
with
builder.Services.Configure<MyOptions>(builder.GetContext().Configuration.GetSection("MyOptions"));
I don't know why this worked while the way that was described in the tutorial did not. But this has resolved my issue so I'm marking the question as answered.

How to use proper connection string .net core console application

I have scenario where I have to expand my project with .NET CORE WEB API PROJECT.
In the beginning it was just console app which was working as a windows service on a simple tasks, adding data from xml files to database.
And my structure looks like this:
Database Project (ENTITY FRAMEWORK CORE)
Console App ( REFERENCING TO DATABASE PROJECT)
Web Api (REFERENCING TO DATABASE PROJECT)
Now when I created WEB API project it requires me to register Context in Startup.cs and I did it like this:
Startup.cs
// DbContext for MSSQL
services.AddDbContextPool<ProductXmlDBContext>(options => options.UseSqlServer(_configuration.GetConnectionString(Config.CONNECTION_STRING)));
And that is all fine.
But now I want to read connection string for console application for it's own connection string (appsettings.json) since when I created API it got his own appsettings.json where I'm storing connection string for WEB API.
My DBContext looks like this:
public ProductXmlDBContext()
{
}
// I added this because it was required for WEB API to work
public ProductXmlDBContext(DbContextOptions<ProductXmlDBContext> options) : base(options)
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(#"Server=localhost;Database=ProductXml;Trusted_Connection=True;");
}
In my console app I have add appsettings.json
And here is my Program.cs:
static void Main(string[] args)
{
try
{
// Set up configuration sources.
var builder = new ConfigurationBuilder()
.SetBasePath(Path.Combine(AppContext.BaseDirectory))
.AddJsonFile("appsettings.json", optional: true);
var services = new ServiceCollection();
Configuration = builder.Build();
//
var x = Configuration.GetConnectionString("DefaultConnection");
services.AddDbContextPool<ProductXmlDBContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory);
}
}
And here is how I use Context in my console application:
using (var context = new ProductXmlDBContext())
{
companies = context.Companies.ToList();
}
And probably because of this usage it uses value from protected override void OnConfiguring method instead from its own appsettings.json ?
So how could I read connection string for this console application only from itself appsettings.json
(to remove/avoid using hardcoded value) from context.
Edit:
There is no GetConnectionString option:
Thanks a lot!
Cheers
One option I can suggest is to create another DBContextClass in your console application and just override the OnConfiguring method to read from appsettings of console application project
Example:
New Class in Console Application
public class ConsoleProductXmlDBContext : ProductXmlDBContext
{
private readonly IConfiguration _iConfiguration;
private readonly string _connectionString;
public ConsoleProductXmlDBContext()
{
IConfigurationBuilder builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
_iConfiguration = builder.Build();
_connectionString = _iConfiguration.GetConnectionString("DefaultConnection");
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
_ = optionsBuilder.UseSqlServer(_connectionString, providerOptions => providerOptions.CommandTimeout(60))
.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
}
}
Usage in Console Application
using (var context = new ConsoleProductXmlDBContext())
{
companies = context.Companies.ToList();
}
Check the documentation here: https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.dbcontext.onconfiguring?view=efcore-5.0
In situations where an instance of DbContextOptions may or may not have been passed to the constructor, you can use IsConfigured to determine if the options have already been set, and skip some or all of the logic in OnConfiguring(DbContextOptionsBuilder).
So you can check the IsConfigured value and do nothing if options have been provided already.
The proper solution though, would be to have the overridden function read from the configuration directly, if there is no reason to override it at the first place.
https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/?view=aspnetcore-5.0#publish-to-a-folder The published folder will contain the proper configuration file, depending on which application you published.

Can't read data from config.json in .NET Core 2 console application

I have the following code.
IConfigurationRoot config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("config.json", true, true)
.Build();
string beep = config.GetSection("beep").Value;
My config.json looks like this.
{ "beep": "bopp" }
When I hit a breakpoint I can see that there's one provider but the data in it is of zero length. I've tried different approaches as config["beep"] etc. but sems to fail to get the value in. It's null all the time. I'm trying to follow the docs but must be missing something.
Make sure the json file is set to copy to directory as discussed in this blog. Otherwise when you build or debug the configuration file won’t be included. Make sure you change it in the properties.
The reason you are not able to access your configuration, is because the IConfigurationRoot does not reference the dependency for ConfigurationBuilder. To ensure that your configuration content will load, would be to do something along these lines:
public static class ConfigurationProvider
{
public static IConfiguration BuildConfiguration => new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", true, true)
.Build();
}
The above will build our configuration, now we should use the configuration.
public static class ServiceProvider
{
public static IServiceProvider BuildServiceProvider(IServiceCollection services) => services
.BuildDependencies()
.BuildServiceProvider();
}
Once we have defined our provider, we can do the following so we can pass our IConfiguration around the application to access the objects.
var serviceCollection = new ServiceCollection()
.AddSingleton(configuration => ConfigurationProvider.BuildConfiguration());
var serviceProvider = ServiceProvider.BuildServiceProvider(serviceCollection);
Then inside of another class, you would have the following:
public class SampleContext : ISampleRepository
{
private readonly string dbConection;
public SampleContext(IConfiguration configuration) => configuration.GetConnectionString("dbConnection");
...
}
Then our appsettings.json would look like this:
{
"ConnectionStrings": {
"dbConnection": "..."
}
}
The above is the correct format for a json object. If you have a nested object along these lines:
{
"Sample" : {
"Api" : {
"SampleGet" : "..."
}
}
}
Then your C# would be:
configuration.GetSection("Sample:Api")["SampleGet"];
The above is based on the assumption your configuration and usage are not in the same sequential area, ie directly in your main. Also you should use appsettings.json since that is the default, less extra wiring if I remember correctly. Your json also needs to be correctly formatted.
But that will definately work, if you need more help let me know and I can send you some sample console core applications to demonstrate usage.
I feel like you are just missing the name for the object.
Try adding a name for the object in the config.json like so:
{"beep":{"beep":"bopp"}}
Then you can do string beep =config.GetSection("beep").Value

Append test project appSettings to ASP.NET Core integration tests

I'm creating ASP.NET Core integration tests (xUnit based) following these docs. I want to start the test web server with its own appsettings.json. My abbreviated folder structure is:
\SampleAspNetWithEfCore
\SampleAspNetWithEfCore\SampleAspNetWithEfCore.csproj
\SampleAspNetWithEfCore\Startup.cs
\SampleAspNetWithEfCore\appsettings.json
\SampleAspNetWithEfCore\Controllers\*
\SampleAspNetWithEfCore.Tests\SampleAspNetWithEfCore.Tests.csproj
\SampleAspNetWithEfCore.Tests\IntegrationTests.cs
\SampleAspNetWithEfCore.Tests\appsettings.json
then I have these utilities:
public static class ServicesExtensions
{
public static T AddOptions<T>(this IServiceCollection services, IConfigurationSection section)
where T : class, new()
{
services.Configure<T>(section);
services.AddSingleton(provider => provider.GetRequiredService<IOptions<T>>().Value);
return section.Get<T>();
}
}
and inside Startup.cs ConfigureServices(...) I do this:
services.AddOptions<SystemOptions>(Configuration.GetSection("System"));
Referring to the appsettings.json section like this:
"System": {
"PingMessageSuffix": " suffix-from-actual-project"
}
So far so good: this is picked up in a strongly typed manner. My controller gets a SystemOptions instance that mirrors the json structure, and the controller uses the suffix correctly.
The problems are with building the Integration Tests WebHost. I want to run the Startup from my real project as is, with its own appsettings.json settings, but as an extra layer of settings I want the appsettings.json from my test csproj to be added, overriding any settings if applicable. This is my appsettings from the test project:
"System": {
"PingMessageSuffix": " suffix-from-test-appsettings"
}
Here's what I've tried:
public class CustomWebApplicationFactory : WebApplicationFactory<Startup>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder
.UseStartup<Startup>()
.ConfigureAppConfiguration(config => config
.AddJsonFile("appsettings.json")
);
}
}
However, this doesn't work. If I hit a breakpoint in my controller I see only the settings from the base project. The controller just echo's the config value currently, and logically the return result is also not as expected.
The documentation doesn't mention "appsettings" anywhere on the page.
Bottom line: How can you add a layer of appSettings from a test project's appsettings.json file when running ASP.NET Core integration tests?
Solved it like this:
For appsettings.json in the Test project set the Properties:
Build Action to Content
Copy to Output Directory to Copy if newer
Use a custom WebApplicationFactory like so:
public class CustomWebApplicationFactory : WebApplicationFactory<Startup>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
// Note: ↓↓↓↓
builder.ConfigureTestServices(services =>
services.AddOptions<SystemOptions>(configuration.GetSection("System"))
);
}
}
And voila: it works!
The first step is needed to make the ConfigurationBuilder find your json file easily. The second step subtly uses a ...TestServices configuration (if you use the regular ConfigureServices method it'll be called before the Startup's service configuration and get overwritten).
Footnote: commenters (on the question) have mentioned it might be better to have a appsettings.ci.json file in the SUT project, and control things by environment (which you'd set via launch settings or via the WebHostBuilder). The documentation links to a few closed GitHub issues that suggest the same thing: 8712, 9060, 7153. Depending on your scenario and taste, that might be a better or more idiomatic solution.
Update Feb 2020 - ASP.NET Core 3.0 and above
The way you do this has changed, you need to use the ConfigureAppConfiguration delegate.
public class HomeControllerTests : IClassFixture<WebApplicationFactory<Startup>>
{
private readonly WebApplicationFactory<Startup> _factory;
public HomeControllerTests(WebApplicationFactory<Startup> factory)
{
var projectDir = Directory.GetCurrentDirectory();
var configPath = Path.Combine(projectDir, "appsettings.json");
//New ↓↓↓
_factory = factory.WithWebHostBuilder(builder =>
{
builder.ConfigureAppConfiguration((context,conf) =>
{
conf.AddJsonFile(configPath);
});
});
}
}
Credit to: https://gunnarpeipman.com/aspnet-core-integration-tests-appsettings/
Both Unit test projects and integration test projects will require their own appsettings. We call ours appsettings.Test.json
Then we use the Microsoft.Extensions.Configuration to access them. So typically in a test I will do something like:
private readonly IConfiguration _configuration;
public HomeControllerTests()
{
_configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.Test.json")
.Build();
}
then a reference to a value in this file by:
_configuration["user:emailAddress"]
where the file looks like:
"user": { "emailAddress": "myemail.com", …...
For what you are trying to do you will probably need to create an appsettings.test.json file similar to mine that sits alongside your main apsettings file. You'll then want to test the environment and then add the appropritate appsettings file.

custom keys in appSettings.json not working in ASP.NET Core 2.0

I added a CustomSettings section keys in appSettings.json in ASP.NET Core project:
{
"ConnectionStrings": {
"DefaultConnectionString": "Data Source=..."
},
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
},
"CustomSettings": {
"Culture": "es-CO"
}
}
I've not been able to load Culture key in following controller:
public AccountController(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager,
IEmailSender emailSender,
ILogger<AccountController> logger,
IConfiguration configuration)
{
Response.Cookies.Append(
CookieRequestCultureProvider.DefaultCookieName,
CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(configuration.GetSection("CustomSettings")["Culture"])),
new CookieOptions { Expires = DateTimeOffset.UtcNow.AddYears(1) }
);
}
No matter if I do following, always they return NULL:
configuration.GetSection("CustomSettings")["Culture"];
configuration.GetSection("CustomSettings").GetValue("Culture");
I tried help based in ASP.NET Core: Step by Step Guide to Access appsettings.json in web project and class library and I've created CustomSettings class with string Culture property and injecting in Startup as follows:
// Load Custom Configuration from AppSettings.json
services.Configure<Models.CustomSettings>(Configuration.GetSection("CustomSettings"));
Accesing by inject IOptions customSettings, the value of
customSettings.Value.Culture returns NULL.
First Question: ¿What am I doing wrong or what is missing?
Second Question: ¿Why doing following in Index of HomeController throws an exception?
public class HomeController : Controller
{
public IActionResult Index(IConfiguration configuration)
{
}
}
Exception:
An unhandled exception occurred while processing the request.
InvalidOperationException: Could not create an instance of type 'Microsoft.Extensions.Options.IOptions`1[[OmniMerchant.Models.CustomSettings, OmniMerchant, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. Model bound complex types must not be abstract or value types and must have a parameterless constructor.
Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ComplexTypeModelBinder.CreateModel(ModelBindingContext bindingContext)
Third Question: I need to set Culture from Starting for all the app in background based on Culture property on appSettings.json, I read MSDN documentation, but I've not been able to achieve that, ¿How can I achieve this?
Thanks
First create the modal that matches the appsetting section
public class CustomSettings
{
public string Culture { get; set; }
}
Then register it in the ConfigureServices method in Startup.cs
services.Configure<CustomSettings>(Configuration.GetSection("CustomSettings"));
Then inject it using IOptions where its needed
AccountController(IOptions<CustomSettings> settings)
{
_settings = settings.Value;
}
Why configuration section values are null?
By default there are two config files. For Release build and one for Debug. Have you checked that you actually editing the correct one (probably appsettings.Development.json)
Why DI is not working.
In .NET Core basically you can use DI in two ways. By injecting it in constructor or directly in method. In the second option you have to use special attribute [FromServices]
In your application properties -> Debug section -> Environment variables
If this is set
ASPNETCORE_ENVIRONMENT: Development
It will use appsettings.Development.json
TL:DR; Your appsettings.json file needs to be in the same working directory as your dll file.
You need to make sure that you are running the app from it's working directory.
For example, if your dll's are built to the bin folder, the following won't work:
cd bin
dotnet run app.dll
Because the appsettings.json file is not in the same folder as the dll that you are running from.
However if you are in the directory that the appsettings.json file is in, the current working directory will be set and that file will be read.
dotnet run bin/app.dll
If using VS Code launch.json you can set the cwd property to achieve this.
I had problems reading from different configuration files until I added each of them specifically in the Startup constructor like below:
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json") //***
.AddJsonFile("appsettings.development.json") //***
.AddEnvironmentVariables();
Configuration = builder.Build();
}
This is a bug I think but probably you can solve it by setting "HostingEnvironment.ContentRootPath" manualy.
Try to add the following code in the Startup method in Startup.cs:
if (env.EnvironmentName== "Production")
{
env.ContentRootPath = System.IO.Directory.GetCurrentDirectory();
}
or hardcode path like this:
if (env.EnvironmentName == "Production")
{
env.ContentRootPath = "/var/aspnetcore/...";
}
For example if your files located in "/var/aspnetcore/my_ASP_app", the startup method should be something like this:
public Startup(IHostingEnvironment env)
{
if (env.EnvironmentName== "Production")
{
//env.ContentRootPath = "/var/aspnetcore/my_ASP_app";
env.ContentRootPath = System.IO.Directory.GetCurrentDirectory();
}
//env.ContentRootPath = System.IO.Directory.GetCurrentDirectory();//you can write this line outside of IF block.
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile("appsettings.Production.json", optional: true, reloadOnChange: true);
Configuration = builder.Build();
}
It is better to use Directory.GetCurrentDirectory() instead of hardcoding.
This worked for me in Linux Ubuntu with nginx, but I don't know if it is applicable for your enviornment.

Categories

Resources