Azure Function not binding to appsettings when deployed - azure

I have an Azure Function app written in C# with Visual Studio using version 3.0.9 of Functions SDK. In the same directory as the csproj file and the host.json, I have an appsettings.json file with the following content:
{
"test-queue": "test,
"myOptions": {
"batchSize": 5000
}
}
The function works fine when I run it locally but for some reason it doesn't seem to bind to the appsettings file when it is deployed to Azure. I use the following startup class:
public class Startup : FunctionsStartup
{
public override void ConfigureAppConfiguration(IFunctionsConfigurationBuilder builder)
{
builder.ConfigurationBuilder
.SetBasePath(Environment.CurrentDirectory)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables();
var builtConfig = builder.ConfigurationBuilder.Build();
var keyVaultName = builtConfig["AzureKeyVaultName"];
if (!string.IsNullOrEmpty(keyVaultName))
{
var azureServiceTokenProvider = new AzureServiceTokenProvider();
var keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));
builder.ConfigurationBuilder.AddAzureKeyVault($"https://{keyVaultName}.vault.azure.net/");
}
builder.ConfigurationBuilder
.AddJsonFile("local.settings.json", true)
.AddEnvironmentVariables()
.Build();
}
public override void Configure(IFunctionsHostBuilder builder)
{
FunctionsHostBuilderContext context = builder.GetContext();
builder.Services.AddOptions<MyOptions>()
.Configure<IConfiguration>((settings, configuration) =>
{
configuration.GetSection("myOptions").Bind(settings);
});
}
}
I have a function class like this:
public class MyFunction
{
private readonly MyOptions options;
public MyFunction(IOptions<MyOptions> options)
{
this.options = options.Value;
}
[FunctionName("Test")]
public async Task Run(
[QueueTrigger("%test-queue%")] MyParameters parameters,
ILogger log)
{
log.LogInformation($"Batch size: {options.BatchSize}");
}
}
The trouble is, the function doesn't seem to be using the appsettings file. I get an InvalidOperationException on startup saying '%test-queue%' does not resolve to a value." I can get rid of the queue binding and hard code the queue name. That makes the funtion run Okay but then the log file says the batch size is 0 instead of 5000.
Again, it works when running locally but not when it is deployed. The optional flag is false when registering the appsettings file in the startup so it must be getting found.

I've got a partial solution by replacing
builder.ConfigurationBuilder
.SetBasePath(Environment.CurrentDirectory)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables();
with
FunctionsHostBuilderContext context = builder.GetContext();
builder.ConfigurationBuilder
.AddJsonFile(Path.Combine(context.ApplicationRootPath, "appsettings.json"), optional: false, reloadOnChange: false)
.AddEnvironmentVariables();
It is still making me hard code the queue names but batchSize variable is getting set at least.

Related

Cannot setup configuration in a Isolated Azure Function - .NET 7

I am upgrading my function from .NET Core 3.1 up to .NET 7 Isolated
My Function App inherits from a base class which does all my setup that is relevant to all function app. This works perfectly
However, in .NET 7 Isolated, it appears as though function startup is not supported/recommended?
I can create a function initializer class which I can then call to setup my services, this is fine
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults((IFunctionsWorkerApplicationBuilder builder) =>
{
})
.ConfigureServices((context, s) =>
{
var initializer = new FunctionAppInitializer(s);
initializer.Run();
})
.Build();
host.Run();
However, I have a problem with configuration as this is not available.
How can I run the method below?
public override void ConfigureAppConfiguration(IFunctionsConfigurationBuilder builder)
{
var kvEndpoint =
Environment.GetEnvironmentVariable("ASPNETCORE_HOSTINGSTARTUP__KEYVAULT__CONFIGURATIONVAULT");
var environmentName =
Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
builder.ConfigurationBuilder
.AddAzureKeyVault(new Uri(kvEndpoint!), new DefaultAzureCredential())
.SetBasePath(Environment.CurrentDirectory)
.AddJsonFile("local.settings.json", optional: true)
.AddJsonFile($"local.settings.{environmentName}.json", optional: true)
.AddEnvironmentVariables()
.Build();
}
This is overriding FunctionStartup.ConfigureAppConfiguration which gives me access to the builder.
With the new method, although I can get to builder inside ConfigureFunctionsWorkerDefaults, the builder object does not have ConfigurationBuilder
Paul
You can use ConfigureHostConfiguration method
var host = new HostBuilder()
.ConfigureHostConfiguration(configHost =>
{
configHost.AddEnvironmentVariables(prefix: "DOTNET_");
configHost.AddCommandLine(args);
configHost.AddJsonFile("local.settings.json", optional: true, reloadOnChange: true);
})

Asp.net core: How to use appSettings for a particular environment

