Access hosting environment before building WebHost - asp.net-core

In my Program.cs Main method, I would like to read user secrets, configure a logger and then build the WebHost.
public static Main(string[] args)
{
string instrumentationKey = null; // read from UserSecrets
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.ApplicationInsightsEvents(instrumentationKey)
.CreateLogger();
BuildWebHost(args).Run();
}
I can get to the configuration by building my own, but I quickly end up in a mess trying to access hosting environment properties:
public static Main(string[] args)
{
var envName = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production";
var configBuilder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{envName}.json", optional: true, reloadOnChange: true);
// Add user secrets if env.IsDevelopment().
// Normally this convenience IsDevelopment method would be available
// from HostingEnvironmentExtensions I'm using a private method here.
if (IsDevelopment(envName))
{
string assemblyName = "<I would like the hosting environment here too>"; // e.g env.ApplicationName
var appAssembly = Assembly.Load(new AssemblyName(assemblyName));
if (appAssembly != null)
{
configBuilder.AddUserSecrets(appAssembly, optional: true); // finally, user secrets \o/
}
}
var config = configBuilder.Build();
string instrumentationKey = config["MySecretFromUserSecretsIfDevEnv"];
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.ApplicationInsightsEvents(instrumentationKey) // that.. escallated quickly
.CreateLogger();
// webHostBuilder.UseConfiguration(config) later on..
BuildWebHost(args, config).Run();
}
Is there an easier way to access IHostingEnvironment before building the WebHost?

