Runtime change connection string in Asp.Net Core - c#

I am working on an ASP .NET Core application. i have to change database connection on runtime.
Here i am using appsettings.json file.
Please, I have tries a lot but I nothing seems to be working so I need your help.
For example:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json");
var configuration = builder.Build();
optionsBuilder.UseMySql(configuration
["ConnectionStrings:Defaultconnection"]);
}
Thanks in advance.

I can think of a couple of ways to do this..
1) Create a DbContextFactory which will create your instances for you. Maybe apply an attribute to each context which will allow you to fetch out the relevant db connection string from your connection string collection at the point that the factory makes the instance? A bit of reflection in the DbContextFactory would do that easy enough.
2) If the collection of DbContexts is not likely to change, and/or the connection strings either - you could register each DbContext manually using the AddDbContext<>() extension method in the ServiceCollection and then just inject the DbContext that you want into the classes you want

Related

Is there a way to inject a DB connection string from a service instead of a configuration for EF-Core DbContextPool

My application stores the database connection strings in a secrets vault and not in the config file.
I am attempting to use EF-Core DbContextPool with dependency injection and wondering if I can use the service (IVault,Vault) that has the database connection strings when setting up the DbContextPool instead of the default configuration.
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton <IVault,Vault>();
builder.Services.AddDbContextPool<WeatherForecastContext>(
o => o.UseSqlServer(builder.Configuration.GetConnectionString("WeatherForecastContext")));
}
Lots of ways you could do it, but an easy one would be to add the connection string to the constructor of your vault class, then during DI you could:
Builder.Services.AddSingleton<IVault>(provider => new Vault(builder.Configuration.GetConnectionString("WeatherForecastContext")));

Trying to setup .NET 6 with Cosmos DB EF but issues with setup

I'm trying to setup a simple example for using Cosmos DB, .NET 6 using EF. In the UI project startup, I am implementing the db factory as such:
var config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
// Call CosmosDB connectionString
var cosmosConnectionString = config["cosmosService:CosmosConnectionString"];
var cosmosDatabase = config["cosmosService:DatabaseName"];
services.AddDbContextFactory<DbContext>(optionsBuilder =>
optionsBuilder
.UseCosmos(
connectionString: cosmosConnectionString,
databaseName: cosmosDatabase,
cosmosOptionsAction: options =>
{
options.ConnectionMode(Microsoft.Azure.Cosmos.ConnectionMode.Direct);
options.MaxRequestsPerTcpConnection(20);
options.MaxTcpConnectionsPerEndpoint(32);
}));
I am trying to figure out where to put the call for "CreateDatabaseIfNotExistsAsync" but not finding anyway to adding it. It appears I cannot use the AddDbContextfactory, only implement CosmosClient and add it there. I would prefer to use context factory but so far not finding anything that work.
Any suggestions on implementing it that way, that would be great.
Just an FYI, I have a service project, I've seen examples implementing a provider but again, it uses the CosmosClient as well.
If there is a way to verify and create database beside this, let me know.
Any help would be great.
Thanks!

Is there a way to access appSettings.json globally?

I have a C# console app, and I'm defining an IConfiguration object as follows inside of Program.cs:
var myConfig = new ConfigurationBuilder()
.AddJsonFile($"appsettings.{myEnvironment}.json", true, true)
.Build();
... and I can reference settings from within that same Program.cs file like this:
var mySetting = myConfig.GetValue<string>("Section:SettingName");
However, I'm wondering what would be the way to access that same myConfig object from other files in my application? For instance, myConfig contains a connection string, but I don't actually make use of the connection string until a few layers deeper (inside of myService > myRepository > myDbContext). What is a best practice to read that connection string from inside of myRepository or myDbContext? Obviously, I can pass the connection string as a parameter from Program.cs and myService, but that seems inefficient.
You can use the build-in DI (Dependency Injection), which also works for .NET C# Console Applications as already mentioned here
How to use Dependency Injection in .Net core Console Application
Pass IConfiguration to your constructor. You can use Bind calls to create objects from it, or just read it like a dictionary.

Injecting Connection String into DbContext Class using Autofac in a TopShelf Service (.Net Framework)