In .NET 6.0 WebApi application
I have the following two files:
appSettings.json
appSettings.Linux.json
I want the application to use appSettings.Linux.json when running in linux environment. The following is the code that I have tried so far but unfortunately it is not working. It is still accessing appSettings.json file while running in linux environment.
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
IConfigurationBuilder builder = new ConfigurationBuilder()
.AddJsonFile("appSettings.Linux.json" , optional: true, reloadOnChange: true);
builder.Build();
}
I am adding a new builder as follows
in .NET 6, you can do like this.
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
builder.Environment.EnvironmentName = "Linux";
builder.Configuration.AddJsonFile("appSettings.Linux.json");
}
and then it will override all the configuration values.
appsettings.Linux.json
accessing value from Configuration
Updated answer based on feedback.
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(config =>
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
config.AddJsonFile("appSettings.Linux.json", optional: true, reloadOnChange: true);
}
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});

Can't find host.json

I created a new azure function, and I have a step to read host.json, it worked on my desk, but when I publish to Azure, I got an error:
The configuration file 'host.json' was not found and is not optional.
The physical path is 'D:\Program Files
(x86)\SiteExtensions\Functions\2.0.12507\32bit\host.json'.
Here what I tried:
var Configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("host.json",false)
.Build();
var value = Configuration["value"];
So how can I fix that?
As Volodymyr mentioned, you need to pass the ExecutionContext within your Azure Function method:.
public static async Task<IActionResult> Run( ... , ExecutionContext context)
{
...
}
Now when you build your configuration, you set the base path using the ExecutionContext.FunctionAppDirectory property. I also optional add the local.settings.json for local debug purpose:
var config = new ConfigurationBuilder()
.SetBasePath(context.FunctionAppDirectory) // Here you include the app directory from the context
.AddJsonFile("host.json", optional: false, reloadOnChange: true)
.AddJsonFile("local.settings.json", optional: true, reloadOnChange: true) // for local debugging
.AddEnvironmentVariables()
.Build();
To further improve your code I would recommend creating a class for your Settings. For example:
public sealed class FunctionSettings
{
public string MySetting { get; set; }
}
This way you can access the settings like this:
var settings = new FunctionSettings();
config.Bind(settings);
var value = settings.MySetting
instead of
var value = Configuration["MySetting"];
You need to use ExecutionContext it has property FunctionAppDirectory

Serilog: azure webjob logging doesn't seem to work when hosted in azure?

I have azure webjob sdk (v3.0.3) app which has been configured to use serilog for logging.
The log seems to work when I run the app locally in my system. Below is the configuration:
static void Main(string[] args)
{
try
{
var builder = new HostBuilder()
.ConfigureAppConfiguration(SetupConfiguration)
.ConfigureLogging(SetupLogging)
.ConfigureServices(SetupServices)
.ConfigureWebJobs(webJobConfiguration =>
{
webJobConfiguration.AddTimers();
webJobConfiguration.AddAzureStorageCoreServices(); //this is to store logs in azure storage
})
.UseSerilog()
.Build();
builder.Run();
}
}
The code for SetupConfiguration is below:
private static void SetupConfiguration(HostBuilderContext hostingContext, IConfigurationBuilder builder)
{
var env = hostingContext.HostingEnvironment;
_configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables()
.Build();
}
The code for setting up services:
private static void SetupServices(HostBuilderContext hostingContext, IServiceCollection serviceCollection)
{
serviceCollection.AddSingleton<IConfiguration>(_configuration);
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(_configuration)
.CreateLogger();
_logger = serviceCollection.BuildServiceProvider().GetRequiredService<ILoggerFactory>().CreateLogger("test");
}
The logging is setup as following:
private static void SetupLogging(HostBuilderContext hostingContext, ILoggingBuilder loggingBuilder)
{
loggingBuilder.SetMinimumLevel(LogLevel.Information);
loggingBuilder.AddConsole();
loggingBuilder.AddDebug();
loggingBuilder.AddSerilog(dispose: true);
}
In my TimerTrigger method I'm using the logger:
[Singleton]
public async static Task Trigger([TimerTrigger("%Job%")]TimerInfo myTimer)
{
_logger.LogInformation($"From Trigger {DateTime.UtcNow.ToString()}");
}
In appSettings.json, serilog is configured as follows:
"Serilog": {
"MinimumLevel": "Information",
"WriteTo": [
{
"Name": "RollingFile",
"Args": {
"pathFormat": ".\\Log\\log-{Date}.txt",
"retainedFileCountLimit": 7,
"fileSizeLimitBytes": 5000000,
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} {EventId} [{Level}] [{Properties}] {Message}{NewLine}{Exception}"
}
}
]
}
the folder "Log" and the log files get created when i run the app locally. But when I publish the webjob, the "Log" folder or the log file is not created in the "app_data" folder of webjob. Can anyone help me figureout how to configure serilog to make it work with webjobs?
Following are the nuget packages used:
If you want to use serilog in WebJob , you need to install this package Serilog.Extensions.WebJobs. Then after configuring the serilog, you would be able to use it.
You must inject the ILogger rather than using the global Log.Logger otherwise the log messages will not be written to the Microsoft Azure WebJobs dashboard.
About the detailed description about how to configure and use serilog, you could refer to this doc.
Hope this could help you, if you still have other questions, please let me know.

