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

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);
})

Related

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>();
});

How to retrieve Azure App Configuration Service settings while in Program.cs

I am using Asp.NET Core 5 (v5.0.6) web API deployed as an Azure App Service and I am migrating from using appsettings.json in the API to integrating with Azure App Configuration service. I have the Azure App Configuration Service set up but the issue I am having is how to access values in my Azure App Configuration service for retrieving a connection string for database access while I am still in Program.cs.
Note that in the CreateHostBuilder() method I am examining the context.HostingEnvironment.IsDevelopment() environment variable and if it is "IsDevelopment", implying a local DEV, I am reading an Azure App Configuration Service connection string via User Secrets but if it is not a local DEV, than I rely on the Managed Identity and just pass in the endpoint value from appsettings.json.
The only values I want to get that are not in Azure App Configuration Service is the local DEV Azure App Configuration Service Connection string (from User Secrets) and the Azure App Configuration Service endpoint from Appsettings.json. All other settings should come from Azure App Configuration Service.
The problem I am trying to solve is how to access the values in Azure App Configuration Service, while still in Program.cs, to retrieve the connection string for access to the Azure SQL database I am using for logging.
In the code below, when I link the Azure App Configuration in the CreateHostBuilderMethod and call build, I expected the values in Azure App Configuration Service to then be available via the static Configuration property. However when I try to retrieve the connection string value, it is always null.
How can I correctly retrieve the values for properties in Azure App Configuration Service to use them in Program.cs?
Here is my Program.cs file;
public class Program
{
public static IConfiguration Configuration { get; } = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true)
.AddUserSecrets<Startup>()
.Build();
public static void Main(string[] args)
{
var host = CreateHostBuilder(args)
.Build();
var connectionString = Configuration["CoreApi:Settings:LoggingDb"]; //<-- This returns null
const string tableName = "ApiLogs";
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Information()
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.Filter.ByExcluding(Matching.FromSource("Microsoft.EntityFrameworkCore.Query"))
.WriteTo.MSSqlServer(
connectionString: connectionString,
sinkOptions: new MSSqlServerSinkOptions { TableName = tableName })
.CreateLogger();
// TODO Enable to debug any startup Serilog issues. Make sure to comment out for PROD
// Serilog.Debugging.SelfLog.Enable(msg =>
// {
// Debug.Print(msg);
// Debugger.Break();
// });
//var host = CreateHostBuilder(args)
// .Build();
try
{
Log.Information("Starting ConfirmDelivery API");
host.Run();
}
catch (Exception ex)
{
Log.Fatal(ex, "ConfirmDelivery API Host terminated unexpectedly");
}
finally
{
Log.CloseAndFlush();
}
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, config) =>
{
var settings = config.Build();
if (context.HostingEnvironment.IsDevelopment())
{
var connectionString = settings.GetConnectionString("AzureAppConfiguration");
config.AddAzureAppConfiguration(connectionString);
}
else
{
var endpoint = settings["AppConfigEndpoint"];
var credentials = new ManagedIdentityCredential();
config.AddAzureAppConfiguration(options =>
{
options.Connect(new Uri(endpoint), credentials);
});
}
}).ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
Try the code below to get a config value:
using System;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.AzureAppConfiguration;
namespace myapp
{
class Program
{
static void Main(string[] args)
{
var configItemName = "";
var appConfigConnectionString = "";
var builder = new ConfigurationBuilder();
builder.AddAzureAppConfiguration(appConfigConnectionString);
var config = builder.Build();
Console.WriteLine(config[configItemName]);
}
}
}
Result :

Azure Function not binding to appsettings when deployed

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.

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

Self-hosting MVC6 app

I'm trying to get an MVC6 app to be self-hosted for testing. I can do in-memory testing using TestServer, but for testing integration of multiple web apps, one of which includes a middleware that I have no control over that connects to the other app, I need at least one of the apps to be accessible over TCP.
I have tried using WebApp.Start, but it works with an IAppBuilder rather than IApplicationBuilder, so I can't get it to work with my Startup.
Is there any way to get an MVC6 app to be self-hosted in an xUnit test, via OWIN or any other way?
UPDATE:
FWIW, based on Pinpoint's answer and some additional research, I was able to come up with the following base class that works in xUnit, at least when the tests are in the same project as the MVC project:
public class WebTestBase : IDisposable
{
private IDisposable webHost;
public WebTestBase()
{
var env = CallContextServiceLocator.Locator.ServiceProvider.GetRequiredService<IApplicationEnvironment>();
var builder = new ConfigurationBuilder(env.ApplicationBasePath)
.AddIniFile("hosting.ini");
var config = builder.Build();
webHost = new WebHostBuilder(CallContextServiceLocator.Locator.ServiceProvider, config)
.UseEnvironment("Development")
.UseServer("Microsoft.AspNet.Server.WebListener")
.Build()
.Start();
}
public void Dispose()
{
webHost.Dispose();
}
}
Katana's WebApp static class has been replaced by WebHostBuilder, that offers a much more flexible approach: https://github.com/aspnet/Hosting/blob/dev/src/Microsoft.AspNet.Hosting/WebHostBuilder.cs.
You've probably already used this API without realizing it, as it's the component used by the hosting block when you register a new web command in your project.json (e.g Microsoft.AspNet.Hosting server=Microsoft.AspNet.Server.WebListener server.urls=http://localhost:54540) and run it using dnx (e.g dnx . web):
namespace Microsoft.AspNet.Hosting
{
public class Program
{
private const string HostingIniFile = "Microsoft.AspNet.Hosting.ini";
private const string ConfigFileKey = "config";
private readonly IServiceProvider _serviceProvider;
public Program(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public void Main(string[] args)
{
// Allow the location of the ini file to be specified via a --config command line arg
var tempBuilder = new ConfigurationBuilder().AddCommandLine(args);
var tempConfig = tempBuilder.Build();
var configFilePath = tempConfig[ConfigFileKey] ?? HostingIniFile;
var appBasePath = _serviceProvider.GetRequiredService<IApplicationEnvironment>().ApplicationBasePath;
var builder = new ConfigurationBuilder(appBasePath);
builder.AddIniFile(configFilePath, optional: true);
builder.AddEnvironmentVariables();
builder.AddCommandLine(args);
var config = builder.Build();
var host = new WebHostBuilder(_serviceProvider, config).Build();
using (host.Start())
{
Console.WriteLine("Started");
var appShutdownService = host.ApplicationServices.GetRequiredService<IApplicationShutdown>();
Console.CancelKeyPress += (sender, eventArgs) =>
{
appShutdownService.RequestShutdown();
// Don't terminate the process immediately, wait for the Main thread to exit gracefully.
eventArgs.Cancel = true;
};
appShutdownService.ShutdownRequested.WaitHandle.WaitOne();
}
}
}
}
https://github.com/aspnet/Hosting/blob/dev/src/Microsoft.AspNet.Hosting/Program.cs
You can use Microsoft.AspNet.TestHost
See http://www.strathweb.com/2015/05/integration-testing-asp-net-5-asp-net-mvc-6-applications/ for details on use.
TestHost can work with your startup using a line like
TestServer dataServer = new TestServer(TestServer.CreateBuilder().UseStartup<WebData.Startup>());
where is the name of the application. The application has to be referenced in the test harness

Resources