Getting connection string for DBContext when deploying to Azure - azure

EDIT: This question has been significantly restructured, now I've figured out a bit more of the problem and this should clarify things.
I am following this tutorial: https://learn.microsoft.com/en-us/azure/app-service/tutorial-dotnetcore-sqldb-app
I've deployed my own multiproject app, which works, but I can't get the connection string working properly. For some reason, it only works if I hardcode the connection string into the OnConfiguring method of my DBContext class. Otherwise, it throws an error.
Like so:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer(
"Nasty hard coded azure connection string",
providerOptions => { providerOptions.EnableRetryOnFailure(); });
}
However, obviously, i want to get the connection string from a configuration file or environment variable.
Prior to deploying, I had the following. An extension method for IServiceColleciton which sets up the connection string:
public static void ConfigureSqlContext(this IServiceCollection services,
IConfiguration configuration) =>
services.AddDbContext<PeakedDbContext>(opts =>
opts.UseSqlServer(configuration.GetConnectionString("defaultConnection")));
then this method is called in program.cs. A pretty normal setup.
And I also set up an IDesignTimeDBContextFactory like so:
public class RepositoryContextFactory : IDesignTimeDbContextFactory<PeakedDbContext>
{
public PeakedDbContext CreateDbContext(string[] args)
{
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
var builder = new DbContextOptionsBuilder<PeakedDbContext>()
.UseSqlServer(configuration.GetConnectionString("defaultConnection"));
return new PeakedDbContext(builder.Options);
}
}
Both my appsettings.json AND the Azure App Service configuration have the same name "defaultConnection".
As far as I can tell this is the approach recommended here: https://learn.microsoft.com/en-us/ef/core/cli/dbcontext-creation?tabs=dotnet-core-cli
I have also tried adding an empty constructor for my DBContext. (Not sure how this affects things as I have other DIs on my DBContext constructor. My DBContext consructors are getting a bit out of hand:
public PeakedDbContext()
{
}
public PeakedDbContext(DbContextOptions options) : base(options)
{
}
public PeakedDbContext(DbContextOptions options, ICurrentUserService currentUserService) : base(options)
{
_currentUserService = currentUserService;
}
According the the second link, above, I shouldn't need OnConfiguring method in my DBContext... and even if I do, what is the correct way to pass access to configuration.GetConnectionString, instead of hardcoding the connection string? Should I just add yet another DBContext constructor with the config injected? However, it ONLY works if I have the onconfiguring method. Neither the contextfactory nor the extension method setup are being used by azure app service.
Shouldn't it use the designtime factory or the hostconfiguration extension method I've set up above? What is the right way to use _configuration.GetConnectionString("defaultConnection") so that it works both locally and on Azure Deployment?
Update:
Still no luck. I tried adding the database connection string as an environment variable on azure like so:
and then updating all my reference to getconnection string - in program.cs, IDesignFactory and OnConfiguring - like so:
Environment.GetEnvironmentVariable("PeakedDbConn")
This continues to work locally. But When deploying to Azure it claims the connection string in null... so it's not seeing this variable. Nor can I find any code that will access the defaultConnection from the image. Which is strange, because it accesses the SECRET variable just fine.

I have followed the same code which you have provided with few changes.
Check the below steps to get the Connection string from appsettings.json and override the value if Azure App Connection String has been set.
As you are using .NET Core 6, I have set all the Configurations in Program.cs itself.
My Program.cs
builder.Services.AddDbContext<MyDatabaseContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("MyDbConnection")));
builder.Configuration.AddEnvironmentVariables();
Same Connection String name must exist in both Local and Azure App Connection Strings.
My appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"MyDbConnection": "Dummy Connection String"
}
}
Azure App Connection String:
To check if we are getting the Connection String Value, I have written code in Controller.
private readonly IConfiguration Configuration;
public HomeController(ILogger<HomeController> logger,IConfiguration config)
{
_logger = logger;
Configuration = config;
}
public IActionResult Index()
{
var myconnStr = Configuration.GetConnectionString("MyDbConnection");
ViewBag.myconnStr = myconnStr;
return View();
}
My .csproj file :
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.AzureAppConfiguration" Version="5.2.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" />
</ItemGroup>
</Project>
Local Output:
Deployed App Output:

Ok so it turned out to be GitHub actions that was the problem. The tutorial I followed in the first link doesn't mention this, perhaps because it's a single project api... not exactly sure.
The tutorial uses github actions to build and deploy the app, but for me, during the build it fails saying there is no connection string. This is because GitHub build process doesn't have access to either your local or the azure environment variable.
So I had to go into setting on my github repo and on the left click on secrets and variables < actions.
Click create a new Repository Secret, give it the same name as your environment variable i.e. PEAKEDDBCONN for me. Then, give it a value. I just used my local host string, but I guess you could type 'monkeynuts' in here if you wanted, it just needs to not be null.
Then you need to add a line to your workflow yaml file, the same one the tutorial talks about, to tell it about the environment variable. I added it like so:
jobs:
build:
runs-on: ubuntu-latest
env:
PEAKEDDBCONN: ${{ secrets.PEAKEDDBCONN }}
Then it all builds nicely and works.
For anybody who it helps, I decided to blog this whole setup:
https://garyfrewin.hashnode.dev/setting-up-an-entity-framework-core-web-api-on-azure-with-sqlserver-a-step-by-step-guide-for-hobby-projects

Related

ConnectionString is null when deploying Azure Function

I've been working with functions with Azure, I've built a very simple Http Function locally by following the example linked here, the only difference is I've defined a User table instead of a Todo table
Everything works as expected locally, I'm able to post and get.
However, when deploying the function and trying to make a POST request I see the following within the logs:
Executed 'User' (Failed, Id=5df9dffe-eedf-4b11-aa10-54fda00992b0, Duration=1ms)System.ArgumentNullException : Value cannot be null. (Parameter 'connectionString')
I've checked the SQL Server to ensure it's accessible by other Azure Services just encase that was causing a problem, but I can confirm it's set to allow.
I have found this question, I've gone through the steps and checked against mine and I can confirm my Function App configuration does have the AzureWebJobsStorage connection string.
I'm not 100% sure why this would be happening due to my lack of knowledge of functions at the moment, have anyone else experience this? if so how did you resolve it?
Update
After further testing, it seems the error is coming from my Startup class,
class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
string connectionString = Environment.GetEnvironmentVariable("SqlConnectionString");
builder.Services.AddDbContext<ApplicationDbContext>(
options => SqlServerDbContextOptionsExtensions.UseSqlServer(options, connectionString));
}
}
Upon deployment, connectionString variable is null.... not sure why though.
Yes, you can not get it because you didn't set it in the configuration settings.
If you want to use the Connection Strings section.
Add the "ConnectionStrings":{} section to your local.settings.json file then add your connection string
{
...
"ConnectionStrings": {
"MyConnectionString": ""
}
}
Then you need to set the connection string in the Settings section of the Function App in the Azure Portal.
The scroll down to the Connection Section
And add a new connection string. Make sure it has the same name as you connection in the local.settings.json file.
Your question isn't 100% clear if this is happening locally (as you refer to local.settings.json) or when deploying. If this occurs when deploying, changing your local.settings.json file will not help, unfortunately.
You will need to add the Application Setting within the Azure Portal (located under Settings -> Configuration -> Application Settings -> New application setting).
You will need to save the application setting, and then restart the Azure Function instance for the changes to reflect.
Check out https://learn.microsoft.com/en-us/azure/azure-functions/functions-how-to-use-azure-function-app-settings?tabs=portal

Access Azure App Service ConnectionString from ASP.NET Core

I've got an azure app service that I've set up like this:
But when I call IConfiguration.GetConnectionString("db") I get null back.
I've read articles like this https://mderriey.com/2018/08/21/azure-app-service-connection-strings-and-asp-net-core/ which say "it just works", but they're all several years old. I assume something's changed, but what?
Enumerating over all settings in my IConfiguration object I've got no connection strings. I do in development, where my appsettings.development.json has a connectionStrings: { db: "" } defined.
I can see and read the ENV variable: POSTGRESQLCONNSTR_db from within code, and it's value is correct (what I've set via the Azure portal).
Should I expect to be able to do IConfiguration.GetConnectionString("db")? Or am I expected to switch between reading env variables in prod vs dev.
Do I need to include some nuget package to make IConfiguration work under Azure with these ENV variables and their mad prefixes?
My startup.cs basically looks like:
public Startup(IConfiguration configuration)
{
this.Configuration = configuration;
}
public IConfiguration Configuration { get; }
Nothing else in there of interest to this question.
The POSTGRESQLCONNSTR_ prefix isn't supported by the environment variables configuration provider. The docs shows this, in an indirect fashion, where it states that the following prefixes are supported:
CUSTOMCONNSTR_
MYSQLCONNSTR_
SQLAZURECONNSTR_
SQLCONNSTR_
It's also apparent in the source code for the provider.
There are a couple of options for working around this:
Change the Type to Custom in the Connection strings section of the Azure portal.
Change to an Application setting of ConectionStrings:db in the Azure portal.
This is being tracked on GitHub: https://github.com/dotnet/runtime/issues/36123.
I got confused as well, so here it is:
You have two options to specify Connection String locally:
launchSettings.json (environmentVariables section)
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"SQLAZURECONNSTR_SomeConnectionString": "DefaultEndpointsProtocol=blah"
}
appSettings.json
"ConnectionStrings": {
"SomeConnectionString": "DefaultEndpointsProtocol=blah"
}
Having either way will allow you to get the connection string setting by calling:
IConfiguration.GetConnectionString("SomeConnectionString")
Function call above will also work when deployed to Azure, as it is using EnvironmentVariables configuration provider to read settings.
Instead of getting the config from the interface
IConfiguration.GetConnectionString("db")
try to get it from
Configuration.GetConnectionString("db")
And in production you have an empty string in production.appsetting.json and add the value in azure(appservice) configuration directly under connectionstrings(this will override the json setting file). And no nugets are needed for reading from appsettings