How to specify AzureWebJobsStorage in latest azure webjob 3.03

I updated my old azure webjob code to package to 3.03 and then it is just not working.
I managed to fix all compile-time errors, but when running locally, it throws this error:
Microsoft.Azure.WebJobs.Host.Indexers.FunctionIndexingException
HResult=0x80131500
Message=Error indexing method 'MvQueueProcessorV2.ProcessMVRequest'
Source=Microsoft.Azure.WebJobs.Host
StackTrace:
at Microsoft.Azure.WebJobs.Host.RecoverableException.TryRecover(ILogger logger) in C:\projects\azure-webjobs-sdk-rqm4t\src\Microsoft.Azure.WebJobs.Host\Exceptions\RecoverableException.cs:line 81
at FE.Toolkit.MvPaaS.WebJob.Program.<Main>(String[] args)
Inner Exception 1:
InvalidOperationException: Storage account 'Storage' is not configured.
For me, this seems to indicate that it can't find setting AzureWebJobsStorage? However, it sleeps nicely in the app.config file. So I assumed that I should put my connection string to appsettings.json, so this is what I did in my appsettings.json:
{
"ConnectionStrings": {
"AzureWebJobsDashboard": "xxx",
"Storage": "yyy"
}
}
However, it gives me the same error. So how do I set storage for webjob 3.0?
This is my code in program.cs
var builder = new HostBuilder()
.UseEnvironment("Development")
.ConfigureWebJobs(b =>
{
b.AddAzureStorageCoreServices()
.AddAzureStorage()
.AddTimers()
.AddFiles()
.AddDashboardLogging();
})
.ConfigureLogging((context, b) =>
{
b.SetMinimumLevel(LogLevel.Debug);
b.AddConsole();
})
.ConfigureServices(services =>
{
services.AddSingleton<INameResolver, ConfigNameResolver>();
})
.UseConsoleLifetime();
Please add this line of code in your program.cs:
.ConfigureAppConfiguration((context, config) => {
config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
})
I have tested at my side, and works fine.
code in Program.cs:
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace WebJob1template
{
class Program
{
static void Main()
{
var builder = new HostBuilder()
.UseEnvironment("Development")
.ConfigureAppConfiguration((context, config) => {
config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
})
.ConfigureWebJobs(
b =>
{
b.AddAzureStorageCoreServices()
.AddAzureStorage()
.AddTimers()
.AddFiles();
//.AddDashboardLogging();
})
.ConfigureLogging((context, b) =>
{
b.SetMinimumLevel(LogLevel.Debug);
b.AddConsole();
})
.UseConsoleLifetime();
var host = builder.Build();
using (host)
{
host.Run();
}
}
}
}
appsettings.json(note that set it's property "Copy to Output Directory" as Copy always):
{
"ConnectionStrings": {
"AzureWebJobsDashboard": "xxxx",
"AzureWebJobsStorage": "xxxx"
}
}
Function.cs:
using Microsoft.Azure.WebJobs;
using Microsoft.Extensions.Logging;
namespace WebJob1template
{
public class Functions
{
public static void ProcessQueueMessage([QueueTrigger("queue")] string message, ILogger log)
{
//log.WriteLine(message);
log.LogInformation(message);
}
}
}
The test result:
I ran into the same problem and here is my solution.
Below are the Azure assemblies that I am referring to
With above setup, there is no need to invoke c.AddJsonFile("appsettings.json", ... ) if name of config file is 'appSettings.json'
By default, framework will look for the file named 'appsettings.json'
However, if name of file is something else the we need to tell HostBuilder what is name of our config file.
HostBuilder builder = new HostBuilder();
//Below piece of code is not required if name of json file is 'appsettings.json'
builder.ConfigureAppConfiguration(c =>
{
c.AddJsonFile("Myappsettings.json", optional: false, reloadOnChange: true);
});
Simple and important step that cause problem while debugging on my local machine was 'Copy to Output Directory'property of my 'appsettings.json' file. #Ivan Yang has already mentioned this in his answer.
Here is the git hub link of the source code.
Note: I have followed this documentation to implement the code base.

Resources