EF Core multiple database same schema - c#

Using EF Core .net 2.2.
Trying to have an app where there is a "live" database and a "test" database backing my app. Currently I publish multiple sites each with their own DBContexts and just before publishing I comment out and swap the code for the connection string/db in my startup.cs.
ex:
//services.AddDbContext<DataContext>(options =>
// options.UseSqlServer(Configuration.GetConnectionString("TestDataContext")));
services.AddDbContext<DataContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("LiveDataContext")));
Then my two sites are
testdata.site.com and livedata.site.com
This works but it is time consuming and inefficient whenever updates are made to the site/controllers/views etc. Plus if i ever wanted more than two sites to share the same database schema, the publishing work required would compound even more.
Here is my ideal solution but I don't know how to accomplish it:
I want to send route data to the controller and have the controller decide the connection string when it does this portion of the controller:
private readonly POSContext _context;
public CashierController(POSContext context)
{
_context = context;
}
Example, the URL would be something like:
www.site.com/{test or live}/{controller}/{action}
Then a user could swap between the databases on the fly if needed.
I can work through the routing portion but I am really stuck on what to with the controller and startup database portion to make this work.
Anyone have an idea or can get me going on the right path?

It all depends on how you publish your applications and what level of control you have on your hosting server.
You can use multiple configuration files which have different connection string values, so instead of having two connection string names, you should have only one, for example, "MyAppConnectionString", and use environment based configuration files to override it when needed.
To read more about configuration, visit:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-2.2
Alternatively, you can use the hosting environment capability:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/environments?view=aspnetcore-2.2
Please find some useful information in this answer as well:
Automatically set appsettings.json for dev and release environments in asp.net core?

This is what I ended up doing. I looked at what #Norcino said above and referenced the links in his post.
I created multiple Appsettings{DBIdentifier}.json files (still kept the usual appsettings.json file as well), ex appsettingsste3.json and in these JSON files I put a connection string, all with the same DB Name, but pointing to different DBs on my SQL server.
ex:
{
"ConnectionStrings": {
"SiteDBContext":\\Connection string for unique DB, all with same schema/tables, etc\\
}
}
In my program.cs I created a function that looks at the current directory on my web server, since each site is in its own folder on my webserver (ex d:\inetpub\wwwsites\ste1, d:\inetpub\wwwsites\ste3, d:\inetpub\wwwsites\ste3), then take the last four characters of that string then run a switch statement on adding the extra json file.
The portion of program.cs that i modified looks like this:
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostingContext, config) =>
{
var dirStr = Directory.GetCurrentDirectory().ToString(); //Gets path of directory into string
var lastFour = dirStr.Substring(dirStr.Length - 4); //gets name of directory (last four characters in path)
switch (lastFour)
{
case "ste1":
case "ste2":
case "ste3":
string appStr = "appsettings" + lastFour.Substring(3) + ".json";
config.AddJsonFile(appStr, optional: false);
break;
}
})
.UseStartup<Startup>()
.Build();
Then of course, ConfigureServices in Startup.cs needs this:
services.AddDbContext<DataContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("SiteDBContext")));
Have not yet performed hard testing to know what performance will be like but i think it should be fine since program.cs only runs when the app is first started and so once the app is running there shouldn't be any performance degradation at all. (Am I right?)

Related

.NET Core route based authentication with multiple B2C environments