In the main method, you cannot get the IHostingEnvironment instance before building the WebHost, as hosting is not yet created. And you cannot create a new valid instance properly, as it must be initialized using WebHostOptions`.
For application name you may use Assembly.GetEntryAssembly()?.GetName().Name
For environment name use what you currently do (I assume something like this is used in your IsDevelopment() method):
var environmentName = System.Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
bool isDevelopment = string.Equals(
"Development",
environmentName,
StringComparison.OrdinalIgnoreCase);
See, methods like IHostingEnvironment.IsDevelopment() are extension methods that simply do string comparison internally:
public static bool IsDevelopment(this IHostingEnvironment hostingEnvironment)
{
if (hostingEnvironment == null)
{
throw new ArgumentNullException(nameof(hostingEnvironment));
}
return hostingEnvironment.IsEnvironment(EnvironmentName.Development);
}
public static bool IsEnvironment(
this IHostingEnvironment hostingEnvironment,
string environmentName)
{
if (hostingEnvironment == null)
{
throw new ArgumentNullException(nameof(hostingEnvironment));
}
return string.Equals(
hostingEnvironment.EnvironmentName,
environmentName,
StringComparison.OrdinalIgnoreCase);
}
Note regarding AddJsonFile: as file name is sensitive in Unix OS, sometimes it's better to use $"appsettings.{envName.ToLower()}.json" instead of.

I wanted to do something similar. I wanted to set Kestrel listen options based on the environment. I just save the IHostingEnvironment to a local variable while I am configuring the apps configuration. Then later on I use that variable to make a decision based on the environment. You should be able to follow this pattern to accomplish your goal.
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args)
{
IHostingEnvironment env = null;
return WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.ConfigureAppConfiguration((hostingContext, config) =>
{
env = hostingContext.HostingEnvironment;
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
if (env.IsDevelopment())
{
var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
if (appAssembly != null)
{
config.AddUserSecrets(appAssembly, optional: true);
}
}
config.AddEnvironmentVariables();
if (args != null)
{
config.AddCommandLine(args);
}
})
.UseKestrel(options =>
{
if (env.IsDevelopment())
{
options.Listen(IPAddress.Loopback, 44321, listenOptions =>
{
listenOptions.UseHttps("testcert.pfx", "ordinary");
});
}
else
{
options.Listen(IPAddress.Loopback, 5000);
}
})
.Build();
}
}

In .Net Core 2.0 you can do like this
var webHostBuilder = WebHost.CreateDefaultBuilder(args);
var environment = webHostBuilder.GetSetting("environment");

Related

.Net 6 Azure Function App with use of Startup class

I have created an HTTP trigger-based .Net 6 Azure FunctionApp and trying to configure the database connection strings, other key values, and dependency injections for my service classes but, I don't know how to call my configure method of Startup.cs file from Program.cs main function. I am new to FunctionApp-based hosting.
I have tried with IHostBuilder like the following in the Program.cs file, but it says: "does not contain a definition for ConfigureWebHostDefaults" even used the namespace => using Microsoft.AspNetCore.Hosting;
public static void Main(string[] args)
{
var host = new HostBuilder().ConfigureFunctionsWorkerDefaults()
.Build();
host.Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
My Startup.cs file,
[assembly: FunctionsStartup(typeof(Startup))]
namespace kpi
{
public class Startup : FunctionsStartup
{
private static IConfiguration _configuration = null;
public override void Configure(IFunctionsHostBuilder builder)
{
var serviceProvider = builder.Services.BuildServiceProvider();
_configuration = serviceProvider.GetRequiredService<IConfiguration>();
var appSettingsSection = _configuration.GetSection("AppSetting");
builder.Services.Configure<AppSetting>(appSettingsSection);
var appSettings = appSettingsSection.Get<AppSetting>();
RuntimeConfig.appsettings = appSettings;
var ConnectionString = RuntimeConfig.appsettings.AppDBConnection;
builder.Services.AddDbContext<ShardingDbContext>(options =>
options.UseSqlServer(ConnectionString), ServiceLifetime.Transient);
}
}
}
I have used the FunctionStartup assembly, I don't know where I did go wrong, Can anyone help me to configure my connection strings from Startup.cs file in .Net6 Function App project?
You can refer below code to fix your issue. For more details, please read official doc.
Guide for running C# Azure Functions in an isolated process
1. Startup.cs file
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
[assembly: FunctionsStartup(typeof(triggerFunc.Startup))]
namespace triggerFunc
{
public class Startup : FunctionsStartup
{
private static IConfiguration _configuration = null;
public override void ConfigureAppConfiguration(IFunctionsConfigurationBuilder builder)
{
var context = builder.GetContext();
// optional: customize your configuration sources
// here, we add appsettings.json files
// Note that these files are not automatically copied on build or publish.
//builder.ConfigurationBuilder
// .AddJsonFile(Path.Combine(context.ApplicationRootPath, "appsettings.json"), optional: true, reloadOnChange: false)
// .AddJsonFile(Path.Combine(context.ApplicationRootPath, $"appsettings.{context.EnvironmentName}.json"), optional: true, reloadOnChange: false);
}
public override void Configure(IFunctionsHostBuilder builder)
{
// get the configuration from the builder
//var configuration = builder.GetContext().Configuration;
var serviceProvider = builder.Services.BuildServiceProvider();
_configuration = serviceProvider.GetRequiredService<IConfiguration>();
var appSettingsSection = _configuration.GetSection("AppSetting");
builder.Services.Configure<AppSetting>(appSettingsSection);
var appSettings = appSettingsSection.Get<AppSetting>();
RuntimeConfig.appsettings = appSettings;
var ConnectionString = RuntimeConfig.appsettings.AppDBConnection;
builder.Services.AddDbContext<ShardingDbContext>(options =>
options.UseSqlServer(ConnectionString), ServiceLifetime.Transient);
}
}
}
2. Program.cs file
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace triggerFunc
{
public class Program
{
public static async Task Main(string[] args)
{
var builder = Host
.CreateDefaultBuilder(args)
.ConfigureFunctionsWorkerDefaults()
.ConfigureAppConfiguration((hostingContext, configBuilder) =>
{
var env = hostingContext.HostingEnvironment;
;
})
.ConfigureServices((appBuilder, services) =>
{
var configuration = appBuilder.Configuration;
});
await builder.Build().RunAsync();
}
}
}
I just did override the function ConfigureAppConfiguration as below
public override void ConfigureAppConfiguration(IFunctionsConfigurationBuilder builder)
{
FunctionsHostBuilderContext context = builder.GetContext();
builder.ConfigurationBuilder
.AddJsonFile(Path.Combine(context.ApplicationRootPath, "local.settings.json"), optional: true, reloadOnChange: false)
.AddJsonFile(Path.Combine(context.ApplicationRootPath, $"local.settings.{context.EnvironmentName}.json"), optional: true, reloadOnChange: false)
.AddEnvironmentVariables();
}
It's working fine in my local without an isolated way or Program.cs file and I don't know whether this same code will work on a server or not.
My local.setings.json
{
"IsEncrypted": false,
"AppSettings": {
"AppDBConnection": "xyz....."
}
My Startup.cs file below and I can access the AppSettings section from my json file
public class Startup : FunctionsStartup
{
private static IConfiguration _configuration = null;
public override void Configure(IFunctionsHostBuilder builder)
{
//var connectionString = Environment.GetEnvironmentVariable("ConnectionStrings:DBConnection");
var serviceProvider = builder.Services.BuildServiceProvider();
_configuration = serviceProvider.GetRequiredService<IConfiguration>();
var appSettingsSection = _configuration.GetSection("AppSettings");
builder.Services.Configure<AppSettings>(appSettingsSection);
var appSettings = appSettingsSection.Get<AppSettings>();
RuntimeConfig.appsettings = appSettings;
var ConnectionString = RuntimeConfig.appsettings.AppDBConnection;
builder.Services.AddDbContext<ShardingDbContext>(options => options.UseSqlServer(ConnectionString), ServiceLifetime.Transient);
builder.Services.AddScoped<ITestService, TestService>();
}
public override void ConfigureAppConfiguration(IFunctionsConfigurationBuilder builder)
{
FunctionsHostBuilderContext context = builder.GetContext();
builder.ConfigurationBuilder
.AddJsonFile(Path.Combine(context.ApplicationRootPath, "local.settings.json"), optional: true, reloadOnChange: false)
.AddJsonFile(Path.Combine(context.ApplicationRootPath, $"local.settings.{context.EnvironmentName}.json"), optional: true, reloadOnChange: false)
.AddEnvironmentVariables();
}
}

ASP.NET CORE Web API Serilog and ElasticSearch logging not working on IIS but works locally

I configured .NET 5 Web API Serilog and Elasticsearch, it works normally on local when run application and send request from REST client. But when i publish it on IIS and made same request to same endpoints it is not working entirely. No any logs from app published on IIS.
public class Program
{
public static void Main(string[] args)
{
ConfigureLogging();
CreateHost(args);
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
webBuilder.UseWebRoot("client-app");
})
.ConfigureAppConfiguration(configuration =>
{
configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
configuration.AddJsonFile(
$"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")}.json",
optional: true);
})
.UseSerilog();
private static void ConfigureLogging()
{
var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile(
$"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")}.json",
optional: true)
.Build();
Log.Logger = new LoggerConfiguration()
.Enrich.FromLogContext()
.Enrich.WithMachineName()
.WriteTo.Debug()
.WriteTo.Console()
.WriteTo.Elasticsearch(ConfigureElasticSink(configuration, environment))
.Enrich.WithProperty("Environment", environment)
.ReadFrom.Configuration(configuration)
.CreateLogger();
}
private static ElasticsearchSinkOptions ConfigureElasticSink(IConfigurationRoot configuration, string environment)
{
return new ElasticsearchSinkOptions(new Uri(configuration["ElasticConfiguration:Uri"]))
{
AutoRegisterTemplate = true,
IndexFormat = $"{Assembly.GetExecutingAssembly().GetName().Name?.ToLower().Replace(".", "-")}-{environment?.ToLower().Replace(".", "-")}-{DateTime.UtcNow:yyyy-MM}"
};
}
private static void CreateHost(string[] args)
{
try
{
CreateHostBuilder(args).Build().Run();
}
catch (System.Exception ex)
{
Log.Fatal($"Failed to start {Assembly.GetExecutingAssembly().GetName().Name}", ex);
throw;
}
}
}
Elastic uri has not restriction on IIS

Dependency injection for IOptions

Not sure what I am doing wrong here. IOptions<ConnectionStrings> is not updating with new values.
appSettings.json
"ConnectionStrings": {
"Database": "UserID={0};Password={1};Host=xyz.com;Port=5432;Database=xyz;Pooling=true;SSL Mode=Require;Trust Server Certificate=true"
}
Program.cs
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.SetSecretsAsEnvironmentVariables() //where I am setting environment variable from secret.json
.UseStartup<Startup>()
.ConfigureAppConfiguration((hostContext, config) =>
{
var env = hostContext.HostingEnvironment;
config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true); config.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
config.AddEnvironmentVariables();
if (hostContext.HostingEnvironment.IsDevelopment())
{
config.AddUserSecrets<Program>();
}
})
.Build();
host.Run();
}
StartUp:
public IConfiguration Configuration { get; private set; }
public IHostingEnvironment HostingEnvironment { get; private set; }
public Startup(IConfiguration configuration, IHostingEnvironment env)
{
this.HostingEnvironment = env;
this.Configuration = configuration;
}
private void ConfigureAppSettings(IServiceCollection services)
{
services.Configure<ConnectionStrings>(Configuration.GetSection("ConnectionStrings"));
Configuration.GetSection("ConnectionStrings").Bind(Service.Configuration.Configuration.ConnectionStrings);
Service.Configuration.Configuration.ConnectionStrings.Database =
string.Format(Service.Configuration.Configuration.ConnectionStrings.Database, "abc",
"xyz"); //here I am setting up new value for UserID and Password.
}
MyConnectionFactory.cs
public class ConnectionFactory : IConnectionFactory
{
private readonly ConnectionStrings _connectionStrings;
public ConnectionFactory(IOptions<ConnectionStrings> connectionStrings)
{
_connectionStrings = connectionStrings.Value;// Here connection string values are always withn{0},{1}.
}
public NpgsqlConnection CreateOpenConnection()
{
var conn = new NpgsqlConnection(_connectionStrings.QuoteDatabase);
conn.Open();
return conn;
}
In my ServiceRegistry.cs
ForSingletonOf<IConnectionFactory>().Use<MyConnectionFactory>();
ForSingletonOf<ConnectionStrings>().Use(new ConnectionStrings() { Database = Configuration.Configuration.ConnectionStrings.Database });
Don't know What I am doing wrong here. My Connection string values are not updating with new values. If I am changing in MyConnectionFactory.cs from IOptions<ConnectionStrings> to ConnectionStrings the values are updating with new one.
Please anyone me help me out here.

ASP.NET core how could i get my websites url/hostname when the program starts up

I need to get my site's host name so I can change the directory of the JSON file that I am adding to my project. I just have no clue how I can collect the URL.
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostingContext, config) => {
var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
config.SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile("wwwroot/sites/"environment"/"environment".json", optional: false, reloadOnChange: true);
})
.UseStartup<Startup>()
.ConfigureLogging((hostingContext, logging) =>
{
logging.AddLog4Net();
logging.SetMinimumLevel(LogLevel.Information);
});
You can get the url in the following ways.
public static void Main()
{
var host = BuildWebHost(null);
host.Start();
var serverAddress = host.ServerFeatures.Get<IServerAddressesFeature>().Addresses;
foreach (var address in serverAddress)
{
var uri = new Uri(address);
var port = uri.Port;
Console.WriteLine($"Bound to port: {port}");
}
host.WaitForShutdown();
}
private static IWebHost BuildWebHost(string[] args)
{
return WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
}

How to access secrets in ASP.NET Core Program.cs

I am building an ASP.NET Core version 1.1 application that I want Kestrel to run over HTTPS/SSL. Here is the Program.cs bootstrap code...
public class Program
{
public static void Main(string[] args)
{
var contentRoot = Directory.GetCurrentDirectory();
var certFilePath = Path.Combine(contentRoot, #"Certificates\Kestrel.pfx");
// TODO Store password in Secrets
var certificate = new X509Certificate2(certFilePath, "kr0GEE6lJ5Ok");
var host = new WebHostBuilder()
.UseKestrel(cfg => cfg.UseHttps(certificate))
.UseContentRoot(contentRoot)
.UseSetting("detailedErrors", "true")
.UseIISIntegration()
.UseStartup<Startup>()
.UseUrls("https://localhost:5001/")
.CaptureStartupErrors(true)
.Build();
host.Run();
}
}
This works as you might expect, but I would like to remove the certificate's password string from the code.
I have used the new (to me anyway) Secrets Manager Tool in the rest of the application, but I cannot find a way to reference this at this stage in the application pipeline.
Is this possible? If not, what other options might I look at?
Thanks.
I am not sure whether you can use the Secrets API. But you can read the password either from Environment variables or appsettings.json file. Here is the sample code. I am using .NET Core 2.0 code, which is similar to .NET Core 1.1.
public class Program
{
public static void Main(string[] args)
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddEnvironmentVariables()
.AddJsonFile("appsettings.json");
Configuration = builder.Build();
BuildWebHost(args).Run();
}
public static IConfigurationRoot Configuration { get; set; }
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args).UseKestrel(options =>
{
var password = Configuration["certPassword"];
options.Listen(System.Net.IPAddress.Loopback, 5001, listenOptions =>
{
listenOptions.UseHttps("testCert.pfx", password);
listenOptions.UseConnectionLogging();
});
})
.UseStartup<Startup>()
.Build();
}
Hope it helps.
This is a quick solution based on the following article who explain how to add the user secret in console application.
// secrets.json (you can access to this file in Visual Studio via right click on your project in Solution Explorer => Manage user secrets)
{
"SecretSection": {
"Secret1": "Value1",
"Secret2": "Value2"
}
}
// YourSecretSettings.cs
public class YourSecretSettings
{
public string Secret1 { get; set; }
public string Secret2 { get; set; }
}
// Program.cs
public class Program
{
public static void Main(string[] args)
{
var config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddEnvironmentVariables()
.AddJsonFile("appsettings.json", optional: false)
.AddUserSecrets<YourSecretSettings>()
.Build();
var secretSettings = config.GetSection("SecretSection").Get<YourSecretSettings>();
// Do Something with your secret settings ...
CreateWebHostBuilder(args)
.Build()
.Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
Et VoilĂ  :)