Can't find host.json - azure

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

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.

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

ASP.NET Core How to read the content of an AzureTable

I develop a ASP.NET Core application working with Azure Tables.
So, I created a tables storage account in Azure Portal, created a table, filled it with some test data, and now I would like to display the content of that table to test the reading.
my appsettings.json is
{
"ConnectionStrings": {
"MyTables":"DefaultEndpointsProtocol=https;AccountName=yyy;AccountKey=xxx;EndpointSuffix=core.windows.net"
},
"Logging": {
"IncludeScopes": false,
[etc etc...]
}
}
And my Startup.cs:
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
// here in debug we can see the connection string, that is OK
Console.WriteLine($"conn string:{Configuration["ConnectionStrings:MyTables"]}");
}
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
}
And here is my controller I try to Display the values:
using Microsoft.AspNetCore.Mvc;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Table;
using NextMove.Models;
using System.Text;
[...]
public class HelloWorldController : Controller
{
public string ReadTables() {
// ????? Code does not work, as Startup not a reference
string myConnString = Startup.Configuration["ConnectionStrings:MyTables"];
//////////////////////////////////
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(myConnString);
CloudTableClient tableClient = storageAccount.CreateCloudTableClient();
CloudTable table = tableClient.GetTableReference("themes");
TableQuery<ProjectThemeEntity> query = new TableQuery<ProjectThemeEntity>().Where(TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, "fr"));
StringBuilder response = new StringBuilder("Here is your test table:");
foreach (ProjectThemeEntity item in table.ExecuteQuery(query)) {
response.AppendLine($"Key: {item.RowKey}; Value: {item.Description}");
}
return response.ToString();
}
//
// GET: /HelloWorld/
public IActionResult Index() {
return View();
}
Questions:
a) How to fix this code in order to get the connection string?
b) There should be a "Table.ExecuteQuery(query)" as per this MSDN article in the controller's foreach, but it does not find such a method in CloudTable class, I however added the necessary references, as shown in the controller's code above, only two "Async" methods are available:
PS.
-For the (b) question several people has the same issue here, hope the situation changed now...
You can't access Startup.Configuration from the controller because it's not a static property. Even though you've made it public (generally not a good idea) it still requires you to have an instance of Startup to get access to it.
Generally to get access to settings in ASP.NET Core it's best to create a class with the properties you want and use the IOptions pattern to get them with Dependency Injection. In your startup where you configure your services (add services to the dependency injection container) you would use the helper methods to add your configuration object to the container and then in your controller you would specify you wanted an IOptions or IOptionsSnapshot to get access to it.
I'd suggest you don't put your data access in your controller though. It makes your controller harder to read and harder to maintain if you need to change your strategy later. Move your ReadTables method to its own class and add it to the DI container in Startup taking whatever settings you need to create the service. Use constructor injection in your controller to get the service and execute calls from your controller actions where you need them.

Resources