Situation
We have clients that should be able to login into our application. Our clients do also have clients, who also may login. Therefore we have an Azure AD B2C environment per client.
So, we want to have one single application that can be used to authenticate against multiple Azure B2C environments. We want to have this route-based. So:
/client1 goes to B2C environment Client1B2C, with user flow B2C_1_Client1
/client2 goes to B2C environment Client2B2C, with user flow B2C_1_Client2
Challenge
So, we need to define multiple instances of AddOpenIdConnect. I do this inside a specific builder, so my Startup.cs keeps clean:
Startup.cs
...
var AzureAdB2CSettings = new List<AzureAdB2COptions>();
Configuration.GetSection("Authentication:AzureAdB2C").Bind(AzureAdB2CSettings, c => c.BindNonPublicProperties = true);
services.AddAuthentication(sharedOptions =>
{
...
})
.AddAzureAdB2C(options => Configuration.Bind("Authentication:AzureAdB2C", options), AzureAdB2CSettings)
...
And there is the builder:
AzureAdB2CAuthenticationBuilderExtensions.cs
...
public static string policyToUse;
public static AuthenticationBuilder AddAzureAdB2C(this AuthenticationBuilder builder, Action<AzureAdB2COptions> configureOptions, List<AzureAdB2COptions> openIdOptions)
{
...
foreach(var b2c in openIdOptions)
{
builder.AddOpenIdConnect(b2c.SignUpSignInPolicyId, b2c.SignUpSignInPolicyId, options =>
{
options.Authority = b2c.Authority;
options.ClientId = b2c.ClientId;
options.CallbackPath = b2c.CallbackPath;
options.SignedOutCallbackPath = b2c.SignedOutCallbackPath;
options.ClientSecret = b2c.ClientSecret;
});
}
return builder;
}
...
public Task OnRedirectToIdentityProvider(RedirectContext context)
{
...
string policyToUse = "B2C_1_" + context.Request.Query["area"];
...
var b2cSettings = AzureAdB2CSettings.Find(x => x.SignUpSignInPolicyId.ToLower().Equals(policyToUse.ToLower()));
AzureAdB2CAuthenticationBuilderExtensions.policyToUse = b2cSettings.DefaultPolicy;
...
Yippee ya yeeey! We can have a dynamic amount of add AddOpenIdConnect, based on a configuration file. The chosen authentication scheme has been set to the static string "AzureAdB2CAuthenticationBuilderExtension.policyToUse".
But now it comes... how to define the Authorization header?
BackofficeController.cs
...
[Authorize(AuthenticationSchemes = AzureAdB2CAuthenticationBuilderExtensions.policyToUse)]
public async Task<IActionResult> ChooseBackoffice()
{
...
}
...
AUTCH!! You can't use dynamic attributes... Have tried to set the chosen scheme as a default, but it seems we can only define a default at startup, not during runtime...
Any suggestions how to solve this challenge?
One suggestion is to set all possible values of AzureAdB2CAuthenticationBuilderExtensions.policyToUse in config and read from there.
For each action method/controller (as per your use case), define the attribute value from these configs.
It seems indeed impossible at the moment to have multiple B2C environments connected to one Azure App Service.
Therefore there is a choice:
Don't do it. Just create one giant B2C environment.
Make a multi-instance application instead of a multi-tenant application.
Our partner came with another solution. We haven't explored this route. Who knows does this help somebody:
Orchard core. Seems like a multi-tenant .NET Core solution. Looks like a complete application, where this multi-tenant question will be handled.
We did choose option 2. This makes sure we have a good separation of data. There are more hosting costs, although with a multi-tenant application all the traffic does to one application. This does require better hardware, so is also more expensive. I do not know which option is more expensive.
Now comes the question how to deploy this efficiently, but that's another question...

.net core 3.1 call to User Secrets for SendGrid keys returns null values

I have set up a project following https://learn.microsoft.com/en-us/aspnet/core/security/authentication/accconfirm?view=aspnetcore-3.1&tabs=visual-studio
From Startup.cs:
services.AddTransient<IEmailSender, EmailSender>();
services.Configure<AuthMessageSenderOptions>(Configuration);
_secretOne = Configuration["SecretStuff:SecretOne"];
the _secretOne variable was added to prove that the correct secrets.json file is being accessed. It has both a SecretStuff block and a AuthMessageSenderOptions block.
In EmailSender.cs
public EmailSender(IOptions<AuthMessageSenderOptions> optionsAccessor)
{
Options = optionsAccessor.Value;
}
A breakpoint after Options = shows the keys with null values.
Eventually I gave up and hard coded the Options.SendGridKey and Options.SendGridUser and with this change the project works as it should.
This is my first use of User-Secrets so when it did not work I set up a console app that references the same secrets.json file and it sends emails.
I found a possible answer in: https://www.twilio.com/blog/2018/05/user-secrets-in-a-net-core-web-app.html
I changed Startup.cs to:
services.Configure<AuthMessageSenderOptions>(Configuration.GetSection("AuthMessageSenderOptions"));
and now the values are as they should be. I sure wasted a lot of time looking in the wrong places for an answer.

Store connection string for asp.net core 2 on aws eb

I deployed .net core 2 application on aws eb. I need staging, qa and prod servers, and don't how to handle connection strings?
Is it possible to store it in env variables on eb?
Or is it possible to make env for these three instances and to have different application.json file for each?
You can use Options pattern in ASP.NET Core
Example you have appsetting.json like this
"WebAppUrl": {
"Staging": "someurl",
"Qa": "someurl",
"Prod": "someurl"
}
I will need to define a model class like this to store information
public class WebAppUrl
{
public string Staging { get; set; }
public string Qa { get; set; }
public string Prod { get; set; }
}
So we have the the config structure. The last thing we need is register inside Startup.cs
services.Configure(configuration.GetSection("WebAppUrl"));
So you can use like this
private readonly IOptions<WebAppUrl> _webAppUrl;
public EmailSender(IOptions<WebAppUrl> _webAppUrl)
{
_webAppUrl = _webAppUrl;
}
_webAppUrl.Value.Staging;
_webAppUrl.Value.Qa;
_webAppUrl.Value.Prod;
Ok this is how Microsoft setup multiple appsetting.json file. You can take a look at because it's kinda long post
Here is how I config using json file base on enviroment
public Startup(
IConfiguration configuration,
IHostingEnvironment hostingEnvironment)
{
_configuration = configuration;
_hostingEnvironment = hostingEnvironment;
var builder = new ConfigurationBuilder();
if (_hostingEnvironment.IsDevelopment())
{
builder.AddUserSecrets<Startup>();
}
else
{
builder
.SetBasePath(hostingEnvironment.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{hostingEnvironment.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
}
}
Here's an approach using ebextensions & instance metadata:
Use ebextensions to manage customisation of the server before your app starts.
Your instance should have a role which has the ec2:DescribeTags permission.
Configure your EB application to set an environment tag.
You either have an application config file with connection strings per environment, or you use something like AWS Secrets manager (your instance role will need permission for this service) to hold your connection strings (better security).
The application code uses an environment variable to select the correct connection string from the config file, or Secrets manager.
You deploy your application with an init.config file inside .ebextensions folder off the root.
The init.config reads instance metadata to determine the environment and set the environment variable.
Your app starts, reads the variable, pulls the connection string and connect to correct db.
Below example is init.config for windows instance with powershell, but can be adapted to bash + aws cli.
The first step writes a powershell script out. The script reads the Environment tag and writes the value to a machine wide variable for the app to pickup. Second step invokes the script.
files:
"c:/cfn/init.ps1":
content: |
$instanceDoc = Invoke-RestMethod 'http://169.254.169.254/latest/dynamic/instance-identity/document'
$nameTagList = Get-EC2Tag -Region ($instanceDoc.region) -Filter #{name='resource-type';value='instance'},#{name='resource-id';value=$instanceDoc.instanceId},#{name='tag:Environment';value='*'} | Select -First 1
$envName = $nameTagList.Value
[Environment]::SetEnvironmentVariable("APP_ENV", $envName, [EnvironmentVariableTarget]::Machine
container_commands:
00-invoke-init:
command: powershell.exe -nologo -noprofile -file "c:\cfn\init.ps1"
So, there is a problem with handling env variables from elastic beanstalk for .net core application.
Here is the answer (hack) how to force application to get your env variables AWS Elastic Beanstalk environment variables in ASP.NET Core 1.0
So, all sensitive data like connection string should be on elastic beanstalk env variables. Others can be in appsettings.Staging.json, appsettings.Qa.json, etc.
Just don't forget to add ASPNETCORE_ENVIRONMENT var in env variables.
I would suggest putting configuration into AWS System Manager's Parameter Store. The Amazon.Extensions.Configuration.SystemsManager NuGet package adds Parameter Store as a configuration provider to the .NET Configuration system. Here is a blog post for more info. https://aws.amazon.com/blogs/developer/net-core-configuration-provider-for-aws-systems-manager/

Botframework How to change table storage connection string in startup based on incomming request

I'm using BotFramework version(v4) integrated with LUIS. In ConfigureServices(IServiceCollection services) method in startup.cs file I'm assigning storage and LUIS in the middleware.Below is the sample code.
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton(configuration);
services.AddBot<ChoiceBot>(options =>
{
options.CredentialProvider = new ConfigurationCredentialProvider(configuration);
var (luisModelId, luisSubscriptionKey, luisUri) = GetLuisConfiguration(configuration, "TestBot_Dispatch");//
var luisModel = new LuisModel(luisModelId, luisSubscriptionKey, luisUri);
var luisOptions = new LuisRequest { Verbose = true };
options.Middleware.Add(new LuisRecognizerMiddleware(luisModel, luisOptions: luisOptions));
//azure storage emulater
//options.Middleware.Add(new ConversationState<Dictionary<string, object>>(new AzureTableStorage("UseDevelopmentStorage=true", "conversationstatetable")));
IStorage dataStore = new AzureTableStorage("DefaultEndpointsProtocol=https;AccountName=chxxxxxx;AccountKey=xxxxxxxxx;EndpointSuffix=core.windows.net", "TableName");
options.Middleware.Add(new ConversationState<Dictionary<string,object>>(new MemoryStorage()));
options.Middleware.Add(new UserState<UserStateStorage>(dataStore));
}
}
My bot will be getting requests from users of different roles such as (admin,sales,etc..).I want to change the table storage connection-string passed to middleware based on the role extracted from the incoming request. I will get user role by querying DB from the user-name which is extracted from the current TurnContext object of an incoming request. I'm able to do this in OnTurn method, but as these are already declared in middleware I wanted to change them while initializing in the middleware itself.
In .NET Core, Startup logic is only executed once at, er, startup.😊
If I understand you correctly, what you need to be able to do is: at runtime, switch between multiple storage providers that, in your case, are differentiated by their underlying connection string.
There is nothing "in the box" that enables this scenario for you, but it is possible if use the correct extension points and write the correct plumbing for yourself. Specifically you can provide a customized abstraction at the IStatePropertyAccessor<T> layer and your upstream code would continue to work at that level abstraction and be none-the-wiser.
Here's an implementation I've started that includes something I'm calling the ConditionalStatePropertyAccessor. It allows you to create a sort of composite IStatePropertyAccessor<T> that is configured with both a default/fallback instance as well as N other instances that are supplied with a selector function that allows them to look at the incoming ITurnContext and, based on some details from any part of the turn, indicate that that's the instance that should be used for the scope of the turn. Take a look at the tests and you can see how I configure a sample that chooses an implementation based on the ChannelId for example.
I am a little busy at the moment and can't ship this right now, but I intend to package it up and ship it eventually. However, if you think it would be helpful, please feel free to just copy the code for your own use. 👍

.Net Core 2.1 Web and Console DbContexts

Im not known for my clarity when asking questions, so forgive me, also i have no formal training for any of this but im stumped.
I am mid upgrade from .Net 4.7.1 to .Net Core 2.1, my solution consists of 2 parts, an IIS Web Application for MVC, and a Console Application, the IIS App displays data, and the console application does all the actual processing.
Before i started this port for my console app when i needed stuff from the database i would simply
using (var db = new ApplicationDbContext())
{
SomethingModel model = db.X.First(x => x.Whatever == whatever);
}
And just like that i have the data i want from the database, butttt do you think i can do that with Core 2.1 can i hell.
I got all the code ported all the refrences resolved and so far as i can tell its ready to run. Except i cant call data from the database and im stumped, google just shows code first and ef stuff, or i dont know what im really asking.
So if anyone can help its much appreciated
-- Update 1---
The error is An Object refrence is required for the non-static field, metho or property Program._db
The DbModel is defined in Data/ApplicationDbContext.cs for the IIS App and is as follows
public ApplicationDbContext(DbContextOptions<ApplicationDbContext>
options)
: base(options)
{
}
-- Program.cs for Console App
class Program
{
private ApplicationDbContext _db { get; }
public Program(ApplicationDbContext context)
{
_db = context;
}
static void Main(string[] args)
{
new ExecutionEngine(_db).Run();
}
}
The previous way you wrote the code (using) was never a good idea. Your context should be request-scoped; using using can lead to all sorts of issues with entity tracking and totally destroys all the helpful caching EF does. The best method for getting a context instance was always dependency injection via a DI container.
ASP.NET Core uses dependency injection for everything, and because of this EF Core's DbContext is designed to be dependency injected. In this regard, it no longer uses a default constructor out of the box, which is why your old code is failing (it depends on there being a default constructor).
Long and short, do things right and inject your context. It looks like you're attempting to do this based on your update. However, you cannot inject into something like Program. This is the entry point for your application, which means literally nothing exists yet. If you take a look at your web app, you'll notice that Program there sets up the web host builder (using Startup) and then builds and runs it. Behind the scenes this is doing a bunch of stuff, including setting up the service collection. This is what you need to do in your console app (set up the service collection). That's relatively straight forward:
class Program
{
static void Main(string[] args)
{
var serviceProvider = new ServiceCollection()
.AddDbContext<ApplicationDbContext>(o =>
o.UseSqlServer("connection string"))
.BuildServiceProvider();
var context = serviceProvider.GetRequiredService<ApplicationDbContext>();
new ExecutionEngine(context).Run();
}
}
Now, this is a bit of overkill just based on the code you have here. You can simply new up an instance of your context via DbContextOptionsBuilder:
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseSqlServer("connection string")
.Options;
var context = new ApplicationDbContext(options);
However, using the service collection allows you to handle more advanced scenarios and better reuse your instances of things like your context across your codebase. Also it's worth mentioning that you should probably consider integrating configuration providers as well, so you don't need to hardcode your connection string. That's also relatively straight-forward:
var config = new ConfigurationBuilder()
.SetBasePath(Path.Combine(AppContext.BaseDirectory))
.AddJsonFile("appsettings.json", optional: true)
.Build();
You might also want to add environment-specific configuration:
var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
Then:
.AddJsonFile($"appsettings.{environment}.json", optional: true);
This is just the same as doing all this in a web app, so you can add whatever type of configuration you like.

Categories

Resources