Upgrading to WebJobs SDK 3.0 has broken overridden configuration in appsettings - .net-core

I migrate a WebJobs project to 3.0 and I've run into a peculiar issue. My project has an appsettings.json and various appsettings.environment.json files.
When running under any environment, instead of the environment settings overriding the base settings, the reverse is occurring: When any settings exist in the base settings and environment settings, the base settings is used.
I've tried variations of using HostBuilder.ConfigureAppConfiguration() and HostBuilder.ConfigureHostConfiguration() and in each case I notice something peculiar when I look at hostInstance.Configuration.Providers: There are always three instances of JsonConfigurationProvider, with either two inside ChainedConfigurationProvider when configuring host or just as part of the main provider array when using application. The first instance is always my base, the second is always the environment and the third is always the base again.
So I believe my problem is that this third JsonConfigurationProvider is getting added, but I don't know how it is getting added.
Here is the relevant code from my WebJob:
var envName = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")
?? Environment.GetEnvironmentVariable("ENV")
?? "development";
var builder = new HostBuilder()
.UseEnvironment(envName)
.ConfigureAppConfiguration(b =>
{
b.SetBasePath(Environment.CurrentDirectory)
.AddCommandLine(args, StartupSettings.SwitchMapping)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{envName}.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables();
});
builder.ConfigureWebJobs((context, b) =>
{
b.AddAzureStorageCoreServices();
b.AddAzureStorage();
b.AddTimers();
});
builder.ConfigureLogging((context, b) =>
{
if (false && context.HostingEnvironment.IsDevelopment())
{
b.SetMinimumLevel(LogLevel.Debug);
}
else
{
b.SetMinimumLevel(LogLevel.Information);
b.AddFilter("Microsoft.EntityFrameworkCore.Database.Command", LogLevel.Warning);
}
b.AddConsole();
var applicationInsightsKey = context.Configuration.GetValue<string>("ApplicationInsights:InstrumentationKey");
b.AddApplicationInsights(o => o.InstrumentationKey = applicationInsightsKey);
})
.UseConsoleLifetime();
builder.ConfigureServices((context, services) =>
{
/*...*/
});
return builder.Build();

Just figured it out.
The extension method for ConfigureWebJobs() calls ConfigureAppConfiguration() and automatically adds appsettings. Since I was calling it after calling ConfigureAppConfiguration() that was the reason the last appsettings.json was the base one.
Code from WebJobsHostBuilderExtensions.ConfigureWebJobs:
builder.ConfigureAppConfiguration(config =>
{
config.AddJsonFile("appsettings.json", optional: true);
config.AddEnvironmentVariables();
});
The worst part is that these seems so superfluous.

Related

Azure Functions .NET 5 fails to start after changing value in local settings file

This is a very strange problem and want to see if anyone else can replicate the issue.
I start a brand new Azure Functions app targeting .NET 5. Mine is a timer function but I don't it matters what type of function it is.
I then add a value in my local.settings.json file:
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "MY_CONNECTION_STRING_FOR_AZURE_STORAGE",
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
"MY_APP_ID": "1324"
}
}
}
I modify the Program.cs to read this value:
var host = new HostBuilder()
.ConfigureAppConfiguration(c =>
{
c.AddEnvironmentVariables();
var config = c.Build();
var id = config.GetValue<string>("MY_APP_ID");
})
.ConfigureFunctionsWorkerDefaults()
.Build();
I then run the app and it seems to start up fine.
Then, I modify the local.settings.json file and add a section -- see below:
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "MY_CONNECTION_STRING_FOR_AZURE_STORAGE",
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
"MY_APP_ID": "1324",
"MyApp": {
"MY_APP_ID": "1324"
}
}
}
}
When I try to run the app, I get the following error:
I then remove this section from my local.settings.json file and try running the app again and I get the same error.
It looks like, from this point on, there's nothing I can do to make this app run! If you noticed, I didn't change any code in my Program.cs file. Simply adding a section and removing it from local.settings.json file seems to render the app useless!
Any idea what's going on here?

Disable web security in Cypress just for one test

After reading the Cypress documentation on web security and when to disable it, I've decided I indeed need to do it. Is there a way to disable this just for one particular test/test suite? I'm using version 3.4.1 and this config is being set in cypress.json - therefore it's global for all tests.
Is there a way to disable web security just for one test? Thanks!
Original answer:
Does this work for you?
describe("test the config json", function () {
it("use web security false here", function () {
Cypress.config('chromeWebSecurity',false);
cy.visit("https://www.google.com");
console.log(Cypress.config('chromeWebSecurity'));
});
it("use web security true here", function () {
Cypress.config('chromeWebSecurity',true);
cy.visit("https://www.google.com");
console.log(Cypress.config('chromeWebSecurity'));
});
});
The config is changed as you can see from the console log.
See document here https://docs.cypress.io/guides/references/configuration.html#Cypress-config
Updates:
After I saw DurkoMatKo's comment I managed to find an URL to test this 'chromeWebSecurity' option. It did not work as expected.
I think changing this config might not work during running the same browser as this is more like a browser feature which will determine when start.
In this case what I can think of is only to run Cypress with different configurations.
The cypress doc here shows clear steps to do this.
hope this helps.
In my case it worked as follows.
the first thing was to set chromeWebSecurity to false
//cypress.json
{
"chromeWebSecurity": false
}
Then what I do is with a before assign it to true with Cypress.config
//cypress/integration/testing.spec.js
context('DEMO-01', () => {
beforeEach(function () {
Cypress.config('chromeWebSecurity', true);
});
describe('CP001 - start dasboard', () => {
it('P01: open dashboard', () => {
cy.visit(URL);
});
});
});