How to not get null values from User secret configs?

I want to use user secrets for KeyVault clientid etc. I call my configs from program.cs when I setup KeyVault. When I add the values in appsettings.json all works fine, but if I remove it and manage user secrets it's always say null when my program.cs is running.
And I have the correct values in the created secret.json and the attribute with a guid to it is also in my project file
I have also tried to add a key and value to the secret.json and tried to get it from a controller and it's also null.
So it seems like WebHost.CreateDefaultBuilder in my program.se does not load my secret.json file.
And if I add AssUserSecrets to my WebHost.CreateDefaultBuilder(args) in progam.cs there is no difference same problem.
I'm using WebHost.CreateDefaultBuilder so it shall be used by default in asp .net core 2.2
dontnet user-secret list
Will also shows the values for me from my secret.json file so the value is there, the user-secret has been initialized.
dontnet user-secret init
Also, say it's already setup.
But still just null when calling my secret.json attributes
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.ConfigureServices(services => services.AddAutofac())
.ConfigureAppConfiguration((context, builder) =>
{
var config = builder.Build();
var vault = config["KeyVault:Vault"];
builder.AddAzureKeyVault($"https://{config["KeyVault:Vault"]}.vault.azure.net/", config["KeyVault:ClientId"], config["KeyVault:ClientSecret"]);
});
I test in my site and it works fine, here is the steps you could refer to.
1.Right click on the project and select Manage User Secrets. It will open the secrets.json file in Visual Studio where you can add your secrets and it will add the to the .csproj file.
2.Add what you may wish to add to your User Secrets. It includes two ways of writing a json object and a simple key/value pair.
3.Mapping User Secrets to a model.
public class KeyVault
{
public string Vault { get; set; }
public string ClientId { get; set; }
public string ClientSecret { get; set; }
}
4.Adding the Configuration.
Install package Microsoft.VisualStudio.Web.CodeGeneration.Design and Microsoft.Extensions.Configuration and Microsoft.Extensions.Configuration.UserSecrets
5.Bind the values. This is done in the Startup.cs file.
6.Using your mapped secrets
public class HomeController : Controller
{
private readonly KeyVault _keyvault;
// I’ve injected twilioAccountDetails into the constructor
public HomeController(IOptions<KeyVault> keyvault)
{
// We want to know if twilioAccountDetails is null so we throw an exception if it is
_keyvault = keyvault.Value ?? throw new ArgumentException(nameof(keyvault));
}
........
}
7.Call it in Program.cs.
For more details about how to use User Secrets in a .NET Core Web App, you could refer to this article.
It seems to be related to: Microsoft.Extensions.Configuration.AzureKeyVault
because as soon I add that nuget it stops working.
UPDATE: More info. it seems like the latest Microsoft.Extensions.Configuration.AzureKeyVault downgrade some of the configuration libs for me. And then it stops working.
I was having this same issue when using Host.CreateDefaultBuilder() and discovered that it was because my host environment was not Development.
To resolve this I added the following section to my launchSettings.json:
"environmentVariables": {
"DOTNET_ENVIRONMENT": "Development"
}
See related documentation:
The following defaults are applied to the returned HostBuilder:
load app IConfiguration from User Secrets when EnvironmentName is 'Development' using the entry assembly
In my case, I was missing the following NuGet package:
Microsoft.Extensions.Configuration.UserSecrets
Once I installed this, it worked perfectly.

Azure WebJobs Connection Strings configuration ( AzureWebJobsDashboard?? )

