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

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

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

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.

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.

Enable file logging on IIS for serilog

I want to enable file logging on IIS to resolve an issue locally on IIS. Following is the code I am using. It works when I run on visual studio and logs to a file but not when I deployed to IIS. Folder has enough permission to create a file and also I created the folder and file. What am I missing here ?
Program.cs
var path = #"C:\workspace\Logs\Log-{Date}.txt";
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Verbose()
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
.Enrich.FromLogContext()
.WriteTo.Console(
outputTemplate:
"[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}",
theme: AnsiConsoleTheme.Literate)
.WriteTo.File(path, fileSizeLimitBytes: 1_000_000,
rollOnFileSizeLimit: true,
shared: true,
flushToDiskInterval: TimeSpan.FromSeconds(1))
.CreateLogger();
enable useSerialLog()
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureServices(services => services.AddAutofac())
.UseStartup<Startup>()
.UseSerilog()
.Build();
Serilog works perfectly on local. For IIS the log file folder need IIS User Permission.
public Startup(IHostingEnvironment env)
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel
.Information()
.WriteTo.RollingFile(#"C:\inetpub\serilog\agilogoservicelogs\{Date}.txt", LogEventLevel.Information)
.WriteTo.Seq("http://localhost:5341")
.CreateLogger();
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
Update your controller :
public class ValuesController : Controller
{
private readonly ILogger<ScrumUserController> _logger;
public ValuesController(ILogger<ScrumUserController> logger)
{
_logger = logger;
}
[HttpGet]
public IEnumerable<string> Get()
{
_logger.LogInformation("Agilibo Service start running");
return new string[] { "Agilibo Core", "Service" };
}
}
Now give folder permission on deployed IIS.
If you use .net core ,because default application pool identity:
ApplicationPoolIdentity has no write permission.
You should:
Change application pool user to Local System...
Or-
Give ApplicationPoolIdentity write permission:
ApplicationPoolIdentity is a virtual name , you can use "IIS AppPool\DefaultAppPool" set the permission.
you can use cli do the same work
ICACLS C:\sites\MyWebApp /grant "IIS AppPool\DefaultAppPool":F
Don't forget restart applicationpool and site.
From Microsoft Docs

Resources