.NET Core 2.2 Razor Pages Conditional Connections String based on Dev or Prod Enviroment

I have done this in .NET Web Forms for years:
Dim conn As New SqlConnection(f1.fUseThisConnection(Server.MachineName))
Public Function fUseThisConnection(ByVal strThisMachine As String) As String
If strThisMachine = "T61" Then
fUseThisConnection = ConfigurationManager.AppSettings("DevHome")
Else
fUseThisConnection = ConfigurationManager.AppSettings("Production")
End If
End Function
And the AppSettings are in Web.config:
<appSettings>
<add key="Production" value="server=xxx;uid=xxx;pwd=xxx;database=xxx;pooling=false" />
<add key="DevHome" value="server=xxx;uid=xxx;pwd=xxx;database=xxx;pooling=false" />
But I have yet to search and find anything so simple for my C# Razor Pages. Anything from Microsoft howto's just boggles my mind.
Here is my appSetting.json in my Razor Pages project:
"ConnectionStrings": {
"DefaultConnection": "Data Source=Txx\\SQLxxx;Initial Catalog=xxx;Persist Security Info=True",
"Production": "Data Source=xxx;Initial Catalog=xxx;Integrated Security=True;User ID=xxx;Password=xxx"
},
And here is where I reference the connections string--which is hardcoded--but I am hoping there is a way to do an "If" statement: "If environment= whatever, then get this connection string".
services.AddDbContext<MyDbContext>(
options => { options.UseSqlServer(#"Data Source=Txxx;Initial Catalog=xxx;Integrated Security=True"); });
Just check the configuration documentation, I suppose you are using Asp Net Core, and you are adding Mvc in your startup
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
app.UseMvc();
...
}
By default, it will try to load 2 configurations files, appsettings.json and appsettings.{Environment}.json, the latest overrides the contents from the final configuration, and also by default, core already has two environments, development and production, all depends in which profile you are using (in visual studio by default will be development).
So, your config file could be renamed to appsettings.json, then you declare your connection strings for production, and create a file appsettings.Development.json were you can override the connection with the development connection string.
You can also load more files o change the default ones by using something like this
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.SetBasePath(Directory.GetCurrentDirectory());
config.AddInMemoryCollection(arrayDict);
config.AddJsonFile("json_array.json", optional: false, reloadOnChange: false);
config.AddJsonFile("starship.json", optional: false, reloadOnChange: false);
config.AddXmlFile("tvshow.xml", optional: false, reloadOnChange: false);
config.AddEFConfiguration(options => options.UseInMemoryDatabase("InMemoryDb"));
config.AddCommandLine(args);
})
.UseStartup<Startup>();
If you want the environment variable
Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT ")
But please, consider being more secure
For sensible data something more secure could be to configure your connection string as environment variable in your server, this way it can be removed from your code and keep more private.
This and other solution using a secret manager can be found in the secrets documentation.
Edit
Otherwise, it defaults to the "appSettings.json" file (this would be
in production)
No, it will not load one or another, first it will load appsettings.json, this is always, no matter what Environment you have.
Then, after it loads appsettings.json it will try to load appsettings.{Environment}.json and it will add the keys that were not there before, and it will override an old key if exists.
If you have in appSettings
{
"SomeConfigJustInAppSettings": "some value",
"ThisWillBeOverride": "some value"
}
And your appsettings.Development.json
{
"SomeConfigJustInDev": "some other value",
"ThisWillBeOverride": "some other value"
}
Your config in dev would ended up being:
{
"SomeConfigJustInAppSettings": "some value",
"SomeConfigJustInDev": "some other value",
"ThisWillBeOverride": "some other value"
}
what determines what "ASPNETCORE_ENVIRONMENT" holds?
Is an environmental variable, is set in the Operative System. Also, for development Visual studio has the capacity to preload some variables for when you are developing your app, just check your launchSettings.json, example:
{
"profiles": {
"MyProject": {
"commandName": "Project",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "http://localhost:50051"
},
"Docker": {
"commandName": "Docker",
"launchBrowser": true,
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}"
}
}
}
Specially this part
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
It would be nice to just find what the server name is and use that.
Because I would only check for my dev box servername, everything else
would mean production
Right now it is even more simple, if the variable does not exist, it just loads the appsettings which could contain you prod settings. When you are developing, visual studio (or VS code, or Rider), are already adding the variable with the value Development, so all you need to do is to add the file appsettings.Development.json.
Hell, you can even just create a new project, the default project layout already creates this two files for you!