I'm creating a service that needs to reference legacy DLLs for our ERP system so unfortunately .NET framework is how I need to do this. I tried using a Core Worker Service but my calls to the DLLs won't work.
I like using TopShelf and looked up DI to use with this and Autofac seems to be the way to go and it has TopShelf support. I don't like keeping any settings in our config files so that it's easier to deploy to other environments. We have a whole system that keeps settings tied to the environment you are in (dev, test, prod, etc...). In Core apps I simply inject the connection string in the startup and all is well. In this app I want to inject the connection string when I start the service and be able to spin up a DbContect class at anytime and have it use that connection string.
Since I scaffold my data layer and didn't want to modify the generated DbContext, so I created another partial class (MyContext.Custom.cs) with a constructor that allows me to pass in the connection string
public MyContext(string name) : base(name)
{ }
In my Program.cs I'm adding in an Autofac container to TopShelf
HostFactory.Run(serviceConfig =>
{
serviceConfig.UseAutofacContainer(container);
// etc...
}
The container is being built in a function, I tried following many examples but I can't seem to get my constructor with the parameter to be the one that is used. Here are a few of the ways I've tried. Each method produces no error but it runs my default constructor where it is looking for the named connection string in the config file.
static IContainer BuildContainer()
{
string myConnectionString = AppConfig.Instance.Settings["MyConnectionString"];
var builder = new ContainerBuilder();
//builder.RegisterType<MyContext>()
// .UsingConstructor(typeof(string))
// .WithParameter("name", myConnectionString)
// .InstancePerLifetimeScope();
//builder.Register(c => new MyContext(myConnectionString)).As<MyContext>();
//.WithParameter("name", myConnectionString);
//builder.RegisterType<MyContext>()
// .WithParameter("name", myConnectionString)
// .AsSelf();
//.InstancePerLifetimeScope();
builder.Register(c => new MyContext(myConnectionString)).AsSelf();
builder.RegisterType<MainService>();
return builder.Build();
}
I've tried each variation with .As and .AsSelf(). I've put in the .InstancePerLifetimeScope() and also left it out. I'm not really sure what to set the scope to in this case but figured it should work with lifetime. No matter what I've tried I can't seem to get it to use my constructor.
If I've left out any information feel free to ask and I can fill it in. Hopefully someone has done this. I guess I could pass in the connection string everytime I instantiate a DbContext but I was hoping to get it to work like a Core app which is so much nicer.
thanks
EDIT:
Adding code to show how I'm using my DbContext
public bool Start()
{
using(var db = new MyContext())
{
var warehouse = (from w in db.Warehouses
where w.WarehouseCode == "ABCD"
select w).FirstOrDefault();
}
// other code...
}

ASP Core 3.1 multiple/alterante DB contexts

I have a question, about how to set up a project for multiple clients. We currently develop an ASP Core application. All clients use the same database structure, so they have identical databases, just filled with different users,... We currently use one publish, set up two test websites on IIS and each of those publishes has a different JSON config file containing the DB context (read out at Startp.cs).
Our problem here is, that if we have multiple clients, we have to copy our publish multiple times to maintain multiple websites for the IIS. We didn't find a way to just use one publish and define the config file to be used dependent on the URL/port of the connected client. We tried a tutorial for tenancy under ASP Core, but it failed at the User Manager.
Can someone point me out, to what's the best solution for this kind of webproject, which requires one shared website/publish under IIS, different DB contexts for each client (URL/port), user authentication (we used Identity for that). We tried this for weeks now, but failed to get it working. Our main problem is, that the DB context is created in the Startup.cs before the host is built, so we don't have any URL's from the request and can't create a specific DB context when the application starts. This means, we can't fill the UserStore and can't get the login to work. We know we could make a master table with all the users and domains, but we really would like to avoid this approach, as we are not sure, if the websites will run on the same server, or if one client may use his own internal company server.
EDIT: I need to be more specific, as I made a small mistake. The databases are identical. So i actually won't need different context classes, but just different connection strings. But they are set in the Startup.cs in the AddDbContext function...
EDIT2: I tried a few solutions now, and I'm always failing on Identity. It doesn't seem to work and crashes, when no DbContext is created directly during Startup.
EDIT3: As I failed so far, here are some Code Snippets from our Projekt
In our Startup.cs/ConfigureServices(IServiceCollection services)
We are loading our DB settings from a config file
IConfigurationSection DbSection = Configuration.GetSection("Database");
We are adding the DB Context
services.AddDbContext<MyDbContext>(options => options.UseSqlServer(MyDbContext.createConnectionString(DbSection), b => b.MigrationsAssembly("MyNamespace.DB").EnableRetryOnFailure()));
And setting up Identity
services.AddScoped<Microsoft.AspNetCore.Identity.SignInManager<MyUser>, MySignInManager>();
services.AddScoped<Microsoft.AspNetCore.Identity.IUserStore<MyUser>, MyUserStore>();
services.AddScoped<Microsoft.AspNetCore.Identity.UserManager<MyUser>, Services.LogIn.MyUserManager>();
services.AddIdentity<MyUser, MyRole>()
.AddUserManager<MyUserManager>()
.AddUserStore<MyUserStore>()
.AddRoleStore<MyRoleStore>()
.AddDefaultTokenProviders();
We are adding authorization
services.AddAuthorization(options =>
{ options.DefaultPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});
In our Startup.cs/Configure(...)
- We set UseAuthorization
app.UseAuthentication();
app.UseAuthorization();
In our MyDbContext class we are creating the connection string
public static string createConnectionString(IConfigurationSection configurationSection)
{
return #"server=" + configurationSection.GetSection("Server").Value +
";database=" + configurationSection.GetSection("Database").Value +
";persist security info=True;user id=" + configurationSection.GetSection("User").Value +
";password=" + configurationSection.GetSection("Password").Value + ";multipleactiveresultsets=True;";
}
So basically all we are trying to achieve, is to make different connections for different urls. Let's say we have 2 cients:
clientone.mysite.com:123 -> database_clientone
clienttwo.mysite.com:456 -> database_clienttwo
This does not work, as we don't have the domain when creating the DbContext. But we are required to have each database store it's own login credentials, instead of using a master table with all the logins for each existing users/databases...
And Identity doesn't seem to work with this scenario either.
If you are registering your DB context with Core's DI via AddDbContext call you can leverage it's overload which provides you access to IServiceProvider:
public void ConfigureServices(IServiceCollection services)
{
services
.AddEntityFrameworkSqlServer()
.AddDbContext<MyContext>((serviceProvider, options) =>
{
var connectionString = serviceProvider.GetService // get your service
// which can determine needed connection string based on your logic
.GetConnectionString();
options.UseSqlServer(connectionString);
});
}
Also here something similar is achieved with Autofac.
You can have multiple appsettings.json files and depending on client load appropriate json file .
With above case you will still be creating Db
Context in Startup.cs
public class ConfigurationService : IConfigurationService
{
public IConfiguration GetConfiguration()
{
var env = GetCurrentClient();//You can also set environment variable for client
CurrentDirectory = CurrentDirectory ?? Directory.GetCurrentDirectory();
return new ConfigurationBuilder()
.SetBasePath(CurrentDirectory)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: false)
.AddJsonFile($"appsettings.{env}.json", optional: true, reloadOnChange: false)
.AddEnvironmentVariables()
.Build();
}
}
Instead of using AddDbContext , try to use inject DBContext lazily.
Add a class DataContext which will implement DBContext
public class DataContext : DbContext
{
public DataContext(IConfigurationService configService)
: base(GetOptions( GetConnectionString())
{
this.ChangeTracker.LazyLoadingEnabled = true;
}
private static DbContextOptions GetOptions(string connectionString)
{
return SqlServerDbContextOptionsExtensions.UseSqlServer(new DbContextOptionsBuilder(), connectionString).Options;
}
public static string GetConnectionString()
{
//Your logic to read connection string by host
// This method will be called while resolving your repository
}
}
In your Startup.cs
remove AddDbContext() call
and instead use
public void ConfigureServices(IServiceCollection serviceCollection)
{
serviceCollection.AddScoped<DbContext, YourDBContext>();
//likewise Addscope for your repositories and other services
//Code to build your host
}
DI DbContext in your controller's constructor or in service's constructor
Since your DBContext is now being loaded lazily , you will be able decide on connection string depending on your host.
Hope this works for you!!
I tried to create an environment variable via web.config and reading it out within our application. But I couldn't find a way, how to setup multiple websites in IIS7 with the SAME publish/file-folder, but DIFFERENT web.config files. This didn't work either, or at least I had no clue how to achieve this...

Categories