I'm trying to work with Azure Webjobs, I understand the way its works but I don't understand why I need to use two connection strings, one is for the queue for holding the messages but
why there is another one called "AzureWebJobsDashboard" ?
What its purpose?
And where I get this connection string from ?
At the moment I have one Web App and one Webjob at the same solution, I'm experiment only locally ( without publishing anything ), the one thing I got up in the cloud is the Storage account that holds the queue.
I even try to put the same connection string in both places ( AzureWebJobsDashboard,AzureWebJobsStorage) but its throw exception :
"Cannot bind parameter 'log' when using this trigger."
Thank you.
There are two connection strings because the WebJobs SDK writes some logs in the storage account. It gives you the possibility of having one storage account just for data (AzureWebJobsStorage) and the another one for logs (AzureWebJobsDashboard). They can be the same. Also, you need two of them because you can have multiple job hosts using different data accounts but sending logs to the same dashboard.
The error you are getting is not related to the connection strings but to one of the functions in your code. One of them has a log parameter that is not of the right type. Can you share the code?
Okay, anyone coming here looking for an actual answer of "where do I get the ConnectionString from"... here you go.
On the new Azure portal, you should have a Storage Account resource; mine starts with "portalvhds" followed by a bunch of alphanumerics. Click that so see a resource Dashboard on the right, followed immediately by a Settings window. Look for the Keys submenu under General -- click that. The whole connection string is there (actually there are two, Primary and Secondary; I don't currently understand the difference, but let's go with Primary, shall we?).
Copy and paste that in your App.config file on the connectionString attribute of the AzureWebJobsDashboard and AzureWebJobsStorage items. This presumes for your environment you only have one Storage Account, and so you want that same storage to be used for data and logs.
I tried this, and at least the WebJob ran without throwing an error.
#RayHAz - Expanding upon your above answer (thanks)...
I tried this https://learn.microsoft.com/en-us/azure/app-service/webjobs-sdk-get-started
but in .Net Core 2.1, was getting exceptions about how it couldn't find the connection string.
Long story short, I ended up with the following, which worked for me:
appsettings.json, in a .Net Core 2.1 Console app:
{
"ConnectionStrings": {
"AzureWebJobsStorage": "---your Azure storage connection string here---",
"AzureWebJobsDashboard":"---the same connectionstring---"
}
}
... and my Program.cs file...
using System;
using System.IO;
using Microsoft.Azure.WebJobs;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace YourWebJobConsoleAppProjectNamespaceHere
{
public class Program
{
public static IConfiguration Configuration;
static void Main(string[] args)
{
var builder = new ConfigurationBuilder()
.SetBasePath(Path.Combine(AppContext.BaseDirectory))
.AddJsonFile("appsettings.json", true);
Configuration = builder.Build();
var azureWebJobsStorageConnectionString = Configuration.GetConnectionString("AzureWebJobsStorage");
var azureWebJobsDashboardConnectionString = Configuration.GetConnectionString("AzureWebJobsDashboard");
var config = new JobHostConfiguration
{
DashboardConnectionString = azureWebJobsDashboardConnectionString,
StorageConnectionString = azureWebJobsStorageConnectionString
};
var loggerFactory = new LoggerFactory();
config.LoggerFactory = loggerFactory.AddConsole();
var host = new JobHost(config);
host.RunAndBlock();
}
}
}

Visual Studio Integration Tests using setting from web.config

I have some integration tests that get the database connection string from the web.config. My web code and my test code are all in one project and this has worked great until now.
When deployed on app harbor app harbor replaces the value in the web.config, but when in the Visual Studio unit test environment the value is not being pulled.
Is there a way to pull the value from web.config when doing a unit test?
Here is my code:
private static string GetMongoDbConnectionString()
{
string con = ConfigurationManager.AppSettings.Get("MONGOHQ_URL") ??
ConfigurationManager.AppSettings.Get("MONGOLAB_URI") ??
"mongodb://www.fromCSFile/test";
return con;
}
Here is my web.config
<appSettings>
<add key="MONGOLAB_URI" value="mongodb://www.fromweb.config/test"/>
I wasn't able to get it to work having app harbor inject the correct config settings, but I got close.
I added an app.config to my web project because my web project has my integration tests in it.
I added the environment app setting to app.config:
<appSettings>
<add key="Environment" value="localconfig"/>
I noticed when that code ran on app harbor the environment value was Test:27017.
I wrote this code:
private static string GetMongoDbConnectionString()
{
string con = ConfigurationManager.AppSettings.Get("MONGOHQ_URL") ??
ConfigurationManager.AppSettings.Get("MONGOLAB_URI");
string env = ConfigurationManager.AppSettings.Get("Environment");
if (env.StartsWith("Test", StringComparison.OrdinalIgnoreCase))
{
con = "mongodb://xxxxxxxxx";
}
return con;
}
Where the xxxxxxxxx is the value I want to use at integration test time.

Resources