Custom appsettings.json is not loaded

I have an .net core 2.0 application, having multiple appsettings files. I have appsettings.json, appsettings.Development.json and a custom appsettings.Secure.json.
//appSettings.json
{
"AzureAd" : {
"ClientId" : "CID"
}
}
//appSettings.Development.json
{
"AzureAd" : {
"Random" : "random2"
}
}
//appSettings.Secure.json
{
"AzureAd" : {
"ClientSecret" : "CSECRET"
}
}
I always want my appSettings.Secure.json configs to be loaded. Here's how the json files are configured.
var config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appSettings.json", optional : false)
.AddJsonFile("appSettings.Development.json")
.AddJsonFile("appsettings.Secure.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables()
.Build();
The problem is that my "AzureAd:ClientSecret" gets loaded in the config (gets listed in the Providers section of the config object) but when I inject IOption, the ClientSecret property is null.
AzureAdOptions is the class to which the properties are mapped, and it's registered as follows:
services.Configure<AzureAdOptions>(Configuration.GetSection("AzureAd"));
JSON is not secure in any way, especially so considering that this is likely being committed to your source control.
The appsettings.json file is for generic, environment in-specific config. Then, you should have appsettings.{environment}.json files for each environment, but still no secrets should go in any of these files. In development, you can utilize User Secrets to store your secrets. In production, you should be using something like Azure Key Vault and/or environment variables for secrets.
Long and short, your appsettings.Secure.json file should not exist in the first place. It's not an environment and it's not secure.

Q: How to extend the routing config in Rebus

I have a rebus config project which is shared for many web api projects
So basically, it looks like
Web api 1 ==> Shared Rebus Config
Web api 2 ==> Shared Rebus Config
Web api 3 ==> Shared Rebus Config
My question is, if I have some messages & handlers in Web api 3 project, how can I configure the routing for them?
My current config:
var autofacContainerAdapter = new AutofacContainerAdapter(container);
return Configure
.With(autofacContainerAdapter)
.Serialization(s => s.UseNewtonsoftJson())
.Routing(r =>
{
r.TypeBased()
.MapAssemblyOf<ProjectA.MessageA>(EnvironmentVariables.ServiceBusQueueName)
.MapAssemblyOf<ProjectB.MessageB>(EnvironmentVariables.ServiceBusQueueName);
})
.Sagas(s =>
{
s.StoreInSqlServer(EnvironmentVariables.ConnectionString, "Saga", "SagaIndex");
})
.Options(o =>
{
o.LogPipeline();
o.EnableDataBus().StoreInBlobStorage(EnvironmentVariables.AzureStorageConnectionString, EnvironmentVariables.BlobStorageContainerName);
o.EnableSagaAuditing().StoreInSqlServer(EnvironmentVariables.ConnectionString, "Snapshots");
})
.Logging(l =>
{
l.Use(new SentryLogFactory());
})
.Transport(t =>
{
t.UseAzureServiceBus(EnvironmentVariables.AzureServiceBusConnectionString, EnvironmentVariables.ServiceBusQueueName).AutomaticallyRenewPeekLock();
})
.Start();
Well... as you have probably already found out, it is not possible to make additional calls to the .Routing(r => r.TypeBased()....) part. Therefore, I can see two fairly easy ways forward:
1: Simply pass additional parameters to your shared configuration method from the outside, e.g. something like this:
var additionalEndpointMappings = new Dictionary<Assembly, string>
{
{ typeof(Whatever).Assembly, "another-queue" }
};
var bus = CreateBus("my-queue", additionalEndpointMappings);
which of course then needs to be handled appropriately in the .Routing(...) configuration callback.
2: Pull out all the common configurations into a new extension method. I almost always use this method myself, because I have found it to be easy to maintain.
First you create a new RebusConfigurer extension method in a shared lib somewhere:
// shared lib
public static class CustomRebusConfigEx
{
public static RebusConfigurer AsServer(this RebusConfigurer configurer, string inputQueueName)
{
return configurer
.Logging(...)
.Transport(...))
.Sagas(...)
.Serialization(...)
.Options(...);
}
}
and then you can call this by going
Configure.With(...)
.AsServer("my-queue")
.Start();
in your endpoints.
3: A combination of (1) and (2) which enables this:
Configure.With(...)
.AsServer("my-queue")
.StandardRouting(r => r.MapAssemblyOf<MessageType>("somewhere-else"))
.Start();
which can end up avoiding repetitive code, still preserving a great deal of flexibility, and actually looking pretty neat :)

Resources